简易RPC框架 - 服务注册模块
1、结构设计
紧接上文,结合框架结构图进行讲解:

本节主要讲解服务注册的细节,首先定义了三个注解:@RpcService、@RpcReference、@RpcScan
@RpcService用于标注服务提供者,@RpcReference用于标注服务消费者,@RpcScan用于扫描特定的Bean
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 
 | @Documented@Retention(RetentionPolicy.RUNTIME)
 @Target(ElementType.TYPE)
 @Inherited
 public @interface RpcService {
 
 
 
 String version() default "";
 
 
 
 
 String group() default "";
 }
 
 | 
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 
 | @Documented@Retention(RetentionPolicy.RUNTIME)
 @Target(ElementType.FIELD)
 @Inherited
 public @interface RpcReference {
 
 
 
 String version() default "";
 
 
 
 
 String group() default "";
 }
 
 | 
| 12
 3
 4
 5
 6
 7
 
 | @Target({ElementType.TYPE, ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)
 @Import(CustomScannerRegistrar.class)
 @Documented
 public @interface RpcScan {
 String[] basePackage() default {};
 }
 
 | 
2、实现
首先服务端执行的大致流程如下:

服务注册模块采用注解加包扫描的方式去实现,将标注了@RpcService服务的元信息注册到Zookeeper,然后客户端要向服务端发送消息时,就可以从Zookeeper中获取远程服务的信息完成服务的调用。
| 12
 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
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 
 | public class RpcServerAutoConfiguration implements InitializingBean, ApplicationContextAware {private ApplicationContext applicationContext;
 
 @Override
 public void afterPropertiesSet() {
 Map<String, Object> beanMap = applicationContext.getBeansWithAnnotation(RpcService.class);
 if(beanMap.size() == 0){
 
 return;
 }
 NettyRpcServer nettyRpcServer = new NettyRpcServer();
 nettyRpcServer.initServerConfig();
 for (String beanName : beanMap.keySet()) {
 log.info("[{}] is annotated with [{}]", beanName, RpcService.class.getCanonicalName());
 
 Object bean = beanMap.get(beanName);
 RpcService rpcService = bean.getClass().getAnnotation(RpcService.class);
 RpcServiceConfig rpcServiceConfig = null;
 try {
 
 rpcServiceConfig = RpcServiceConfig.builder()
 .host(InetAddress.getLocalHost().getHostAddress())
 .group(rpcService.group())
 .version(rpcService.version())
 .service(bean)
 .port(SERVER_CONFIG.getServerPort()).build();
 } catch (UnknownHostException e) {
 e.printStackTrace();
 }
 
 nettyRpcServer.exposeService(rpcServiceConfig);
 }
 
 nettyRpcServer.start();
 }
 
 @Override
 public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
 this.applicationContext = applicationContext;
 }
 }
 
 | 
其中服务注册到Zookeeper的代码我就不放上来了,大家可以根据自己的需要自己定义规则。需要注意的是,在注册时我们还定义了一个服务端缓存类,用于存储服务端提供的类信息:
| 12
 3
 4
 5
 
 | public class CommonServerCache {public static ServerConfig SERVER_CONFIG;
 
 public static Map<String, Object> SERVICE_MAP = new HashMap<>();
 }
 
 | 
我们注册时需要把暴露的服务缓存到本地,后面才能为客户端提供服务类
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 
 | public void addService(RpcServiceConfig rpcServiceConfig) {String rpcServiceName = rpcServiceConfig.getRpcServiceName();
 Object service = rpcServiceConfig.getService();
 
 if(SERVICE_MAP.containsKey(rpcServiceName)){
 return;
 }
 SERVICE_MAP.put(rpcServiceName, service);
 log.info("服务注册 - 成功注册服务: {} 和接口: {}", rpcServiceName, service.getClass().getInterfaces());
 }
 
 | 
