【如何实现一个简单的RPC框架】系列文章:
【远程调用框架】如何实现一个简单的RPC框架(一)想法与设计
【远程调用框架】如何实现一个简单的RPC框架(二)实现与使用
【远程调用框架】如何实现一个简单的RPC框架(三)优化一:利用动态代理改变用户服务调用方式
【远程调用框架】如何实现一个简单的RPC框架(四)优化二:改变底层通信框架
【远程调用框架】如何实现一个简单的RPC框架(五)优化三:软负载中心设计与实现
第一个优化以及第二个优化修改后的工程代码可下载资源 如何实现一个简单的RPC框架
参考【远程调用框架】如何实现一个简单的RPC框架(一)想法与设计,对应四个模块一共创建了四个Java工程,他们分别是:
-
【ServiceAccessCenter】:一个Java Web应用,服务注册查找中心
-
【LCRPC】:LCRPC服务框架的核心部分,最终利用Maven生成一个jar包提供服务发布以及远程调用功能
-
【LCRPCTest】:服务端的测试部分,发布一个计算器服务
-
【LCRPCClientTest】:客户端的测试部分,利用服务发布者提供的二方包,远程调用该计算器服务
1. 服务注册查找中心
一个Java Web应用,服务注册查找中心
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:ServiceInfoDO
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模式。
来源:oschina
链接:https://my.oschina.net/u/238082/blog/1554037