【远程调用框架】如何实现一个简单的RPC框架(二)实现与使用

【如何实现一个简单的RPC框架】系列文章:java

【远程调用框架】如何实现一个简单的RPC框架(一)想法与设计 
【远程调用框架】如何实现一个简单的RPC框架(二)实现与使用 
【远程调用框架】如何实现一个简单的RPC框架(三)优化一:利用动态代理改变用户服务调用方式 
【远程调用框架】如何实现一个简单的RPC框架(四)优化二:改变底层通讯框架 
【远程调用框架】如何实现一个简单的RPC框架(五)优化三:软负载中心设计与实现 
第一个优化以及第二个优化修改后的工程代码可下载资源 如何实现一个简单的RPC框架web

 

 

参考【远程调用框架】如何实现一个简单的RPC框架(一)想法与设计,对应四个模块一共建立了四个Java工程,他们分别是:spring

  • 【ServiceAccessCenter】:一个Java Web应用,服务注册查找中心apache

  • 【LCRPC】:LCRPC服务框架的核心部分,最终利用Maven生成一个jar包提供服务发布以及远程调用功能json

  • 【LCRPCTest】:服务端的测试部分,发布一个计算器服务数组

  • 【LCRPCClientTest】:客户端的测试部分,利用服务发布者提供的二方包,远程调用该计算器服务缓存

1. 服务注册查找中心

一个Java Web应用,服务注册查找中心tomcat

1.1 接口设计

