spring boot 根据注解动态注入bean到spring容器中
简要
有的时候需要动态注入bean到spring容器中,@service,@component 满足不了,还可以在class上的根据注解来进行扩展,例如我想根据注解里的多个id来进行注入spring容器中,不用创建每个id来写@component,然后根据id中获取实例,还可以动态注入一些需要的属性,等等。
解决方案还是有的,而且还不止一种,这都得亏于spring的设计扩展性太强,根据不同时刻满足不同需求,我这边分别用2中方式BeanDefinitionRegistryPostProcessor和ImportBeanDefinitionRegistrar进行切入口
1.BeanDefinitionRegistryPostProcessor
结合@Import使用
spring官方就是用这种方式,实现@Component、@Service等注解的动态注入机制。定义一个ImportBeanDefinitionRegistrar的实现类,然后在有@Configuration注解的配置类上使用@Import导入。
类似mybatis @Mapper和@MapperScan结合使用,可以指定注解参数,并进行处理业务,比如需要扫描指定的package进行注入,以及实现开启注解功能等,扩展性强
2.BeanDefinitionRegistryPostProcessor
这个接口扩展自BeanFactoryPostProcessor,专门用于动态注册Bean。
其实在spring的生命周期中,bean在实例化之前都是无差别的被当做资源加载进来的,并被封装成一个个Beandefinition。在spring启动时,所有bean实例化的注解和xml文件都会被加载进来,并注册成Beandefinition。准备后续的bean实例化。那么在注册成 Beandefinition这步,spring其实提供给了我们不少后门进行操作。常见的后置处理器BeanDefinitionRegistryPostProcessor就是一个比较常见的自定义操作bean的接口。BeanDefinitionRegistryPostProcessor是BeanFactoryPostProcessor的子接口, BeanFactoryPostProcessor的作用是在bean的定义信息已经加载但还没有初始化的时候执行方法postProcessBeanFactory()方法,而BeanDefinitionRegistryPostProcessor是在BeanFactoryPostProcessor的前面执。
实现
- 编写自定义注解
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface CoreAnnotation { String[] value() default {}; }
注解的value是输入数组,比如输入多个id。
- 注解应用
public interface FinService { String say(String arg); }
@CoreAnnotation({"write"}) public class WriteService implements FinService { @Override public String say(String arg) { return "write"; } }
以上就随意写了个WriteService对象,用coreAnnotation进行注解并写了write值传入。接下来就应该要进行注入bean到spring容器中了
-
BeanDefinitionRegistryPostProcessor
先用实现这个最简单,只需要实现BeanDefinitionRegistryPostProcessor接口就行,会在启动后执行一次
先写了一个公共的注册方法,都可以用
/** * 注册 BeanDefinition */ private void registerCandidateComponents(BeanDefinitionRegistry registry, SetcandidateComponents) throws ClassNotFoundException { for (BeanDefinition candidateComponent : candidateComponents) { if (candidateComponent instanceof AnnotatedBeanDefinition) { AnnotatedBeanDefinition annotatedBeanDefinition = (AnnotatedBeanDefinition) candidateComponent; AnnotationMetadata annotationMetadata = annotatedBeanDefinition.getMetadata(); Map customImportAnnotationAttributesMap = annotationMetadata.getAnnotationAttributes(CoreAnnotation.class.getName()); AnnotationAttributes customImportAnnotationAttributes = Optional.ofNullable(AnnotationAttributes.fromMap(customImportAnnotationAttributesMap)).orElseGet(AnnotationAttributes::new);
//获取注解里的值 String[] values = customImportAnnotationAttributes.getStringArray("value"); String className = annotationMetadata.getClassName(); Class<?> clazzName = Class.forName(className); // AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(CustomImportFactoryBean.class) // .addPropertyValue("type", clazzName) // .addPropertyValue("beanName", beanName) // .setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE) // .setRole(BeanDefinition.ROLE_INFRASTRUCTURE) // .getBeanDefinition(); // registry.registerBeanDefinition(beanName, beanDefinition); Arrays.asList(values).forEach(m ->{ RootBeanDefinition mbean = null; try { mbean = new RootBeanDefinition(clazzName); } catch (Exception e) { e.printStackTrace(); } registry.registerBeanDefinition(m, mbean); }); } } }
@Component public class DefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor, ResourceLoaderAware, EnvironmentAware { private Environment environment; private ResourceLoader resourceLoader; @Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException { //用扫描器根据指定注解进行扫描获取BeanDefinition ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(beanDefinitionRegistry, false, environment, resourceLoader); scanner.addIncludeFilter(new AnnotationTypeFilter(CoreAnnotation.class)); SetcandidateComponents = scanner.findCandidateComponents("com"); registerCandidateComponents(beanDefinitionRegistry,candidateComponents); } @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException { } @Override public void setEnvironment(Environment environment) { this.environment = environment; } @Override public void setResourceLoader(ResourceLoader resourceLoader) { this.resourceLoader = resourceLoader; } }
ResourceLoaderAware, EnvironmentAware 这个内置接口,只要实现接口Spring会自动帮你注入,是不是很方便,根据内容来进行对ClassPathBeanDefinitionScanner的扫描获取指定注解,其中findCandidateComponents方法就是指定扫描包开始位置,
这里写死了com包下,具体的时候需要根据动态的去获取,也可以根据指定用户进行扫描,比如@MapperScan功能
如果不用ClassPathBeanDefinitionScanner,用反射表Reflections来进行查找注解的哪些对象也可以实现,如下
Reflections reflections = new Reflections("com"); Setextends FinService>> subTypes = reflections.getSubTypesOf(FinService.class); Set > annotated = reflections.getTypesAnnotatedWith(CoreAnnotation.class); annotated.forEach(x -> { x.getSimpleName(); CoreAnnotation coreAnnotation = x.getAnnotation(CoreAnnotation.class); String[] values = coreAnnotation.value(); if (null != values) { List strings = Arrays.asList(values); strings.forEach(m -> { RootBeanDefinition mbean = null; try { mbean = new RootBeanDefinition(x.newInstance().getClass()); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } beanDefinitionRegistry.registerBeanDefinition(m, mbean); }); } });
-
ImportBeanDefinitionRegistrar
这个要结合@Import来使用
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Import(MyImportBeanDefinitionRegistrar.class) public @interface EnableCustomImport { String[] packages() default {}; }
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware { private Environment environment; private ResourceLoader resourceLoader; @SneakyThrows @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { boolean enableCustomImport = importingClassMetadata.hasAnnotation(EnableCustomImport.class.getName()); //@Import不是在这个EnableCustomImport注解上的不执行 if (!enableCustomImport) { return; } MapannotationAttributesMap = importingClassMetadata.getAnnotationAttributes(EnableCustomImport.class.getName()); AnnotationAttributes annotationAttributes = Optional.ofNullable(AnnotationAttributes.fromMap(annotationAttributesMap)).orElseGet(AnnotationAttributes::new); // 获取需要扫描的包 String[] packages = retrievePackagesName(importingClassMetadata, annotationAttributes); // useDefaultFilters = false,即第二个参数 表示不扫描 @Component、@ManagedBean、@Named 注解标注的类 ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry, false, environment, resourceLoader); scanner.addIncludeFilter(new AnnotationTypeFilter(CoreAnnotation.class)); // 扫描包 for (String needScanPackage : packages) { Set candidateComponents = scanner.findCandidateComponents(needScanPackage); try { registerCandidateComponents(registry, candidateComponents); } catch (ClassNotFoundException e) { e.printStackTrace(); } } } /** * 获取需要扫描的包 */ private String[] retrievePackagesName(AnnotationMetadata annotationMetadata, AnnotationAttributes annotationAttributes) { String[] packages = annotationAttributes.getStringArray("packages"); if (packages.length > 0) { return packages; } //如果不存在,则默认第一个包开始 String className = annotationMetadata.getClassName(); return new String[]{className.split("\\.")[0]}; } @Override public void setEnvironment(Environment environment) { this.environment = environment; } @Override public void setResourceLoader(ResourceLoader resourceLoader) { this.resourceLoader = resourceLoader; } }
在启动项中加入@EnableCustomImport注解配置
@SpringBootApplication @EnableCustomImport public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }