Spring通过注解加载Bean的基本原理浅析(java版)

最近在看spring的源码,想写一系列的文章,来模拟spring的一些最基本功能的实现。
部分源码是从spring中抽取出来的,然后进行了无限的精简,目的是让入门的弟兄们也可以看得很清楚。
首先这第一篇,就是说的spring的IOC功能中,是如何实现通过注解来加载Bean文件的。

文章原创,转载请注明出处www.neohope.com

经过无限精简之后,整体流程为:
1、初始化bean工厂
bean工厂根据配置,加载带有指定annotation的类,并放到了map中
2、从bean工厂获取一个bean
通过bean的id,实例化一个类,并返回

需要的前置知识为:
1、spring的基本知识
2、反射

然后是源码:
1、TestAnnotation.java
这是个注解类,声明了一个新的注解类型,用于表示bean及bean的名字

package com.neohope.annotation.test;

import java.io.File;
import java.io.IOException;
import java.lang.annotation.*;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * Created by Hansen on 2016/3/10.
 */

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
public @interface TestAnnotation {
    String ntype();
    String nname();
    String nauthor();
    String nversion();
    String nmsg();
}

2、TestBean.java
这是个Bean,使用了注解作为标识,是用于具体加载的类

package com.neohope.annotation.test;

/**
 * Created by Hansen on 2016/3/10.
 */

@TestAnnotation(nauthor = "neohope",nversion="1.0",ntype = "BEAN",nname = "testbean", nmsg = "this is just a message")
public class TestBean {
    public String name;
    public int age;
    public String sex;
}

3、ClassPathScanner.java
从spring中抽取的,类扫描工具类
其作用为,通过指定包名,扫描包名下所有的类
可用于jar文件中类的扫描

package com.neohope.annotation.test;

import java.io.File;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLConnection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * Created by Hansen on 2016/3/10.
 */
public class ClassPathScanner {

    /**
     * Get all class name in given packageName
     * @param packageName the package name
     * @return the result as String array
     * @throws IOException in case of I/O errors
     * @see #getAllClassFileInPackage
     */
    public static String[] getAllClassNameInPackage(String packageName) throws IOException, URISyntaxException {
        String[] classFiles = getAllClassFileInPackage(packageName);
        Set<String> result = new LinkedHashSet<String>(classFiles.length);
        for(String clazz : classFiles)
        {
            result.add(clazz.replace(".class", "").replace("/", ".").replace("\\", "."));
        }
        return result.toArray(new String[result.size()]);
    }

    /**
     * Get all class files in given packageName
     * @param packageName the package name
     * @return the result as String array
     * @throws IOException in case of I/O errors
     * @see #findAllClassPathByPackageName
     * @see #loadJarClasses
     * @see #loadFileClasses
     */
    protected static String[] getAllClassFileInPackage(String packageName) throws IOException, URISyntaxException {
        if (packageName.startsWith("/")) {
            packageName = packageName.substring(1);
        }
        if (packageName.endsWith("/")) {
            packageName = packageName.substring(0,packageName.length()-1);
        }

        URL[] rootDirResources = findAllClassPathByPackageName(packageName);
        Set<String> result = new LinkedHashSet<String>(16);
        for (URL rootDirResource : rootDirResources) {
            if (rootDirResource.toString().toUpperCase().startsWith("JAR")) {
                result.addAll(loadJarClasses(rootDirResource));
            }
            else {
                String rootEntryPath = rootDirResource.getPath().replace(packageName,"").replace("/",File.separator).substring(1);
                result.addAll(loadFileClasses(rootDirResource,rootEntryPath));
            }
        }

        return result.toArray(new String[result.size()]);
    }

    /**
     * Find all class path with the given packageName via the ClassLoader.
     * @param packageName the absolute path within the classpath
     * @return the result as URL array
     * @throws IOException in case of I/O errors
     * @see java.lang.ClassLoader#getResources
     */
    protected static URL[] findAllClassPathByPackageName(String packageName) throws IOException {
        ClassLoader cl = ClassPathScanner.class.getClassLoader();
        Enumeration<URL> resourceUrls = (cl != null ? cl.getResources(packageName) : ClassLoader.getSystemResources(packageName));
        Set<URL> result = new LinkedHashSet<URL>();
        while (resourceUrls.hasMoreElements()) {
            URL url = resourceUrls.nextElement();
            result.add(url);
        }
        return result.toArray(new URL[result.size()]);
    }

