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的前面执。

实现

  1. 编写自定义注解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CoreAnnotation {
    String[] value() default {};
}

注解的value是输入数组,比如输入多个id。

  1. 注解应用
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, Set<BeanDefinition> candidateComponents) throws ClassNotFoundException {
        for (BeanDefinition candidateComponent : candidateComponents) {
            if (candidateComponent instanceof AnnotatedBeanDefinition) {
                AnnotatedBeanDefinition annotatedBeanDefinition = (AnnotatedBeanDefinition) candidateComponent;
                AnnotationMetadata annotationMetadata = annotatedBeanDefinition.getMetadata();
                Map<String, Object> 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));
        Set<BeanDefinition> candidateComponents = 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");
        Set<Class<? extends FinService>> subTypes = reflections.getSubTypesOf(FinService.class);
        Set<Class<?>> annotated = reflections.getTypesAnnotatedWith(CoreAnnotation.class);
        annotated.forEach(x -> {
            x.getSimpleName();
            CoreAnnotation coreAnnotation = x.getAnnotation(CoreAnnotation.class);
            String[] values = coreAnnotation.value();
            if (null != values) {
                List<String> 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;
        }
        Map<String, Object> annotationAttributesMap = 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<BeanDefinition> 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);
    }
}

 

 

posted @ 2021-06-26 19:54  归去如风  阅读(4783)  评论(0编辑  收藏  举报