最近在学习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):
github.com/hanshuaikan…