    /**
     * Get all the class file in given class dir
     * @param rootClassDir the root directory
     * @return all the class in rootClassDir
     * @throws IOException if directory contents could not be retrieved
     */
    protected static Set<String> loadFileClasses(URL rootClassDir, String rootEntryPath) throws IOException, URISyntaxException {
        File rootDir;
        rootDir = new File(rootClassDir.toURI());
        if(!rootDir.exists() || !rootDir.isDirectory() || !rootDir.canRead())
        {
            return Collections.emptySet();
        }

        Set<String> result = new LinkedHashSet<String>(8);
        EnumClassFiles(rootDir, rootEntryPath, result);
        return result;
    }

    /**
     * Recursively get all class files in the given dir
     * @param dir the given directory
     * @param result the Set of class names to add to
     * @throws IOException if directory contents could not be retrieved
     */
    protected static void EnumClassFiles(File dir, String rootEntryPath, Set<String> result) throws IOException {
        File[] dirContents = dir.listFiles();
        if (dirContents == null) {
            return;
        }
        for (File content : dirContents) {
            if (content.isDirectory()) {
                if (!content.canRead()) {
                }
                else {
                    EnumClassFiles(content, rootEntryPath, result);
                }
            }
            else {
                if(content.toString().endsWith(".class")) {
                    result.add(content.toString().replace(rootEntryPath, ""));
                }
            }
        }
    }

    /**
     * Get all the class file in given jar path
     * @param jarPath the given jar path
     * @return the Set of matching Resource instances
     * @throws IOException in case of I/O errors
     */
    protected static Set<String> loadJarClasses(URL jarPath)
            throws IOException {
        JarFile jarFile;
        String jarFileUrl;
        String rootEntryPath = "";
        Set<String> result = new LinkedHashSet<String>(8);

        URLConnection connection = jarPath.openConnection();
        if (connection instanceof JarURLConnection) {
            JarURLConnection jarCon = (JarURLConnection) connection;
            jarFile = jarCon.getJarFile();
            jarFileUrl = jarCon.getJarFileURL().toExternalForm();
            JarEntry jarEntry = jarCon.getJarEntry();
            rootEntryPath = (jarEntry != null ? jarEntry.getName() : "");
        } else {
            return Collections.emptySet();
        }

        try {
            for (Enumeration<JarEntry> entries = jarFile.entries(); entries.hasMoreElements(); ) {
                JarEntry entry = entries.nextElement();
                String entryPath = entry.getName();

                if (entryPath.startsWith(rootEntryPath) && entryPath.endsWith(".class")) {
                    result.add(entryPath);
                }
            }

        } catch (Exception ex) {
        }

        return result;
    }

    public static void main(String[] args) throws IOException, URISyntaxException {
        //String[] classes = getAllClassNameInPackage("com/neohope/annotation");
        //String[] classes = getAllClassNameInPackage("org/apache/log4j/config");

        String[] classes = getAllClassNameInPackage("/com/neohope/annotation/");
        //String[] classes = getAllClassNameInPackage("org/apache/log4j/config");
        for(String clazz : classes)
        {
            System.out.println(clazz);
        }
    }
}

4、ClassAnnotationScanner.java
工厂类,初始化时,通过annotation过滤bean,并将bean的名称及class放到map中
获取实例时,通过bean名称,获取class,并实例化,返回

package com.neohope.annotation.test;

import java.io.IOException;
import java.net.URISyntaxException;
import java.util.HashMap;

/**
 * Created by Hansen on 2016/3/10.
 */

public class ClassAnnotationScanner {

    /**
     * HashMap of bean name and bean class
     */
    static HashMap<String, Class> beanMap = new HashMap<String, Class>();

    /**
     * Load all bean class with annotation from package
     * @param packageName the package name
     * @return void
     * @throws IOException in case of I/O errors
     * @throws URISyntaxException in case of URI syntax error
     * @throws ClassNotFoundException in case of class not found
     * @see ClassPathScanner#getAllClassNameInPackage
     */
    protected static void loadBeanClasses(String packageName) throws IOException, URISyntaxException, ClassNotFoundException {
        String[] classes = ClassPathScanner.getAllClassNameInPackage(packageName);
        for(String clazz : classes)
        {
            Class loadedClazz = Class.forName(clazz);
            Object obj  = loadedClazz.getAnnotation(TestAnnotation.class);
            if(obj==null)continue;

            TestAnnotation ta = (TestAnnotation)obj;
            String beanName = ta.nname();
            beanMap.put(beanName,loadedClazz);
        }
    }

