springboot自定义注解,项目启动时扫描注解类并注入容器
Posted wen-pan
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了springboot自定义注解,项目启动时扫描注解类并注入容器相关的知识,希望对你有一定的参考价值。
以下是核心流程的实现示例,如果需要更完整的实现,可参考:
注意:需要切换到simple-rpc-like-feign分支
- https://gitee.com/mr_wenpan/basis-simple-rpc/blob/master/simple-rpc-starter/src/main/java/org/simple/rpc/starter/registrar/SimpleRpcClientsRegistrar.java
- https://gitee.com/mr_wenpan/basis-simple-rpc/blob/master/simple-rpc-starter/src/main/java/org/simple/rpc/starter/registrar/ExampleRegistrar.java
一、需求说明
-
有两个自定义注解
@EnableSimpleRpcClients
和@SimpleRpcClient
-
@EnableSimpleRpcClients
注解标注在启动类上,并且可以指定要扫描的包(basePackages)@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented public @interface EnableSimpleRpcClients String[] value() default ; String[] basePackages() default ;
-
@SimpleRpcClient
注解标注在需要注入到容器的类或接口上@Documented @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface SimpleRpcClient @AliasFor("name") String value() default ""; @AliasFor("value") String name() default "";
-
-
要求项目启动时自动扫描
@EnableSimpleRpcClients
注解的basePackages属性指定的包路径下的所有标注了@SimpleRpcClient
注解的类或接口,并将这些类或接口都注入到容器中
二、实现过程分析
主要是借助spring留给我们的扩展点ImportBeanDefinitionRegistrar
以及一些工具类BeanDefinitionReaderUtils
- 利用
ImportBeanDefinitionRegistrar
我们就可以在容器启动前期,扫描指定basePackages
下所有标注了@SimpleRpcClient
注解的类或接口信息 - 然后借助spring提供的工具类
BeanDefinitionReaderUtils
,将扫描到的类或接口按自定义逻辑注入容器即可。(比如我这里是扫描到basePackages包下,所有标注了@SimpleRpcClient
注解的接口,然后借助factoryBean为这些接口创建【动态代理类】,然后将动态代理类注入容器中)
三、代码实现
①、先定义两个自定义注解
EnableSimpleRpcClients
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
public @interface EnableSimpleRpcClients
String[] value() default ;
String[] basePackages() default ;
SimpleRpcClient
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface SimpleRpcClient
@AliasFor("name")
String value() default "";
@AliasFor("value")
String name() default "";
②、自定义ImportBeanDefinitionRegistrar
@Component
public class ExampleRegistrar implements ImportBeanDefinitionRegistrar,
ResourceLoaderAware, EnvironmentAware
/**
* 资源加载器
*/
private ResourceLoader resourceLoader;
/**
* 环境
*/
private Environment environment;
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry)
// 创建scanner
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(resourceLoader);
// 设置扫描器scanner扫描的过滤条件
AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(SimpleRpcClient.class);
scanner.addIncludeFilter(annotationTypeFilter);
// 获取指定要扫描的basePackages
Set<String> basePackages = getBasePackages(metadata);
// 遍历每一个basePackages
for (String basePackage : basePackages)
// 通过scanner获取basePackage下的候选类(有标@SimpleRpcClient注解的类)
Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(basePackage);
// 遍历每一个候选类,如果符合条件就把他们注册到容器
for (BeanDefinition candidateComponent : candidateComponents)
if (candidateComponent instanceof AnnotatedBeanDefinition)
// verify annotated class is an interface
AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
Assert.isTrue(annotationMetadata.isInterface(), "@SimpleRpcClient can only be specified on an interface");
// 获取@SimpleRpcClient注解的属性
Map<String, Object> attributes = annotationMetadata.getAnnotationAttributes(SimpleRpcClient.class.getCanonicalName());
// 注册到容器
registerSimpleRpcClient(registry, annotationMetadata, attributes);
/**
* 利用factoryBean创建代理对象,并注册到容器
*/
private static void registerSimpleRpcClient(BeanDefinitionRegistry registry,
AnnotationMetadata annotationMetadata,
Map<String, Object> attributes)
// 类名(接口全限定名)
String className = annotationMetadata.getClassName();
// 创建SimpleRpcClientFactoryBean的BeanDefinition
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(SimpleRpcClientFactoryBean.class);
// 解析出@SimpleRpcClient注解的name
String name = getName(attributes);
if (!StringUtils.hasText(name))
throw new ProviderNameNullException(String.format("class [%s] , @SimpleRpcClient name or value can not be null, please check.", className));
// 给factoryBean添加属性值
definition.addPropertyValue("name", name);
definition.addPropertyValue("type", className);
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
String alias = name + "SimpleRpcClient";
AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
// 注册bean定义信息到容器
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, new String[]alias);
// 使用BeanDefinitionReaderUtils工具类将BeanDefinition注册到容器
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
/**
* 创建扫描器
*/
protected ClassPathScanningCandidateComponentProvider getScanner()
return new ClassPathScanningCandidateComponentProvider(false, environment)
@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition)
boolean isCandidate = false;
if (beanDefinition.getMetadata().isIndependent())
if (!beanDefinition.getMetadata().isAnnotation())
isCandidate = true;
return isCandidate;
;
/**
* 获取base packages
*/
protected static Set<String> getBasePackages(AnnotationMetadata importingClassMetadata)
// 获取到@EnableSimpleRpcClients注解所有属性
Map<String, Object> attributes = importingClassMetadata.getAnnotationAttributes(EnableSimpleRpcClients.class.getCanonicalName());
Set<String> basePackages = new HashSet<>();
assert attributes != null;
// value 属性是否有配置值,如果有则添加
for (String pkg : (String[]) attributes.get("value"))
if (StringUtils.hasText(pkg))
basePackages.add(pkg);
// basePackages 属性是否有配置值,如果有则添加
for (String pkg : (String[]) attributes.get("basePackages"))
if (StringUtils.hasText(pkg))
basePackages.add(pkg);
// 如果上面两步都没有获取到basePackages,那么这里就默认使用当前项目启动类所在的包为basePackages
if (basePackages.isEmpty())
basePackages.add(ClassUtils.getPackageName(importingClassMetadata.getClassName()));
return basePackages;
/**
* 获取name
*/
protected static String getName(Map<String, Object> attributes)
String name = (String) attributes.get("name");
if (!StringUtils.hasText(name))
name = (String) attributes.get("value");
return name;
@Override
public void setEnvironment(Environment environment)
this.environment = environment;
@Override
public void setResourceLoader(ResourceLoader resourceLoader)
this.resourceLoader = resourceLoader;
③、具体使用
// 启动类上标注@EnableSimpleRpcClients注解,如果不指定value和basePackages那么默认扫描classpath下
@EnableSimpleRpcClients
@SpringBootApplication
public class SimpleRpcConsumerApplication
public static void main(String[] args)
SpringApplication.run(SimpleRpcConsumerApplication.class, args);
以上是关于springboot自定义注解,项目启动时扫描注解类并注入容器的主要内容,如果未能解决你的问题,请参考以下文章
JAVA——通过自定义注解实现每次程序启动时,自动扫描被注解的方法,获取其路径及访问该路径所需的权限并写入数据库
JAVA——通过自定义注解实现每次程序启动时,自动扫描被注解的方法,获取其路径及访问该路径所需的权限并写入数据库