vlambda博客
学习文章列表

听说JavaGuide写了一个不错的rpc,特来围观!


前言:之前看到了JavaGuide 写的一个rpc项目,拜读了一下文档,有点小缺陷就是没有项目运行的文档,在项目下面有部分小伙伴留言说,“项目运行不起来”。自己试了一下 运行其实做过微服务的人多多少少都知道怎么弄,但部分没怎么接触微服务架构的人就不知道怎么弄,此文章一方面是作为JavaGuide的运行说明,主要的还是从底层研究下 这个rpc功能 具体是怎么实现的,为什么服务能够被注册 被发现并调用呢,当然同时你还巩固了一下spring基础的知识。

1. 关于项目

技术: 反射(如注解式注入)、jdk动态代理 、序列化(Kryo 序列化) 、 中间件(Zookeeper) Map缓存 、 Netty(通信技术)、消息编码器/解码器 、

机制: SPI机制

其他: Curator操作zookeeper

2. 项目运行

2.1. 使用 Edit Configurations 新建两个Application

分别命名为Server_Netty_Application 和 Client_Netty_Application。主类分别为example-server文件夹下的NettyServerMain类,以及example-client文件夹下的NettyClientMain(这样看起来比较直观而已)

听说JavaGuide写了一个不错的rpc,特来围观!听说JavaGuide写了一个不错的rpc,特来围观!

2.2. 其他说明:

先开启zookeeper ,再一次启动Server_Netty_Application 和 Client_Netty_Application。建议zookeeper 版本在3.5以上,貌似Curator对3.5以下版本的兼容性不是很好,之前我用的是3.4版本 直接报错。

3项目代码

3.1 服务端启动类代码:
@RpcScan(basePackage = {"github.javaguide"})
public class NettyServerMain {
public static void main(String[] args) {
// Register service via annotation
// 通过注释 注册服务
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(NettyServerMain.class);
NettyRpcServer nettyRpcServer = (NettyRpcServer) applicationContext.getBean("nettyRpcServer");
// 可以不用手动注册服务
// Register service manually
//手动注册服务
/* HelloService helloService2 = new HelloServiceImpl2();
//了解@lomlock注解的用法
RpcServiceProperties rpcServiceProperties = RpcServiceProperties.builder()
.group("test2").version("version2").build();
nettyRpcServer.registerService(helloService2, rpcServiceProperties);*/
nettyRpcServer.start();
}
}
3.2 客户端启动代码:
package github.javaguide;
import github.javaguide.annotation.RpcScan;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

/**
* @author shuang.kou
* @createTime 2020年05月10日 07:25:00
*/
@RpcScan(basePackage = {"github.javaguide"})
public class NettyClientMain {
public static void main(String[] args) throws InterruptedException {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(NettyClientMain.class);
HelloController helloController = (HelloController) applicationContext.getBean("helloController");
helloController.test();
}
}
3.3 代码分析:
3.3.1 分析相关的“SPi类”等 注解式 注入是怎么实现的 ,服务是如何注册和发现的

观察两端代码有两处公共的部分,一是类上面都有个@RpcScan的注解,二是都用到了 Spring的注解式 注入 的方式new AnnotationConfigApplicationContext(Class);

下面跟随AnnotationConfigApplicationContext的源码,找到服务是怎么被注册的?

这就在AnnotationConfigApplicationContext类里面了,如下图

听说JavaGuide写了一个不错的rpc,特来围观!

调用invokeBeanFactoryPostProcessors方法前的 beanFactory

听说JavaGuide写了一个不错的rpc,特来围观!

调用 invokeBeanFactoryPostProcessors方法,主要作用实例化并调用所有已注册的 BeanFactoryPostProcessor. 这方法里面会调用CustomScannerRegistrar的相关方法,进行注解式注入并生成BeanDefinition,并将其追加到beanFactory容器中,如果你不信,可以试着把registerBeanDefinitions方法的内容置空,看看调用完invokeBeanFactoryPostProcessors方法的中beanFactory中的BeanDefinition内容,下面是注解式生成了相关的BeanDefinition的截图:

