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

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM