前言: 之前看到了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(这样看起来比较直观而已)
file
file
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类里面了,如下图
file调用invokeBeanFactoryPostProcessors方法前的 beanFactory
file调用 invokeBeanFactoryPostProcessors方法,主要作用实例化并调用所有已注册的 BeanFactoryPostProcessor. 这方法里面会调用CustomScannerRegistrar的相关方法,进行注解式注入并生成BeanDefinition,并将其追加到beanFactory容器中,如果你不信,可以试着把registerBeanDefinitions方法的内容置空,看看调用完invokeBeanFactoryPostProcessors方法的中beanFactory中的BeanDefinition内容,下面是注解式生成了相关的BeanDefinition的截图:
file在刚开始的beanDefinition里面 只有一个 nettyServerMain 主类
ConfigurationClassParser类 doProcessConfigurationClass方法
fileprocessImports 里面有一行代码
ImportBeanDefinitionRegistrar registrar = (ImportBeanDefinitionRegistrar)ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class, this.environment, this.resourceLoader, this.registry);
他需要实现的接口为 ImportBeanDefinitionRegistrar
file然后执行了 invokeAwareMethods方法:(意义是调用“Aware接口”的方法)
说明该类需要实现 父接口是Aware的接口
fileConfigurationClassBeanDefinitionReader类里面的方法:loadBeanDefinitionsForConfigurationClass
调用 registerBeanDefinitions方法
file从上面可以看到 首先会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的不同 代表接口的实现不同而已】
file
使用了 SPI的接口分别为 RpcRequestTransport 、ServiceRegistry、ServiceProvider、LoadBalance 。
所有的“SPI类”经此都实例化了。
//SPI类实例化顺序 ZkServiceRegistry 》 NettyRpcClient 》ZkServiceDiscovery 》 ConsistentHashLoadBalance
// 分别是 zookeeper注册器 NettyRpcClient zk服务发现 负载均衡器
例如 RpcRequestTransport serviceDiscovery
postProcessBeforeInitialization 方法的作用 其实 就是生产者提供服务接口,满足带有注解RpcService的”生产者”,这里面就是保存了所有注册服务【服务链】,并把其注册到zookeeper上去(实际操作是在zookeeper上添加节点,父节点为my-rpc代表是rpc服务,二级节点是服务地址包含group和version ,三级节点是服务主机和端口号)。
postProcessAfterInitialization方法
postProcessAfterInitialization 这个方法在这里就是 取获取引用RpcReference的字段 ,并把通过JDK动态代理生成该对象,并把代理对象赋予给该字段。后面调用时,直接走项目中的RpcClientProxy类,因为该实现了InvocationHandler接口,最终也是 根据上面提到的postProcessBeforeInitialization方法 的serviceProvider 取出对应的接口的实现类, 这个根据需求 整个项目也只有充当“消费者”的 example-client中才使用了,这也更明确了这方是调用方/消费者了。
file最后文章若有不足,请指正。
欢迎关注我的公众号:程序员ken,程序之路,让我们一起探索,共同进步。
网友评论