最近在学习Netty的过程中,跟着前辈们的思路用Netty作为底层通信开发了一个非常牛逼,宇宙第一(实际超级垃圾)的Netty Rpc Demo。为啥不叫框架叫Demo呢,一个好的框架是需要非常长时间的开发和优化的,离不开大佬们的全情投入,我这种级别的菜鸟,充其量叫demo。好,废话不多说,原本的思路呢,是需要手动配置一个接口与实现类的映射map,类似于下面这样:
@Bean("handlerMap")
public Map<String, Object> handlerMap(){
Map<String, Object> handlerMap = new ConcurrentHashMap<String, Object>();
handlerMap.put("com.jdkcb.mystarter.service.PersonService",new PersonServiceImpl());
return handlerMap;
}
大佬们勿喷,当我自信满满的把代码交给前辈们看的时候,前辈非常耐心(不留情面)地指出了我这样做的问题。的确,在接口类数量非常多的时候,光配置Map就是一件非常麻烦的事情了,于是我回去冥思苦想,睡的特香,七七四十九分钟之后,脑袋里灵光乍现,想到了前天写Mybatis配置多数据源时候见到的一个注解,@MapperScan
ohohohohohohoh,这就是我独享的moment,就决定是你啦。
接下来,我们将模仿Mybatis的实现,来做一个注解扫描器,将我们自定义的注解扫描并注册成为Spring管理的Bean。
战士上战场,直接开始干。(好吧,这其实是一个梗,我猜很多人都get不到)
既然是要扫描我们自定义的注解,那首先我们得有个自定义的注解才行。来,小二,上自定义的注解,结合我个人的需求,我将它自定义为NRpcServer ,代码如下:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface NRpcServer {
//服务的名称,用来RPC的时候调用指定名称的 服务
String name() default "";
String value() default "";
}
可是,我有个问题,你是怎么知道加@Target({ElementType.TYPE, ElementType.METHOD})这几个注解的呢?
hhh,看来还是被你发现了,不会写,我还不会抄吗,既然是模仿@MapperScan()这个注解,那直接点开@Mapper注解,看看它加了什么注解不就行了吗,嘿嘿
@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER})
public @interface Mapper {
}
Nice,去掉我们不需要的注解,用不到的属性,改个名字就是我们自己的注解了。(滑稽)
那@Mapper是怎么样被扫描到的呢,通过@MapperScan这个注解,我想,在这里我们估计可以达成一致了,抄一个,篇幅有限,后面我就不贴Mybatis的代码了,需要了解的朋友可以打开mybatis的源码查看。
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
//spring中的注解,加载对应的类
@Import(NRpcScannerRegistrar.class)//这个是我们的关键,实际上也是由这个类来扫描的
@Documented
public @interface NRpcScan {
String[] basePackage() default {};
}
当然,这个注解本身是没什么东西的,最核心的地方在哪呢?答案是NRpcScannerRegistrar这个类上,实际上我们注解的扫描过滤主要是交给这个类来实现的。
新建一个代码 NRpcScannerRegistrar 类并继承ImportBeanDefinitionRegistrar, ResourceLoaderAware 这两个接口。
其中: ResourceLoaderAware是一个标记接口,用于通过ApplicationContext上下文注入ResourceLoader
代码如下:
public class NRpcScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware{
ResourceLoader resourceLoader;
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
@Override
public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
//获取所有注解的属性和值
AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(annotationMetadata.getAnnotationAttributes(NRpcScan.class.getName()));
//获取到basePackage的值
String[] basePackages = annoAttrs.getStringArray("basePackage");
//如果没有设置basePackage 扫描路径,就扫描对应包下面的值
if(basePackages.length == 0){
basePackages = new String[]{((StandardAnnotationMetadata) annotationMetadata).getIntrospectedClass().getPackage().getName()};
}
//自定义的包扫描器
FindNRpcServiceClassPathScanHandle scanHandle = new FindNRpcServiceClassPathScanHandle(beanDefinitionRegistry,false);
if(resourceLoader != null){
scanHandle.setResourceLoader(resourceLoader);
}
//这里实现的是根据名称来注入
scanHandle.setBeanNameGenerator(new RpcBeanNameGenerator());
//扫描指定路径下的接口
Set<BeanDefinitionHolder> beanDefinitionHolders = scanHandle.doScan(basePackages);
}
}
这里涉及到一个 FindNRpcServiceClassPathScanHandle类,是我们自定义的包扫描器,我们可以在这个扫描器中添加我们的过滤条件。
public class FindNRpcServiceClassPathScanHandle extends ClassPathBeanDefinitionScanner {
public FindNRpcServiceClassPathScanHandle(BeanDefinitionRegistry registry, boolean useDefaultFilters) {
super(registry, useDefaultFilters);
}
@Override
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
//添加过滤条件,这里是只添加了@NRpcServer的注解才会被扫描到
addIncludeFilter(new AnnotationTypeFilter(NRpcServer.class));
//调用spring的扫描
Set<BeanDefinitionHolder> beanDefinitionHolders = super.doScan(basePackages);
return beanDefinitionHolders;
}
}
看到这里很多读者几乎都会发现,我其实并没有做什么东西,所有核心的骚操作都是由spring提供的实现来完成的,其实是的,最核心的代码其实就在
super.doScan(basePackages);
这一句,我们所做的,其实就是根据Spring的扫描结果做一下二次的处理,在我的demo中,之前提到的map其实就是在这一步自动生成的,由于这次主要讲包扫描器的实现,所以就把那部分逻辑给去掉了。
同时呢,上面代码中的RpcBeanNameGenerator这个类则是实现了根据我们的名称来注入指定bean,这里其实做的就是获取到注解里面属性所设置的值。代码如下:
public class RpcBeanNameGenerator extends AnnotationBeanNameGenerator {
@Override
public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
//从自定义注解中拿name
String name = getNameByServiceFindAnntation(definition,registry);
if(name != null && !"".equals(name)){
return name;
}
//走父类的方法
return super.generateBeanName(definition, registry);
}
private String getNameByServiceFindAnntation(BeanDefinition definition, BeanDefinitionRegistry registry) {
String beanClassName = definition.getBeanClassName();
try {
Class<?> aClass = Class.forName(beanClassName);
NRpcServer annotation = aClass.getAnnotation(NRpcServer.class);
if(annotation == null){
return null;
}
//获取到注解name的值并返回
return annotation.name();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
}
到这里,几乎就已经大功告成了。
首先我们准备一个PersonService类,并打上我们的@NRpcServer注解,代码如下:
@NRpcServer(name="PersonService")
public class PersonService {
public String getName(){
return "helloword";
}
}
然后在Springboot启动类上添加@NRpcScan注解,并指定我们需要扫描的包
@NRpcScan(basePackage = {"com.jdkcb.mybatisstuday.service"})
新建一个Controller,用于测试,代码如下:
@RestController
public class TestController {
@Autowired
@Qualifier("PersonService")
private PersonService personService;
@RequestMapping("test")
public String getName(){
return personService.getName();
}
}
在浏览器中输入http://127.0.0.1:8080/test
输出结果:
helloword
到此,就算真正的大功告成啦。
最后的最后,大家好,我是韩数,哼,关注我,有你好果子吃(叉腰)。
记得点个赞再走哦~
等一下:
相关源码欢迎去我的github下载(欢迎star):