简易RPC框架 - 服务注册模块
1、结构设计
紧接上文,结合框架结构图进行讲解:
本节主要讲解服务注册的细节,首先定义了三个注解:@RpcService、@RpcReference、@RpcScan
@RpcService用于标注服务提供者,@RpcReference用于标注服务消费者,@RpcScan用于扫描特定的Bean
1 2 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 ""; }
|
1 2 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 ""; }
|
1 2 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中获取远程服务的信息完成服务的调用。
1 2 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的代码我就不放上来了,大家可以根据自己的需要自己定义规则。需要注意的是,在注册时我们还定义了一个服务端缓存类,用于存储服务端提供的类信息:
1 2 3 4 5
| public class CommonServerCache { public static ServerConfig SERVER_CONFIG; public static Map<String, Object> SERVICE_MAP = new HashMap<>(); }
|
我们注册时需要把暴露的服务缓存到本地,后面才能为客户端提供服务类
1 2 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对象注册到容器中。
1 2 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) 方法进行扫描。
1 2 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
| @Slf4j public 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 实现了:
1 2 3 4 5 6 7
| @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Import(CustomScannerRegistrar.class) @Documented public @interface RpcScan { String[] basePackage() default {}; }
|