简易RPC框架 - 服务注册模块

1、结构设计

紧接上文,结合框架结构图进行讲解:

xxx

本节主要讲解服务注册的细节,首先定义了三个注解:@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 {
/**
* Service version, default value is empty string
*/
String version() default "";

/**
* Service group, default value is empty string
*/
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 {
/**
* Service version, default value is empty string
*/
String version() default "";

/**
* Service group, default value is empty string
*/
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、实现

首先服务端执行的大致流程如下:

xxxx

服务注册模块采用注解加包扫描的方式去实现,将标注了@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());
//build RpcServiceProperties
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);
}
//启动服务端,监听配置文件中定义的IP:PORT
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();
//将服务名称和服务对象放入map中
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);
//添加注解过滤器,只扫描标注了特定注解的Bean对象
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 {
//扫描框架项目的bean
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()));
//获取注解属性中的basePackage
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()};
}
//扫描 RpcService 注解的扫描器
final CustomScanner rpcServiceScanner = new CustomScanner(registry, RpcService.class);
//扫描 Component 注解的扫描器
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);
//扫描 RpcService 注解
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 {};
}