    /**
     * init the bean factory
     * @param packageName the package name
     * @return void
     * @throws IOException in case of I/O errors
     * @throws URISyntaxException in case of URI syntax error
     * @throws ClassNotFoundException in case of class not found
     * @see #loadBeanClasses
     */
    public static void initBeanFactory(String packageName) throws IOException, URISyntaxException, ClassNotFoundException {
        loadBeanClasses(packageName);
    }

    /**
     * get bean by bean name
     * @param beanName the bean name
     * @return new object of the bean class
     * @throws IllegalAccessException in case of illegal access
     * @throws InstantiationException in case of instantiation error
     */
    protected static Object getBean(String beanName) throws IllegalAccessException, InstantiationException {
        Class loadedClazz = beanMap.get(beanName);
        if(loadedClazz!=null) {
            return loadedClazz.newInstance();
        }
        else
        {
            return null;
        }
    }

    public static void main(String[] args) throws IOException, URISyntaxException, ClassNotFoundException, InstantiationException, IllegalAccessException {
        initBeanFactory("com/neohope/annotation/");
        TestBean bean = (TestBean)getBean("testbean");
        bean.name = "neohope";
    }

}

文章原创,转载请注明出处www.neohope.com

处理windows控制字符0x17

今天同事遇到了很诡异的问题,就是从数据库中读取一个字段时,有些数据中,会多一个0x17的控制字符。

暂时没太好的处理方法,自己写了个测试例子,仅供参考吧:

for java

    public static void main(String[] args)
    {
        byte[] b= { 0x30, 0x30, 0x1f, 0x37, 0x39, 0x33, 0x39, 0x36, 0x34, 0x31 };
        byte[] bt={0x1f};
        String s1 = new String(b);
        String st= new String(bt);
        String s2 = s1.replace(st,"");
        String s3 = s1.replace((char)0x1f,' ').replace(" ","");
        System.out.println(s1);
        System.out.println(s2);
        System.out.println(s3);
    }

for charp

        static void Main(string[] args)
        {
            Byte[] b = { 0x30, 0x30, 0x1f, 0x37, 0x39, 0x33, 0x39, 0x36, 0x34, 0x31 };
            Byte[] bt = {0x1f};
            String s1 = System.Text.Encoding.Default.GetString(b);
            String st = System.Text.Encoding.Default.GetString(bt);
            String s2 = s1.Replace(st, "");
            String s3 = s1.Replace((char) (0x1F), ' ').Replace(" ", "");
            Console.WriteLine(s1);
            Console.WriteLine(s2);
            Console.WriteLine(s3);
            Console.ReadLine();
        }

使用Dubbo实现RPC简单示例02

简单的Dubbo RPC调用描述如下:
dubbo-arch

复杂的Dubbo RPC调用描述如下:
dubbo-arch-ext

在这里我们先用zookeeper来做registry,做一个简单的例子:

一、首先是zookeeper:
1、到zookeeper进行下载
2、解压
3、拷贝conf/zoo_sample.cfg到conf/zoo.cfg,然后按需要修改配置
4、双击bin/zkServer.cmd

二、然后是接口定义,服务端和接口端都会用到,最好打一个jar包,防止错误修改:
IJustATest.java

package com.neohope.dubbo.test;

public interface IJustATest {
    public String SayHelloTo(String name);
    public int Add(int a, int b);
}

三、再就是服务端实现:
1、新建java程序,添加mvn功能,引用dubbo-x.x.x.jar

2、服务实现类MyDubboService.java

package com.neohope.dubbo.test;

public class MyDubboService implements IJustATest{

    public String SayHelloTo(String name) {
        return "Hello " + name;
    }

    public int Add(int a, int b) {
        return a+b;
    }
}

3、服务注册
ServceTest.java

package com.neohope.dubbo.test;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class ServceTest {
    public static void main(String[] args) throws Exception {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[] {"applicationContext.xml"});
        context.start();

        System.in.read();
    }
}

