• 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);
        }
    }

     

  • 相关阅读:
    搭建第一个web项目:Struts+hibernate+spring配置(annotation)
    Visual Studio
    Javascript的性能瓶颈
    导出数据库文档的最简单的方式
    long类型在C#和C++中的异同
    GDI+创建Graphics对象的2种方式
    jQuery中click()与trigger方法的区别
    使用VS调试64位应用程序
    ASP.NET中多个相同name的控件在后台正确取值
    js中的eval方法转换对象时,为何一定要加上括号?
  • 原文地址:https://www.cnblogs.com/zjtao/p/14938888.html
Copyright © 2020-2023  润新知