从0到1实现rpc之重载方法和参数类型转换

一、存在的问题

1.重载方法在当前的实现中还不支持,调用了会报错。

图片

图片

2.类型转换也还存在问题。

假设定义的接口如下,参数是float类型。

图片

图片

在Provider端接受到的是一个Double类型,这是因为web应用接收的请求后处理的类型。

图片

在反射调用的时候就会报错。

图片

二、解决方法重载问题

在Provider端创建的时候使用完整的方法签名替换方法全限定名。

图片

方法签名:方法名称+参数个数+参数类型

图片

在Consumer端封装请求参数时,传入方法签名即可。

图片

举个例子:

图片

三、解决参数类型转换

在Provider端进行反射之前,处理请求参数。

图片

processArgs()方法负责处理请求每个请求参数,传入的参数和方法参数类型匹配处理。

图片

同理,在Consumer端需要对返回的结果类型进行参数处理。

图片

参数处理工具类是TypeUtils.cast():

1.兼容的父子类型不需要处理;

2.处理数组类型:是一个什么样的数组,对每个值进行处理;

3.处理Map:使用json序列化;

4.基本类型使用封装类型;

图片

四、各种类型的测试:

1.引用类型参数;

2.int类型参数;

3.重载方法;

4.无参方法;

5.int类型参数,String类型返回值;

6.本地方法;

7.int类型参数,int类型返回值;

8.无参方法,String类型返回值;

9.数组类型返回值;

10.数组参数,数组返回值

图片

工程地址:https://github.com/midnight2104/midnight-rpc/tree/lesson3

从0到1实现rpc之rpcconsumer的远程调用

一、RPC的简化版原理如下图(核心是代理机制)。

图片

1.本地代理存根: Stub

2.本地序列化反序列化

3.网络通信

4.远程序列化反序列化

5.远程服务存根: Skeleton

6.调用实际业务服务

7.原路返回服务结果

8.返回给本地调用方

二、新建一个模块rpc-demo-consumer

图片

主要的依赖是rpc-demo-api,即业务服务定义的api接口,rpc-core是核心依赖包。

图片

三、启动方法

在启动方法中注入业务服务接口UserService和OrderService,使用的注解是@RpcConsumer,本文主要的功能就是如何实现这个注解。

在启动的时候会导入ConsumerConfig的配置类。

使用的ApplicationRunner来测试方法调用。

图片

四、ConsumerConfig用来启动消费者服务。

图片

五、ConsumerBootstrap实现了ApplicationContextAware接口,注入ApplicationContext,即Spring的应用上下文,获取bean。

图片

在start()方法中,

1.获取所有的bean;

2.遍历每个bean,查找bean中是否有字段用了@RpcConsumer注解;

3.对被标记了的@RpcConsumer的字段进行遍历;

4.获取到该字段的类型,全限定名;

5.使用动态代理的方式创建消费者对象;

6.把代理对象赋值给空字段。

图片

查找bean中的字段是否使用了@RpcConsumer注解,通过循环遍历的方式进行判断。不断向父类寻找,是因为bean本身可能被Spring进行了CGLIB增强,形成了一个代理类。

图片

使用Proxy创建代理对象。

图片

六、代理对象RpcInvocationHandler中定义了远程调用方法的逻辑。

图片

在invoke()方法中定义具体实现:1.判断是否本地方法,就不需要走远程调用;

2.封装请求参数;

3.发起远程调用;

4.处理调用结果。

图片

本地方法判断:即Object类中方法不需要被代理,不需要发起远程调用请求。

图片

RpcRequest封装了远程调用消费者端请求的数据结构:接口全限定名称、方法名称、方法参数。

图片

通过post方法发起远程调用,使用的是OkHttpClient。

图片

RpcResponse定义了消费者端的响应体数据结构:状态、响应结果数据、异常对象。

图片

处理调用结果:如果成功,就对调用结果数据进行反序列化;如果失败就封装异常信息。

图片

七、对各种情况的基本测试:

1.类对象参数的远程调用;2.本地方法的远程调用;3.基本参数类型的远程调用;4. String类型参数的远程调用;5. 另一个业务范围(订单服务)的远程调用;6. 嵌套注入的远程调用;7. 异常测试

图片

还有哪些问题?

  1. 默认使用的Java的动态代理,还有Spring的切面代理,CGLI增强,ByteBuddy的字节码
  2. 当前还是localhost请求,后续应该是走网络请求。
  3. 基本类型参数并不完整,如果是Double类型参数可能会报错。

工程地址:https://github.com/midnight2104/midnight-rpc

从0到1实现rpc之rpcprovider本地实现

RPC的简化版原理如下图(核心是代理机制)。

图片

1.本地代理存根: Stub

2.本地序列化反序列化

3.网络通信

4.远程序列化反序列化

5.远程服务存根: Skeleton

6.调用实际业务服务

7.原路返回服务结果

8.返回给本地调用方

注意处理异常。

RpcProvider的本地实现

1、工程结构

rpc-core是核心实现类

rpc-demo-api是定义实际业务服务的接口

rpc-demo-provider是业务接口实现类

图片

2、RpcProvider在启动过程中把@RpcProvider标记的方法存到Map中去

在启动时加载配置

图片

在配置中创建启动类

图片

启动类在创建的过程,会将带有@RpcProvider注解的类存放到Map中。

@PostConstruct注解可以在bean的初始化后进行执行,刚好满足我们的常见(存储bean)

使用 ApplicationContextAware接口是为了获取Spring的应用上下文ApplicationContext,从里面获取bean。

图片

RpcProvider是自定义的一个注解,用来表示这个类是一个服务提供者。

图片

一个服务提供者,订单服务实现类。

图片

3、在调用的时候通过方法名称找到应该调用的方法,通过反射完成调用。

发起一个http请求调用,获取订单信息。

图片

RpcRequest封装请求参数,包括:接口名称、方法名、参数。

图片

一个请求入口

图片

请求调用,根据服务全限定名(就是一个类的全名)去map存找对应的类,找到后,通过反射发起方法调用。

图片

响应类RpcResponse统一封装结果,返回状态,响应结果数据。

图片

实际的调用结果,就成功了。

图片

4、还有哪些问题?

如果一个接口有多个实现类怎么办?

错误消息怎么处理?

方法有多个重载方法怎么办?

工程地址:https://github.com/midnight2104/midnight-rpc