4、spring配置

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd
            http://code.alibabatech.com/schema/dubbo
            http://code.alibabatech.com/schema/dubbo/dubbo.xsd
            ">

    <!-- 具体的实现bean -->
    <bean id="myDubboService" class="com.neohope.dubbo.test.MyDubboService" />

    <!-- 提供方应用信息,用于计算依赖关系 -->
    <dubbo:application name="neo_service_provider"  />

    <!-- 使用zookeeper注册中心暴露服务地址 -->
    <dubbo:registry address="zookeeper://127.0.0.1:2181" />

    <!-- 用dubbo协议在20880端口暴露服务 -->
    <dubbo:protocol name="dubbo" port="20880" />

    <!-- 声明需要暴露的服务接口 -->
    <dubbo:service interface="com.neohope.dubbo.test.IJustATest" ref="myDubboService" />

</beans>

5、编译运行

四、最后是客户端实现:
1、新建java程序,添加mvn功能,引用dubbo-x.x.x.jar

2、服务调用
ServceTest.java

package com.neohope.dubbo.test;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class ClientTest {
    public static void main(String[] args) throws Exception {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"applicationContext.xml"});
        context.start();

        IJustATest proxy = (IJustATest) context.getBean("myDubboService");
        System.out.println(proxy.SayHelloTo("neohope")) ;
        System.out.println(proxy.Add(1,2)) ;

        System.in.read();
    }
}

3、spring配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd  
            http://code.alibabatech.com/schema/dubbo  
            http://code.alibabatech.com/schema/dubbo/dubbo.xsd  
            ">

    <!-- 消费方应用名,用于计算依赖关系,不是匹配条件,不要与提供方一样 -->
    <dubbo:application name="neo_service_consumer" />

    <!-- 使用zookeeper注册中心暴露服务地址 -->
    <dubbo:registry address="zookeeper://127.0.0.1:2181" />

    <!-- 生成远程服务代理,可以像使用本地bean一样使用demoService -->
    <dubbo:reference id="myDubboService" interface="com.neohope.dubbo.test.IJustATest" />

</beans>

4、编译运行

使用Dubbo实现RPC简单示例01

由于需要用到dubbo-admin,所以直接下载源码进行编译的

1、到github下载源码
dubbo

2、用mvn生成eclipse工程

mvn eclipse:eclipse

3、导入后,进行编译
如果不是为了看代码方便,直接mvn编译也不错哦

4、将spring版本从2升级到3,我用的是3.2.16.RELEASE
如果考虑到后面的dubbo-admin的话,可以使用citrus-webx-all-3.1.6的相同版本,3.2.7.RELEASE

	<properties>
		<spring_version>3.2.16.RELEASE</spring_version>
	</properties>
	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>org.springframework</groupId>
				<artifactId>spring-context</artifactId>
				<version>${spring_version}</version>
			</dependency>
		</dependencies>
	</dependencyManagement>

5、准备将netty3升级到netty4,发现API差距太大,只好后面再搞了哦

6、dubbo-addmin要修改一下依赖

<!--升级citrus-webx-all到3.1.6,但不要升级到3.2.x版本,一堆的错-->
<dependency>
	<groupId>com.alibaba.citrus</groupId>
	<artifactId>citrus-webx-all</artifactId>
	<version>3.1.6</version>
</dependency>

<!--添加依赖包-->
<dependency>
        <groupId>org.apache.velocity</groupId>
        <artifactId>velocity</artifactId>
        <version>1.7</version>
</dependency>

<!--如果你和我一样,用的spring版本与citrus-webx-all不一致,要手工排除一套spring依赖包-->

7、这样就全部编译通过了哦

8、后面准备手工merge一下dubbox的部分代码,可惜他们也没能升级netty4

浅析SOA框架演化

说到SOA的兴起,已经是几年前的事情咯。

按我的理解,SOA框架的演化,主要按四条路来进行的:

第一条路,就是从CORBA衍生,可以作为CORBA的替代品。
这一条路上,典型代表为ICE、Thrift,Avro,ProtocolBuffer+GRPC
其典型行为是,定义idl,对每种语言提供编译器及运行库,然后提供RPC调用功能

第二条路,就是语言框架需要
这一条路上,主要代表为DCOM,EJB,DUBBO,最初目的都是为了给语言框架提供更好的分布式功能。
其典型行为是,语言或框架本身不具备改功能,对其进行了扩展。自身对原语言框架依赖比较严重。

第三条路,就是HTTP中新规范的出现
这一条路上,主要代表为SOAP,REST。
其典型行为是,每种语言都有多种实现,定义schema,每种语言会有多种库支持,最后通过HTTP通讯进行调用