至于客户端调用(服务订阅、服务发现)的细节我们会在后续的文章中进行讲解。
再来是包扫描的实现方式:
这里我们Bean注入方式是基于 ImportBeanDefinitionRegistrar 来实现的,这种方式是最灵活的,能在 registerBeanDefinitions 方法中获取到 BeanDefinitionRegistry 容器注册对象,可以手动控制 BeanDefinition 的创建和注册。
在此还要再引入一个类,ResourceLoader ,官方对 ResourceLoader的解释是:
Spring ResourceLoader为我们提供了一个统一的getResource()方法来通过资源路径检索外部资源。从而将资源或文件(例如文本文件、XML文件、属性文件或图像文件)加载到Spring应用程序上下文中的不同实现
个人能力有限,我个人浅薄的理解大概是用于加载资源配置,有其他不同解释的欢迎在评论区留言。
因为要进行包扫描,所以我们就需要一个扫描器,这里我们定义的是 CustomScanner ,它实现了 ClassPathBeanDefinitionScanner 接口,可以实现将特定路径下的Bean对象注册到容器中。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 
 | public class CustomScanner extends ClassPathBeanDefinitionScanner {
 public CustomScanner(BeanDefinitionRegistry registry, Class<? extends Annotation> annoType) {
 super(registry);
 
 super.addIncludeFilter(new AnnotationTypeFilter(annoType));
 }
 
 @Override
 public int scan(String... basePackages) {
 return super.scan(basePackages);
 }
 }
 
 | 
在 registerBeanDefinitions() 方法中,我们先获取到 @RpcScan 的  AnnotationAttributes 注解属性,然后读取注解的 basePackage 的值,然后创建扫描器对象,把 BeanDefinitionRegistry 和注解类型传入扫描器中,然后就能通过执行扫描器里对应的 scan(basePackage) 方法进行扫描。
| 12
 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
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 
 | @Slf4jpublic class CustomScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
 
 private static final String SPRING_BEAN_BASE_PACKAGE = "com.hurried1y";
 private static final String BASE_PACKAGE_ATTRIBUTE_NAME = "basePackage";
 private ResourceLoader resourceLoader;
 
 @Override
 public void setResourceLoader(ResourceLoader resourceLoader) {
 this.resourceLoader = resourceLoader;
 }
 
 @Override
 public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {
 
 final AnnotationAttributes annotationAttributes = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(RpcScan.class.getName()));
 
 String[] rpcScanBasePackages = new String[0];
 if(annotationAttributes != null){
 rpcScanBasePackages = annotationAttributes.getStringArray(BASE_PACKAGE_ATTRIBUTE_NAME);
 }
 
 if(rpcScanBasePackages.length == 0){
 rpcScanBasePackages = new String[]{((StandardAnnotationMetadata) importingClassMetadata).getIntrospectedClass().getPackage().getName()};
 }
 
 final CustomScanner rpcServiceScanner = new CustomScanner(registry, RpcService.class);
 
 final CustomScanner componentScanner = new CustomScanner(registry, Component.class);
 
 if(resourceLoader != null){
 rpcServiceScanner.setResourceLoader(resourceLoader);
 componentScanner.setResourceLoader(resourceLoader);
 }
 
 int springBeanAmount = componentScanner.scan(SPRING_BEAN_BASE_PACKAGE);
 log.info("springBeanScanner扫描的数量 [{}]", springBeanAmount);
 
 int rpcServiceCount = rpcServiceScanner.scan(rpcScanBasePackages);
 log.info("rpcServiceScanner扫描的数量 [{}]", rpcServiceCount);
 }
 }
 
 | 
当然我们有了实现了 ImportBeanDefinitionRegistrar 的类还不行,我们还得把它注册到容器中,这一步就在 @RpcScan 注解里通过 @Import 实现了:
| 12
 3
 4
 5
 6
 7
 
 | @Target({ElementType.TYPE, ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)
 @Import(CustomScannerRegistrar.class)
 @Documented
 public @interface RpcScan {
 String[] basePackage() default {};
 }
 
 |