1.1.1 服务注册接口

  • (1)url:localhost:8080/ServiceAccessCenter/serviceRegistry.do POST请求
  • (2)参数:JSON格式的字符串,以下:
{
"interfaceName":"interfaceName",
"version":"version",
"implClassName":"imlClassName",
"ip":"ip"
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

(3)响应结果:true(表明注册成功)false(表明注册失败)多线程

1.1.2 服务ip地址列表查询接口

  • (1)url: 
    localhost:8080/ServiceAccessCenter/queryServiceIPsByID.do?serviceID=interfaceName_version GET请求
  • (2)参数:serviceID 服务的惟一标识
  • (3)响应结果:该服务对应的ip列表数组/空字符串,以下:
["ip","ip"]
  • 1

1.1.3 服务信息查询接口

  • (1)url: 
    localhost:8080/ServiceAccessCenter/queryServiceInfoByID.do?serviceID=interfaceName_version GET请求
  • (2)参数:serviceID 服务的惟一标识
  • (3)响应结果:该服务的全部信息,以下:
{"interfaceName":"interfaceName","version":"version","implClassName":"imlClassName","ips":["ip","ip"],"ip":"ip"}
  • 1

1.2 类设计

1.2.1 UML类图

这里写图片描述

1.2.2 核心代码

核心代码主要是对注册服务集合的管理,包括增长以及查询。注意多线程操做的问题。 
- (1)描述服务信息的DO:ServiceInfoDOmvc

package whu.edu.lcrpc.servicecenter.entity;

/**
 * Created by apple on 17/3/26.
 */

import lombok.Data;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * 服务的描述信息,包括:
 * 服务惟一标识\实现类全限定名\ip地址列表等
 */
@Data
public class ServiceInfoDO {

    private String interfaceName;//服务对应接口名称
    private String version;//版本号
    private String implClassName;//实现该接口的类
    private Set<String> ips = new HashSet<>();//该服务的地址
    private String ip;//某一次注册服务的地址
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • (2)以一个单例类保存服务信息的集合:
public class ServicesSingle {
    private static ServicesSingle servicesSingle = null;
    private Map<String,ServiceInfoDO> services = null;

    private ServicesSingle(){
        services = new ConcurrentHashMap<>();
    }

    public static ServicesSingle getServiceSingle(){
        synchronized (ServicesSingle.class){
            if (servicesSingle == null){
                servicesSingle = new ServicesSingle();
            }
        }
        return servicesSingle;
    }


}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • (3)对服务信息集合操做的接口:
public interface IServiceAccess {
    /**
     * 根据用户提供的服务信息,进行服务的注册
     * @param serviceInfo  要注册的服务信息
     * @return
     */
    public boolean serviceRegistry(ServiceInfoDO serviceInfo);

    /**
     * 根据服务的惟一标识ID查询服务的地址列表
     * @param serviceID
     * @return
     */
    public Set<String> queryServiceIPsByID(String serviceID);

    /**
     * 根据服务的惟一标识ID查询服务的信息
     * @param serviceID
     * @return
     */
    public ServiceInfoDO queryServiceInfoByID(String serviceID);

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • (4)接口的实现类:
/**
 * 完成服务的管理操做:注册\查询
 */
public class ServiceAccessImpl implements IServiceAccess{
    @Override
    public boolean serviceRegistry(ServiceInfoDO serviceInfo) {
        if (serviceInfo.getInterfaceName() == null || serviceInfo.getInterfaceName().length() ==0 ||
                serviceInfo.getImplClassName() == null || serviceInfo.getImplClassName().length() ==0 ||
                serviceInfo.getVersion() == null || serviceInfo.getVersion().length() ==0 ||
                serviceInfo.getIp() == null || serviceInfo.getIp().length() ==0)
            return false;
        String serviceID = serviceInfo.getInterfaceName() + "_" + serviceInfo.getVersion();
        if (ServicesSingle.getServiceSingle().getServices().containsKey(serviceID)){
            ServicesSingle.getServiceSingle().getServices().get(serviceID).getIps().add(serviceInfo.getIp());
        }else {
            serviceInfo.getIps().add(serviceInfo.getIp());
            ServicesSingle.getServiceSingle().getServices().put(serviceID,serviceInfo);
        }
        return true;
    }

    @Override
    public Set<String> queryServiceIPsByID(String serviceID) {
        if (!ServicesSingle.getServiceSingle().getServices().containsKey(serviceID))
            return null;


        return ServicesSingle.getServiceSingle().getServices().get(serviceID).getIps();
    }

    @Override
    public ServiceInfoDO queryServiceInfoByID(String serviceID) {
        if (!ServicesSingle.getServiceSingle().getServices().containsKey(serviceID))
            return null;


        return ServicesSingle.getServiceSingle().getServices().get(serviceID);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

2. LCRPC服务框架核心部分

LCRPC服务框架的核心部分,最终利用Maven生成一个jar包提供服务发布以及远程调用功能 
整个工程是由maven以及spring开发构建的,是一个Java工程,最终利用maven构建jar包提供用户使用。 
pom依赖配置以下:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>whu.edu.lcrpc.</groupId>
  <artifactId>lcrpc-core</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>lcrpc-core</name>
  <url>http://maven.apache.org</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <spring.version>4.3.3.RELEASE</spring.version>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
    <!--spring依赖-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-beans</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-web</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <!--gson依赖-->
    <dependency>
      <groupId>com.google.code.gson</groupId>
      <artifactId>gson</artifactId>
      <version>2.2.4</version>
    </dependency>

    <!--lombok依赖-->
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.16.10</version>
    </dependency>

    <!--json解析依赖-->
    <dependency>
      <groupId>net.sf.json-lib</groupId>
      <artifactId>json-lib</artifactId>
      <version>2.4</version>
    </dependency>
  </dependencies>
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <configuration>
          <source>1.8</source>
          <target>1.8</target>
          <encoding>UTF-8</encoding>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85

2.1 服务框架UML类图

这里写图片描述

(看不清可访问服务框架UML类图

2.2 服务调用部分

服务调用端提供LCRPCConsumerImpl类给用户使用,该类中包含两个必要的属性变量分别是interfaceNeme(接口名)、version(版本号),用来标识某一个LCRPCConsumer实例对象是对哪个服务的调用,同时提供方法serviceConsumer,用户经过传入要调用的方法名称以及参数信息,实现对该接口某一个方法的远程调用。LCRPCConsumer类使用一个帮助类ConsumerServiceImpl来实现整个调用过程。同时,设计了一些自定义异常,用于在程序出错时抛出给用户进行捕获。

核心代码

  • (1)类LCRPCConsumerImpl提供用户使用,主要实现方法的远程调用
@Data
public class LCRPCConsumerImpl implements ILCRPCConsumer {

    /**
     * 如下两个变量共同组成服务的惟一标识
     */
    private String interfaceName;//服务对应接口的全限定名
    private String version;//服务版本号
    private IConsumerService consumerService;//初始化客户端辅助类

    public LCRPCConsumerImpl(){
        consumerService = new ConsumerServiceImpl();
    }



    @Override
    public Object serviceConsumer(String methodName, List<Object> params) throws LCRPCServiceIDIsIllegal,LCRPCServiceNotFound,LCRPCRemoteCallException {

        //若服务惟一标识没有提供,则抛出异常
        if (interfaceName == null || interfaceName.length() == 0
                || version == null || version.length() == 0)
            throw new LCRPCServiceIDIsIllegal();
        //step1. 根据服务的惟一标识获取该服务的ip地址列表
        String serviceID = interfaceName + "_" + version;
        Set<String> ips = consumerService.getServiceIPsByID(serviceID);
        if (ips == null || ips.size() == 0)
            throw new LCRPCServiceNotFound();

        //step2. 路由,获取该服务的地址,路由的结果会返回至少一个地址,因此这里不须要抛出异常
        String serviceAddress = consumerService.getIP(serviceID,methodName,params,ips);

        //step3. 根据传入的参数,拼装Request对象,这里必定会返回一个合法的request对象,因此不须要抛出异常
        LCRPCRequestDO requestDO = consumerService.getRequestDO(interfaceName,version,methodName,params);

        //step3. 传入Request对象,序列化并传入服务端,拿到响应后,反序列化为object对象
        Object result = null;
        try {
            result = consumerService.sendData(serviceAddress,requestDO);
        }catch (Exception e){
            //在服务调用的过程种出现问题
            throw new LCRPCRemoteCallException(e.getMessage());
        }
        if (result == null)throw new LCRPCRemoteCallException(Constant.SERVICEUNKNOWNEXCEPTION);
        //step4. 返回object对象
        return result;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • (2)帮助类 ConsumerServiceImpl,主要实现一些辅助函数,包括服务ip列表的查询,路由,数据的发送等。
public class ConsumerServiceImpl implements IConsumerService {
    @Override
    public Set<String> getServiceIPsByID(String serviceID) {

        //调用服务注册查找中心的服务,获取ip列表
        Set<String> ips = new HashSet<>();
        String url = "http://" + Constant.SERVICEACCESSCENTER_IP + ":" + Constant.SERVICEACCESSCENTER_PORT + "/"
                + Constant.QUERYSERVICEIPSBYID + "?serviceID=" + serviceID;

        Set status = new HashSet<Integer>();
        status.add(200);
        StringBuilder response = new StringBuilder();
        GetRemoteInfo.getRemoteInfo(url,"GET",null,null,response,status);
        if (response.length() == 0)return ips;
        ips = new Gson().fromJson(response.toString(),ips.getClass());
        return ips;
    }

    @Override
    public String getIP(String serviceID, String methodName,List<Object> params, Set<String> ips) {
        //能够根据接口\方法\参数进行路由,这里咱们先简单实现,选出列表的第一个,模拟路由的过程
        String[] temparr = new String[ips.size()];
        ips.toArray(temparr);
        return temparr[0];
    }

    @Override
    public LCRPCRequestDO getRequestDO(String interfaceName, String version, String methodName, List<Object> params) {
        LCRPCRequestDO requestDO = new LCRPCRequestDO();
        requestDO.setInterfaceName(interfaceName);
        requestDO.setMethodName(methodName);
        requestDO.setParams(params);
        requestDO.setVersion(version);
        return requestDO;
    }

    @Override
    public Object sendData(String ip,LCRPCRequestDO requestDO) throws IOException, ClassNotFoundException {
        ObjectOutputStream objectOutputStream = null;
        Socket socket = null;
        ObjectInputStream objectInputStream = null;
        try {
            socket = new Socket(ip, Constant.PORT);//向远程服务端创建链接
            objectOutputStream = new ObjectOutputStream(socket.getOutputStream());//得到输出流
            objectOutputStream.writeObject(requestDO);//发送序列化结果
            objectOutputStream.flush();
            socket.shutdownOutput();
            //等待响应
            objectInputStream = new ObjectInputStream(socket.getInputStream());//得到输入流
            Object result = objectInputStream.readObject();//序列化为Object对象
            objectInputStream.close();
            objectOutputStream.close();
            socket.close();
            return result;
        }catch (Exception e){
            throw e;
        }finally {
            try {
                if(objectInputStream != null)objectInputStream.close();
                if (objectOutputStream != null)objectOutputStream.close();
                if (socket !=null )socket.close();
            } catch (IOException e) {
                e.printStackTrace();
                throw e;
            }

        }


    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71

2.3 服务发布部分

服务发布部分,主要包括两部分,第一是服务的监听,第二是服务的注册与处理。服务的监听,经过开启一个socket监听线程ServerThread来实现。当有客户端请求后,开启新的处理线程ServerProcessThread,来进行数据的解析、服务的调用、数据响应等操做。提供给用户使用的是类LCRPCProviderImpl,主要包含四个必要属性变量(interfaceName、version、implClassName、ip),对要发布的服务进行描述。该类主要提供的方法是服务的注册。同时还设计了自定义异常类,用于在出错时抛出,他们分别是:LCRPCServiceInfoNotComplete(要发布的服务的信息不完整)、LCRPCServiceListenFailed(服务监听失败)、LCRPCServiceRegistryFailed(服务注册失败)。 
核心代码

  • (1)LCRPCProviderImpl提供给用户使用,用于服务的发布与注册
@Data
public class LCRPCProviderImpl implements ILCRPCProvider{

    /**
     * 如下变量为发布一个服务的必要变量
     */
    private String interfaceName;//服务对应接口的全限定名
    private String version;//服务版本号
    private String implClassName;//实现该服务接口的类
    private String ip;//发布该服务的地址

    private static boolean isListened = false;//是否已经开启监听

    private IProviderService providerService;
    public LCRPCProviderImpl(){
        providerService = new ProviderServiceImpl();


        //开启服务监听端口
        if (!isListened ){
            if (providerService.startListen())
            isListened = true;
            else throw new LCRPCServiceListenFailed();
        }
    }

    public void checkInfo(){
        //先判断服务参数信息是否完整
        if (interfaceName == null || interfaceName.length() == 0 ||
                version == null || version.length() == 0 ||
                implClassName == null || implClassName.length() ==0 ||
                ip == null || ip.length() ==0)
            throw new LCRPCServiceInfoNotComplete();
    }

    @Override
    public boolean servicePublish() {
        checkInfo();
        //step1. 注册服务.注册服务以前先判断服务是否开启,若没有开启,则首先开启服务
        synchronized (LCRPCProviderImpl.class){
            if (!isListened){
                if (providerService.startListen())
                    isListened = true;
                else throw new LCRPCServiceListenFailed();
            }
            providerService.serviceregistry(interfaceName,version,implClassName,ip);
        }
        return true;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • (2)线程类ServerThread开启socket监听,等待客户端的请求,开启处理线程进行处理
public class ServerThread implements Runnable{

    @Override
    public void run() {
        try {
            ServerSocket serverSocket = new ServerSocket(Constant.PORT);
            System.out.println("已经开始监听,能够注册服务了");
            while (true){
                Socket socket = serverSocket.accept();
                new Thread(new ServerProcessThread(socket)).start();//开启新的线程进行链接请求的处理
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • (3)服务端链接处理线程ServerProcessThread,负责解析客户端的请求,利用反射对响应服务进行方法调用,将调用结果序列化发送给调用端
public class ServerProcessThread implements Runnable {

    private Socket socket;
    public ServerProcessThread(Socket socket){
        this.socket = socket;
    }
    @Override
    public void run() {

        //获取客户端的数据,并写回
        //等待响应
        ObjectInputStream objectInputStream = null;
        ObjectOutputStream objectOutputStream = null;
        try {

            //step1. 将请求数据反序列化为request对象
            objectInputStream = new ObjectInputStream(socket.getInputStream());//得到输入流
            LCRPCRequestDO requestDO = (LCRPCRequestDO) objectInputStream.readObject();//序列化为Object对象

            //step2. 获取服务接口实现类的信息
            ServiceInfoDO serviceInfoDO = ServicesSingle.getServiceSingle().getServices().get(requestDO.getInterfaceName() + "_" + requestDO.getVersion());

            //step3.利用反射建立对象,调用方法,获得结果
            Class clz = Class.forName(serviceInfoDO.getImplClassName());
            Method methodethod = null;
            Object result = null;
            if (requestDO.getParams() != null && requestDO.getParams().size() > 0){
                Class []classes = new Class[requestDO.getParams().size()];
                Object []obj = requestDO.getParams().toArray();
                int i = 0;
                for (Object object:requestDO.getParams()){
                    if(object instanceof Integer){
                        classes[i] = Integer.TYPE;
                    }else if(object instanceof Byte){
                        classes[i] = Byte.TYPE;
                    }else if(object instanceof Short){
                        classes[i] = Short.TYPE;
                    }else if(object instanceof Float){
                        classes[i] = Float.TYPE;
                    }else if(object instanceof Double){
                        classes[i] = Double.TYPE;
                    }else if(object instanceof Character){
                        classes[i] = Character.TYPE;
                    }else if(object instanceof Long){
                        classes[i] = Long.TYPE;
                    }else if(object instanceof Boolean){
                        classes[i] = Boolean.TYPE;
                    }else {
                        classes[i] = object.getClass();
                    }

                    i++;
                }
                methodethod = clz.getDeclaredMethod(requestDO.getMethodName(),classes);
                result = methodethod.invoke(clz.newInstance(),obj);
            }else {
                methodethod = clz.getDeclaredMethod(requestDO.getMethodName());
                result = methodethod.invoke(clz.newInstance());
            }

            //step4.将结果序列化,写回调用端

            objectOutputStream = new ObjectOutputStream(socket.getOutputStream());//得到输出流
            objectOutputStream.writeObject(result);//发送序列化结果
            objectOutputStream.flush();
            socket.shutdownOutput();
            socket.close();

        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            try {
                if(objectInputStream != null)objectInputStream.close();
                if (objectOutputStream != null)objectOutputStream.close();
                if (socket !=null )socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • (4)服务发布的帮助类ProviderServiceImpl
public class ProviderServiceImpl implements IProviderService {
    @Override
    public boolean startListen() {
        new Thread(new ServerThread()).start();
        return true;
    }

    @Override
    public boolean serviceregistry(String interfaceName, String version, String implClassName, String ip) {
        //注册到服务注册查找中心的同时也要缓存到内存services
        //step1. 注册到服务中心

        String url = "http://" + Constant.SERVICEACCESSCENTER_IP + ":" + Constant.SERVICEACCESSCENTER_PORT + "/"
                + Constant.SERVICEREGISTRY;
        Map<String,String> headers = new HashMap();
        headers.put("Content-Type","application/json");
        JSONObject param = new JSONObject();
        param.put("interfaceName",interfaceName);
        param.put("version",version);
        param.put("implClassName",implClassName);
        param.put("ip",ip);
        Set status = new HashSet<Integer>();
        status.add(200);
        StringBuilder response = new StringBuilder();
        GetRemoteInfo.getRemoteInfo(url,"POST",headers,param.toString(),response,status);

        if (response.equals("false")) throw new LCRPCServiceRegistryFailed();

        //step2. 存入到缓存
        if (ServicesSingle.getServiceSingle().getServices().containsKey(interfaceName + "_" + version)){
            ServicesSingle.getServiceSingle().getServices().get(interfaceName + "_" + version).getIps().add(ip);
        }
        else {
            ServiceInfoDO serviceInfoDO = new ServiceInfoDO();
            serviceInfoDO.setInterfaceName(interfaceName);
            serviceInfoDO.setVersion(version);
            serviceInfoDO.setImplClassName(implClassName);
            serviceInfoDO.getIps().add(ip);
            ServicesSingle.getServiceSingle().getServices().put(interfaceName + "_" + version,serviceInfoDO);
        }
        System.out.println("成功注册服务:[" + interfaceName  + "]");

        return true;
    }


}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47

3. LCRPC服务框架使用

3.1 服务发布

服务端的测试部分,发布一个计算器服务; 
该部分为一个基本Java应用,采用spring+maven的开发方式。

  • (1)pom文件以下,包括对LCRPC服务框架提供jar包的依赖、对spring的依赖等
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>whu.edu.lcrpc</groupId>
  <artifactId>lcrpc-test</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>lcrpc-test</name>
  <url>http://maven.apache.org</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <spring.version>4.3.3.RELEASE</spring.version>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
    <!--spring依赖-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-beans</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-web</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>${spring.version}</version>
    </dependency>

    <!--lombok依赖-->
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.16.10</version>
    </dependency>
    <!--对LCRPC服务框架的依赖-->
    <dependency>
      <groupId>whu.edu.lcrpc</groupId>
      <artifactId>lcrpc-core</artifactId>
      <version>1.0-SNAPSHOT</version>
    </dependency>
  </dependencies>
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <configuration>
          <source>1.8</source>
          <target>1.8</target>
          <encoding>UTF-8</encoding>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • (2)spring的配置文件: 
    主要是配置LCRPC的服务发布端的bean,经过该bean来进行服务的发布,配置以下:
<?xml version="1.0" encoding="UTF-8"?>
<beans default-autowire="byName" xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--ILCRPCProvider的bean 进行配置后 自动发布服务-->
    <bean id="lcrpcProvider" class="whu.edu.lcrpc.server.impl.LCRPCProviderImpl" init-method="servicePublish">
        <property name="implClassName" value="whu.edu.lcrpc.service.impl.CaculatorImpl"></property>
        <property name="version" value="0.1"></property>
        <property name="ip" value="127.0.0.1"></property>
        <property name="interfaceName" value="whu.edu.lcrpc.service.ICaculator"></property>
    </bean>
</beans>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

其中四个属性均是必要的。同时init-method的配置也是必要的,该函数经过用户配置为属性信息进行服务的发布。 
若是是在web容器中进行服务的发布,例如tomcat,则只须要在web.xml中配置spring的监听,就能够进行服务的发布。或者在main函数中利用ApplicationContext进行配置文件的读取。 
在本例中,为了简单,采用main函数的方式。代码以下:

  • (3)main函数
package whu.edu.lcrpc.app;

/**
 * Created by apple on 17/3/26.
 */

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * 服务端测试类,调用LCRPC接口,进行服务的发布
 */
public class ProviderTest {

    public static void main(String[] args) {
        //直接读取spring的配置文件就好
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • (4)下面就是须要发布的服务对应的接口,以及接口的实现类。咱们为了进行不一样状况的测试,所以写了四个函数,参数和返回值分别是Java中的基本类型、引用类型。 
    接口代码以下:
package whu.edu.lcrpc.service;

/**
 * Created by apple on 17/3/26.
 */

import whu.edu.lcrpc.entity.MyParamDO;
import whu.edu.lcrpc.entity.MyResultDO;

/**
 * 服务对应接口
 * 该服务实现一个简单的计算器服务,实现加减乘除四个基本功能
 */
public interface ICaculator {

    /**
     * 加
     * @param n1
     * @param n2
     * @return
     */
    public double add(double n1,double n2);

    /**
     * 减
     * @param n1
     * @param n2
     * @return
     */
    public MyResultDO minus(double n1, double n2);


    /**
     * 乘
     * @param myParamDO
     * @return
     */
    public MyResultDO multiply(MyParamDO myParamDO);

    /**
     * 除
     * @param myParamDO
     * @return
     */
    public double divide(MyParamDO myParamDO);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47

接口实现类的代码以下:

package whu.edu.lcrpc.service.impl;

import whu.edu.lcrpc.entity.MyParamDO;
import whu.edu.lcrpc.entity.MyResultDO;
import whu.edu.lcrpc.service.ICaculator;

/**
 * Created by apple on 17/3/26.
 */

/**
 * 服务对应接口的实现类
 */
public class CaculatorImpl implements ICaculator {
    @Override
    public double add(double n1, double n2) {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return n1 + n2;
    }

    @Override
    public MyResultDO minus(double n1, double n2) {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        MyResultDO myResultDO = new MyResultDO();
        myResultDO.setResult(n1 - n2);
        return myResultDO;
    }

    @Override
    public MyResultDO multiply(MyParamDO myParamDO) {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        MyResultDO myResultDO = new MyResultDO();
        myResultDO.setResult(myParamDO.getN1() * myParamDO.getN2());
        return myResultDO;
    }

    @Override
    public double divide(MyParamDO myParamDO) {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return myParamDO.getN2() / myParamDO.getN1();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58

最终该工程会生成一个二方包提供给服务调用端使用。

3.2 服务调用

客户端的测试部分,利用LCRPC提供的包以及服务发布者提供的二方包,远程调用该计算器服务。

  • (1)pom文件
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>whu.edu.lcrpc</groupId>
  <artifactId>lcrpc-clienttest</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>lcrpc-clienttest</name>
  <url>http://maven.apache.org</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <spring.version>4.3.3.RELEASE</spring.version>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
    <!--spring依赖-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-beans</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-web</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>${spring.version}</version>
    </dependency>

    <!--lombok依赖-->
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.16.10</version>
    </dependency>

    <!--对服务发布者二方包的依赖-->
    <dependency>
      <groupId>whu.edu.lcrpc</groupId>
      <artifactId>lcrpc-test</artifactId>
      <version>1.0-SNAPSHOT</version>
    </dependency>
    <!--对LCRPC服务框架的依赖-->
    <dependency>
      <groupId>whu.edu.lcrpc</groupId>
      <artifactId>lcrpc-core</artifactId>
      <version>1.0-SNAPSHOT</version>
    </dependency>

  </dependencies>
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <configuration>
          <source>1.8</source>
          <target>1.8</target>
          <encoding>UTF-8</encoding>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • (2)spring配置文件,主要是配置LCRPC提供的客户端接口,利用该bean,进行方法的调用,每个consumer的bean表明对一个服务接口的调用
<?xml version="1.0" encoding="UTF-8"?>
<beans default-autowire="byName" xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="rpcConsumer" class="whu.edu.lcrpc.server.impl.LCRPCConsumerImpl">
        <property name="interfaceName" value="whu.edu.lcrpc.service.ICaculator" ></property>
        <property name="version" value="0.1"></property>
    </bean>
    <bean id="ConsumerTest" class="whu.edu.lcrpc.app.ConsumerTest"></bean>


</beans>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

属性interfaceName以及version是必要的,标识该bean是对哪个远程服务的调用。

  • (3)服务调用帮助类,在该类中咱们利用LCRPC提供的接口,对计算器服务进行调用,分别调用该服务的加减乘除四个方法
package whu.edu.lcrpc.app;

/**
 * Created by apple on 17/3/26.
 */

import lombok.Data;
import whu.edu.lcrpc.entity.MyParamDO;
import whu.edu.lcrpc.entity.MyResultDO;
import whu.edu.lcrpc.server.ILCRPCConsumer;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;

/**
 * 客户端测试类,调用LCRPC接口,进行服务的调用
 */
@Data
public class ConsumerTest {

    @Resource
    ILCRPCConsumer rpcConsumer;
    public void add() {
        //调用接口 进行加减乘除
        List<Object> params = new ArrayList<>();
        params.add(1.0);
        params.add(2.0);
        Double result = (Double) rpcConsumer.serviceConsumer("add",params);
        System.out.println("add: " + result);

    }

    public void minus(){
        List<Object> params = new ArrayList<>();
        params.add(1.0);
        params.add(2.0);
        MyResultDO result = (MyResultDO) rpcConsumer.serviceConsumer("minus",params);
        System.out.println("minus: " + result.getResult());
    }

    public void multiply(){
        List<Object> params = new ArrayList<>();
        MyParamDO myParamDO = new MyParamDO();
        myParamDO.setN1(1.0);
        myParamDO.setN2(2.0);
        params.add(myParamDO);
        MyResultDO result = (MyResultDO) rpcConsumer.serviceConsumer("multiply",params);
        System.out.println("multiply: " + result.getResult());
    }

    public void divide(){
        List<Object> params = new ArrayList<>();
        MyParamDO myParamDO = new MyParamDO();
        myParamDO.setN1(1.0);
        myParamDO.setN2(2.0);
        params.add(myParamDO);
        Double result = (Double) rpcConsumer.serviceConsumer("divide",params);
        System.out.println("divide: " + result);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • (4)运行主类,开启四个线程,同时对四个方法进行远程调用
package whu.edu.lcrpc.app;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * Created by apple on 17/3/27.
 */
public class Main {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        ConsumerTest consumerTest = (ConsumerTest) context.getBean("ConsumerTest");
        new Thread(()->{
            consumerTest.add();
        }).start();
        new Thread(()->{
            consumerTest.minus();
        }).start();
        new Thread(()->{
            consumerTest.multiply();
        }).start();
        new Thread(()->{
            consumerTest.divide();
        }).start();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

3.3 效果

  • (1)首先开启服务注册查找中心
  • (2)服务测试端,运行main函数进行服务的发布,运行后,服务发布成功,以下图所示: 
    这里写图片描述
  • (3)服务调用端测试,运行main函数进行服务远程调用,运行后,服务调用成功,以下图所示,对1,2两个数字开启四个线程分别进行了加减乘除运算,四个结果同时输出:

这里写图片描述

注意:全部工程代码可访问资源:实现本身的远程调用框架,进行下载

未完待续。。。。这只是服务框架的初步实现版本,期待优化提高后的第二个版本~

值得期待的是:  (1)怎样利用动态代理,使得用户与访问本地接口同样调用远程服务  (2)当链接增多,服务是否能够支撑?须要改变IO模式。

相关文章
相关标签/搜索