第四条路,就是功能整合
这一条路上,主要代表为WCF,各类ESB产品
其典型特征为:提供多种SOA解决方案

具体使用的时候,其实大家可以看到,这些框架都是万变不离其宗。
那就是,都需要接口描述语言(无论是IDL、WSDL还是什么),都提供良好的支持功能开发较为简单,都是SOA框架解决通讯问题,对于开发人员透明。
大家具体使用是,根据实际情况选取即可。

公司SOA架构演化

最开始的时候,公司没有采用任何SOA架构。
(虽然用到了COM,但没有用到DCOM)
(用到了SOAP,但仅仅是做接口使用)

2011年,开始用EJB,但只在一个项目使用了EJB。

2012年,突然涌进了大量的接口,于是求救于ESB,成为了下面的样子。
ESB+SOAP
ESB+HL7
ESB+REST

2015年,开始向RPC框架求救。
ESB+Dubbo+(SOAP/REST/HLU)

使用ProtocolBuffer实现RPC简单示例03

接第01部分,本节用来说明Java语言的代码实现。

使用Protoc.exe生成java代码之后,会生成两个java文件,Client与Server都需要包含这两个文件。

首先是Server端:
1、新建一个java项目,引用以下jar包
protobuf-java-3.0.0-beta-2.jar
grpc-all-0.13.1.jar
guava-18.0.jar
netty-all-4.1.0.CR1.jar
okhttp-2.5.0.jar
okio-1.6.0.jar

2、项目中添加生成的两个java文件。

3、新建一个类MyGRPCServer,实现JustATestGrpc.JustATest接口

package com.neohope.protobuf.grpc.test;

import io.grpc.stub.StreamObserver;

public class MyGRPCServer implements JustATestGrpc.JustATest{

    @Override
    public void add(JustATestOuterClass.AddRequest request, StreamObserver<JustATestOuterClass.AddResponse> responseObserver) {
        JustATestOuterClass.AddResponse rsp =  JustATestOuterClass.AddResponse.newBuilder().setC(request.getA() + request.getB()).build();

        responseObserver.onNext(rsp);
        responseObserver.onCompleted();
    }

    @Override
    public void sayHelloTo(JustATestOuterClass.Person request, StreamObserver<JustATestOuterClass.HelloResponse> responseObserver) {
        JustATestOuterClass.HelloResponse rsp =  JustATestOuterClass.HelloResponse.newBuilder().setRsp("Hello "+ request.getName()).build();

        responseObserver.onNext(rsp);
        responseObserver.onCompleted();
    }
}

4、修改TestServer.java

package com.neohope.protobuf.grpc.test;

import io.grpc.Server;
import io.grpc.ServerBuilder;
import io.grpc.netty.NettyServerBuilder;

import java.io.IOException;
import java.util.Scanner;

public class TestServer {
    public static void main(String[] args) throws IOException {
        Server server = NettyServerBuilder.forPort(1900)
                .addService(JustATestGrpc.bindService(new MyGRPCServer()))
                .build()
                .start();

        Scanner scanner =new Scanner(System.in);
        scanner.nextLine();
    }
}

5、编译运行
然后是Client端:
1、新建一个java项目,引用以下jar包
protobuf-java-3.0.0-beta-2.jar
grpc-all-0.13.1.jar
guava-18.0.jar
netty-all-4.1.0.CR1.jar
okhttp-2.5.0.jar
okio-1.6.0.jar

2、项目中添加生成的两个java文件。

3、修改TestClient.java

package com.neohope.protobuf.grpc.test;

import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;

public class TestClient {

    public static void main(String[] args)
    {
        ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", 1900)
                .usePlaintext(true)
                .build();

        JustATestGrpc.JustATestBlockingStub blockingStub = JustATestGrpc.newBlockingStub(channel);

        JustATestOuterClass.AddRequest areq = JustATestOuterClass.AddRequest.newBuilder().setA(1).setB(2).build();
        JustATestOuterClass.AddResponse arsp = blockingStub.add(areq);
        System.out.println(arsp.getC());

        JustATestOuterClass.Person preq = JustATestOuterClass.Person.newBuilder().setAge(30).setName("neohope").setSex(JustATestOuterClass.Person.SexType.MALE).build();
        JustATestOuterClass.HelloResponse prsp = blockingStub.sayHelloTo(preq);
        System.out.println(prsp.getRsp());

        channel.shutdown();
    }
}

4、编译运行