听说JavaGuide写了一个不错的rpc,特来围观!

在刚开始的beanDefinition里面 只有一个 nettyServerMain 主类

ConfigurationClassParser类 doProcessConfigurationClass方法

听说JavaGuide写了一个不错的rpc,特来围观!

processImports 里面有一行代码

ImportBeanDefinitionRegistrar registrar = (ImportBeanDefinitionRegistrar)ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class, this.environment, this.resourceLoader, this.registry);

他需要实现的接口为 ImportBeanDefinitionRegistrar

听说JavaGuide写了一个不错的rpc,特来围观!

然后执行了 invokeAwareMethods方法:(意义是调用“Aware接口”的方法)

说明该类需要实现 父接口是Aware的接口

听说JavaGuide写了一个不错的rpc,特来围观!

ConfigurationClassBeanDefinitionReader类里面的方法:loadBeanDefinitionsForConfigurationClass

调用 registerBeanDefinitions方法

听说JavaGuide写了一个不错的rpc,特来围观!

从上面可以看到 首先会CustomScannerRegistrar 会依次调用setResourceLoader方法和 registerBeanDefinitions方法,setResourceLoader方法是获取到ResourceLoader,registerBeanDefinitions方法是注入了 使用了RpcService和Component的接口/类 统统被自动注入了

SpringBeanPostProcessor 实现BeanPostProcessor接口

SpringBeanPostProcessor方法里面 手动注入了这两个成员:

ServiceProvider serviceProvider; RpcRequestTransport rpcClient;

postProcessBeforeInitialization方法:

获取了version和group,服务提供者上线==》serviceProvider.publishService(bean, rpcServiceProperties),并把服务链 进行存入缓存(Map,可以是this.getServiceName() + this.getGroup() + this.getVersion())

如上图 this.registerBeanPostProcessors(beanFactory);调用这个方法是生成所有的BeanPostProcessor,如项目中实现BeanPostProcessor的类SpringBeanPostProcessor里面两个方法 postProcessBeforeInitialization 和 postProcessAfterInitialization 就会被先后调用。

SpringBeanPostProcessor的构造器后被调用,因为被注入了 这个方法 主要是创建单例的 服务提供者 和创建 RpcRequestTransport(作用是发送请求)

postProcessBeforeInitialization方法

在 ExtensionLoader的类方法里面 作者使用SPI的方式(不太理解这块的同学 可以网上搜一下),位置唯一resources 的META-INF下面 文件名为接口的全路径 里面为key和对应的接口实现,【key的不同 代表接口的实现不同而已】听说JavaGuide写了一个不错的rpc,特来围观!

使用了 SPI的接口分别为 RpcRequestTransport 、ServiceRegistry、ServiceProvider、LoadBalance 。

所有的“SPI类”经此都实例化了。

//SPI类实例化顺序 ZkServiceRegistry 》 NettyRpcClient 》ZkServiceDiscovery 》 ConsistentHashLoadBalance // 分别是 zookeeper注册器 NettyRpcClient zk服务发现 负载均衡器

听说JavaGuide写了一个不错的rpc,特来围观!

例如 RpcRequestTransport serviceDiscovery

postProcessAfterInitialization方法

postProcessAfterInitialization 这个方法在这里就是 取获取引用RpcReference的字段 ,并把通过JDK动态代理生成该对象,并把代理对象赋予给该字段。后面调用时,直接走项目中的RpcClientProxy类,因为该实现了InvocationHandler接口,最终也是 根据上面提到的postProcessBeforeInitialization方法 的serviceProvider 取出对应的接口的实现类, 这个根据需求 整个项目也只有充当“消费者”的 example-client中才使用了,这也更明确了这方是调用方/消费者了。

最后文章若有不足,请指正。