基于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等等省略

public class Book implements Serializable {
    private String id;
    private String name;
}

3.1.2 数据传输对象

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

public class ClassInfo implements Serializable {
    /**
     * 调用的具体类的类名全路径(即包名加类名 service.BookService)
     */
    private String fullPath;
    /**
     * 类中的那个方法
     */
    private String methodName;
    /**
     * 方法中的参数类型
     */
    private Class []paramType;
    /**
     * 方法中的参数值
     */
    private Object []paramValue;

3.1.4接口

public interface BookService {
    /**
     * 根据Id查找指定的图书
     * @param bookId
     * @return
     */
    Book findBookById(String bookId);
}

3.2 provider(server)项目的实现

该项目中包括

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

3.2.1 BookService的具体实现

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包实现,根据结果类型,找到其下所有实现类

/**
     * 得到某接口下某个实现类的全路径
     * @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();
        }
    }

测试如下

   /**
    * 测试上面结果
    */
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);
}

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

/**
     * 通过反射调用其方法并返回
     * @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得到通道,后发送数据

ChannelFuture future = b.connect("127.0.0.1", 9999).sync();
                    // 将需要调用的方法数据发到服务端
                    future.channel().writeAndFlush(classInfo).sync();

3.3.2数据的接收

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

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 数据的发送的代理

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

四 代码的测试

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);
    }
}

调用结果

代码地址:点击跳转

原文链接:加载失败,请重新获取