使用ProtocolBuffer实现RPC简单示例02

接第01部分,本节用来说明C#语言的代码实现。

使用Protoc.exe生成cs代码之后,会生成两个cs文件,Client与Server都需要包含这两个文件。

首先是Server端:
1、新建一个Console项目,引用Protoc程序集中以下几个dll文件,并添加生成的CS文件
Google.Protobuf.dll
Grpc.Core.dll
System.Interactive.Async.dll

2、新建一个类MyGRPCServer,实现JustATest.IJustATest接口

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Com.Neohope.Protobuf.Grpc.Test;
using Grpc.Core;

namespace TestProtoBufferCliet
{
    class MyGRPCServer : JustATest.IJustATest
    {
        public Task<AddResponse> Add(AddRequest request, ServerCallContext context)
        {
            AddResponse rsp  = new AddResponse();
            rsp.C = request.A + request.B;
            return Task.FromResult(rsp);
        }

        public Task<HelloResponse> SayHelloTo(Person request, ServerCallContext context)
        {
            HelloResponse rsp = new HelloResponse();
            rsp.Rsp = "Hello " + request.Name;
            return Task.FromResult(rsp);
        }
    }
}

3、修改Program.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Com.Neohope.Protobuf.Grpc.Test;
using Grpc.Core;
using TestProtoBufferCliet;


namespace TestProtoBuffer
{
    class Program
    {
        static void Main(string[] args)
        {
            Server server = new Server
            {
                Services = { JustATest.BindService(new MyGRPCServer()) },
                Ports = { new ServerPort("localhost", 1900, ServerCredentials.Insecure) }
            };
            server.Start();

            
        }
    }
}

4、编译运行
其中,非托管dll要如下放置

.
│  yourprogram.exe
│  
└─nativelibs
    ├─windows_x64
    │      grpc_csharp_ext.dll
    │      
    └─windows_x86
            grpc_csharp_ext.dll
            

然后是Client端:
1、新建一个Console项目,引用Protoc程序集中以下几个dll文件,并添加生成的CS文件
Google.Protobuf.dll
Grpc.Core.dll
System.Interactive.Async.dll

2、修改Program.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Com.Neohope.Protobuf.Grpc.Test;
using Grpc.Core;

namespace TestProtoBufferCliet
{
    class Program
    {
        static void Main(string[] args)
        {
            Channel channel = new Channel("127.0.0.1:1900", ChannelCredentials.Insecure);
            JustATest.JustATestClient client = JustATest.NewClient(channel);

            Person p = new Person();
            p.Name = "neohope";
            p.Age = 30;
            p.Sex = Person.Types.SexType.MALE;
            HelloResponse prsp = client.SayHelloTo(p);
            Console.WriteLine(prsp.Rsp);

            AddRequest areq = new AddRequest();
            areq.A = 1;
            areq.B = 2;
            AddResponse arsp = client.Add(areq);
            Console.WriteLine(arsp.C);

            channel.ShutdownAsync().Wait();
            Console.ReadLine();
        }
    }
}

3、编译运行
其中,非托管dll要如下放置

.
│  yourprogram.exe
│  
└─nativelibs
    ├─windows_x64
    │      grpc_csharp_ext.dll
    │      
    └─windows_x86
            grpc_csharp_ext.dll
            

使用ProtocolBuffer实现RPC简单示例01

ProtocolBuffer大家多是使用其强大的序列化功能。但从3.0版本起,官方提供了GRPC作为其RPC功能的扩展。同样可以用于跨语言RPC通讯了哦。其通信架构如下图所示:

grpc_concept

使用ProtocolBuffer+GRPC的时候,首先要先下载ProtocolBuffer、GRPC,各种语言的编译工具及语言支持包,本例主要用到C#和Java。

ProtoBuffer的编译工具:
protoc.exe
可以到github下载

Java的工具及支持包,都可以到mvnrepository下载到
protobuf-java-3.0.0-beta-2.jar
grpc-all-0.13.1.jar
guava-18.0.jar
netty-all-4.1.0.CR1.jar
okhttp-2.5.0.jar
okio-1.6.0.jar
protoc-gen-grpc-java.exe(protoc-gen-grpc-java-0.13.1-windows-x86_32.exe 或 protoc-gen-grpc-java-0.13.1-windows-x86_64.exe)

