盒子
盒子
文章目录
  1. 一 结构说明
  2. 二 整体思路
  3. 三 具体实现
    1. 3.1 interface项目的实现
      1. 3.1.1 数据的返回对象(pojo)
      2. 3.1.2 数据传输对象
      3. 3.1.4接口
    2. 3.2 provider(server)项目的实现
      1. 3.2.1 BookService的具体实现
      2. 3.2.2 netty的服务端实现
      3. 3.2.3 根据数据传输对象完成反射
    3. 3.3 consumer(client)项目的实现
      1. 3.3.1数据的发送
      2. 3.3.2数据的接收
      3. 3.3.3 数据的发送的代理
  4. 四 代码的测试

基于netty的简易RPC

前言:代码以上传点击跳转

一 结构说明

1.1 相信大家使用过RPC框架,例如(dubbo等等)和netty,我这里就不再多说了,基本项目架构如下
基于RPC的简单项目架构

1.2 基于上面,netty也是一样,不过是consumer是netty的客户端,provider是netty的服务端,基本如图所示
netty简易架构
1.3 即一共三个项目
完整架构
该项目需要完成的功能,interface项目中定义了一个接口(BookService),其实现类在provider项目中,
现在consumer项目使用interface项目中的接口(BookService)调用其方法(findBookById),
得到其方法的结果

二 整体思路

2.1
服务提供者(也就是netty服务端,后面我统称服务提供者)
服务消费者(也就是netty客户端,后面我统称服务消费者)

服务消费者传递一个数据对象给服务端,再从服务端得到返回的数据即可。那么重点就是

  • 客户端传输给服务端的数据对象是什么
  • 服务端又如何才能根据数据对象进行本地方法的调用?

2.2
解决如上两个问题便完成了简易的RPC
先说第一个:
客户端传输给服务端的数据对象是什么?

  • 那个类
  • 类中的什么方法
  • 方法的参数类型(一个类又有许多的方法)
  • 方法的参数值

再说第二个:
服务端又如何才能根据数据对象进行本地方法的调用?

直接通过反射调用本地的实现类即可,再将得到的数据返回

三 具体实现

3.1 interface项目的实现

该项目中包括consumer和provider都需要的东西

  • 数据的返回对象(pojo)
  • 方法的接口 (service等等)
  • 客户端与服务端的传输对象

3.1.1 数据的返回对象(pojo)

我这里就定义一个数据返回对象(Book),set/get等等省略

1
2
3
4
public class Book implements Serializable {
private String id;
private String name;
}

3.1.2 数据传输对象

既然消费者需要通过反射,那么传输对象应是如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class ClassInfo implements Serializable {
/**
* 调用的具体类的类名全路径(即包名加类名 service.BookService)
*/
private String fullPath;
/**
* 类中的那个方法
*/
private String methodName;
/**
* 方法中的参数类型
*/
private Class []paramType;
/**
* 方法中的参数值
*/
private Object []paramValue;

3.1.4接口

1
2
3
4
5
6
7
8
public interface BookService {
/**
* 根据Id查找指定的图书
* @param bookId
* @return
*/
Book findBookById(String bookId);
}

3.2 provider(server)项目的实现

该项目中包括

  • 接口的实现
  • 以及netty的服务端实现
  • 根据数据传输对象完成反射

3.2.1 BookService的具体实现

1
2
3
4
5
6
7
8
9
10
11
public class BookServiceImpl implements BookService {
@Override
public Book findBookById(String bookId) {
// 如果bookId为1 就返回图书对象
if("1".equals(bookId)){
return new Book("1","骆驼祥子");
}
// 其它返归空
return null;
}
}

3.2.2 netty的服务端实现

因代码以上传github,就不在在这里写了

3.2.3 根据数据传输对象完成反射

其中需要用到reflections的jar包实现,根据结果类型,找到其下所有实现类

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
/**
* 得到某接口下某个实现类的全路径
* @param classInfo
* @return 类的全路径(包名加类名)如:service.impl.BookServiceImpl
* @throws Exception
*/
private String getImplClassName(ClassInfo classInfo) throws Exception{
// 拿到BookService类
Class superClass=Class.forName(classInfo.getFullPath());
int indexOf = classInfo.getFullPath().lastIndexOf(".");
// 指定从那个包下开始搜索(我这里是因为service与service.impl都在service下,所以我直接截取接口的包名即可)
Reflections reflections = new Reflections(classInfo.getFullPath().substring(0,indexOf-1));
//得到某接口下的所有实现类
Set<Class> ImplClassSet=reflections.getSubTypesOf(superClass);
if(ImplClassSet.size()==0){
System.out.println("未找到实现类");
return null;
}else if(ImplClassSet.size()>1){
System.out.println("找到多个实现类,未明确使用哪一个");
return null;
}else {
//把集合转换为数组
Class[] classes=ImplClassSet.toArray(new Class[0]);
//得到实现类的名字
return classes[0].getName();
}
}

测试如下

1
2
3
4
5
6
7
8
9
10
   /**
* 测试上面结果
*/
public static void testGetImplClassName(){
ClassInfo classInfo = new ClassInfo("service.BookService","findBookById",
new Class[]{String.class},new Object[]{"1"});
String implClassName = getImplClassName(classInfo);
// service.impl.BookServiceImpl
System.out.println(implClassName);
}

接下来就可以根据反射调用实现类的具体方法了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 通过反射调用其方法并返回
* @param classInfo
* @return
*/
private Object invokeAndReturn(ClassInfo classInfo) {
try {
String implClassName = getImplClassName(classInfo);
Class<?> clazz = Class.forName(implClassName);
Object newInstance = clazz.newInstance();
Method method = clazz.getMethod(classInfo.getMethodName(), classInfo.getParamType());
return method.invoke(newInstance,classInfo.getParamValue());
} catch (Exception e) {
e.printStackTrace();
}
return null;
}

3.3 consumer(client)项目的实现

该项目中包括

  • 数据的发送
  • 以及数据的接收

    3.3.1数据的发送

    根据netty中的ChannelFuture得到通道,后发送数据
    1
    2
    3
    ChannelFuture future = b.connect("127.0.0.1", 9999).sync();
    // 将需要调用的方法数据发到服务端
    future.channel().writeAndFlush(classInfo).sync();

3.3.2数据的接收

在客户端收到服务端发送消息处,返回数据结果即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class ClientHandler extends ChannelInboundHandlerAdapter {


private Object response;
public Object getResponse() {
return response;
}

/**
* 读取服务器端返回的数据(远程调用的结果)
* @param ctx
* @param msg
* @throws Exception
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
response = msg;
ctx.close();
}
}

3.3.3 数据的发送的代理

数据的返送使用反射中的代码

四 代码的测试

1
2
3
4
5
6
7
8
9
10
public class TestRPC {

public static void main(String[] args) {
// 通过代理获得接口对象
BookService bookService = (BookService)RpcProxy.create(BookService.class);
// 调用接口其方法,就会激活Proxy的invoke方法,也就打开了netty的client,发送数据
Book book = bookService.findBookById("1");
System.out.println(book);
}
}

调用结果

代码地址:点击跳转

希望对您有所帮助
May all the ordinary are great, all the ignoble bloom
  • smile