C#的工具及支持包,都可以到NuGet上下载到
Google.Protobuf.dll(测试时NuGet没有3.0版本,我从github下源码,把源码从CS6.0人肉降级到CS5.0,用VS2013把编译通过)
Grpc.Core.dll
grpc_csharp_ext.dll(32及64版本)
System.Interactive.Async.dll
grpc_csharp_plugin.exe

使用新版本ProtocolBuffer+GRPC的时候,先定义一个接口描述文件,比如我自己写了一个很简单的接口。
JustATest.proto

syntax = "proto3";

option java_package = "com.neohope.protobuf.grpc.test";

package com.neohope.protobuf.grpc.test;

service JustATest {
  rpc Add (AddRequest) returns (AddResponse){}
  rpc SayHelloTo (Person) returns (HelloResponse);
}

message Person {
  enum SexType {
    MALE = 0;
    FEMALE = 1;
    OTHER = 2;
    UNKNOWN = 4;
  }
  
  string name = 1;
  SexType sex = 2;
  int32 age = 3;
}

message HelloResponse {
  string rsp = 1;
}

message AddRequest {
  int32 a = 1;
  int32 b = 2;
}

message AddResponse {
  int32 c = 1;
}

然后,工具分别编译为java及c#语言

#CS
protoc.exe --plugin=protoc-gen-grpc=grpc_csharp_plugin.exe --grpc_out gen-cs -I. --csharp_out gen-cs JustATest.proto

#Java
protoc.exe --plugin=protoc-gen-grpc=protoc-gen-grpc-java.exe --grpc_out gen-java -I. --java_out gen-java JustATest.proto

使用Avro实现RPC简单示例03

接第01部分,本节用来说明Java语言的代码实现。

使用avro生成java代码之后,会生成两个java文件,无论是Client还是Server都要包含这两个文件。

首先是Server端:
1、新建一个java项目,引用以下jar包
avro-1.8.0.jar
avro-ipc-1.8.0.jar
jackson-core-asl-1.9.13.jar
jackson-mapper-asl-1.9.13.jar
netty-3.5.13.Final.jar
slf4j-api-1.7.7.jar
slf4j-simple-1.7.7.jar

2、项目中添加生成的两个java文件。

3、新建一个类MyAvroServer,实现JustATest接口

package com.neohope.avro.test;

import org.apache.avro.AvroRemoteException;

public class MyAvroServer implements JustATest{
    @Override
    public CharSequence SayHelloTo(Person person) throws AvroRemoteException {
        return "Hello "+person.getName();
    }

    @Override
    public int Add(int a, int b) throws AvroRemoteException {
        return a+b;
    }
}

4、修改TestServer.java

package com.neohope.avro.test;

import org.apache.avro.ipc.NettyServer;
import org.apache.avro.ipc.specific.SpecificResponder;

import java.net.InetSocketAddress;
import java.util.Scanner;

public class TestServer {
    public static void main(String[] args) {
        NettyServer server = new NettyServer(new SpecificResponder(
                JustATest.class,
                new MyAvroServer()),
                new InetSocketAddress(1900));
        server.start();
        Scanner sc=new Scanner(System.in);
        sc.nextLine();
        server.close();
    }
}

5、编译运行

然后是Client端:
1、新建一个java项目,引用以下jar包
avro-1.8.0.jar
avro-ipc-1.8.0.jar
jackson-core-asl-1.9.13.jar
jackson-mapper-asl-1.9.13.jar
netty-3.5.13.Final.jar
slf4j-api-1.7.7.jar
slf4j-simple-1.7.7.jar

2、项目中添加生成的两个java文件。

3、修改TestClient.java

package com.neohope.avro.test;

import org.apache.avro.AvroRemoteException;
import org.apache.avro.ipc.NettyTransceiver;
import org.apache.avro.ipc.specific.SpecificRequestor;
import org.apache.avro.util.Utf8;

import java.io.IOException;
import java.net.InetSocketAddress;

public class TestClient {
    public static void main(String[] args) throws IOException {
        NettyTransceiver client = new NettyTransceiver(new InetSocketAddress("localhost",1900));

        JustATest proxy = (JustATest) SpecificRequestor.getClient(JustATest.class, client);

        Person person = new Person();
        person.setSex(new Utf8("male"));
        person.setName(new Utf8("neohope"));
        person.setAddress(new Utf8("shanghai"));
        System.out.println(proxy.SayHelloTo(person));
        System.out.println(proxy.Add(1,2));

        client.close();
    }
}

4、编译运行