zoukankan      html  css  js  c++  java
  • Spring之Import注解

    在spring框架下做开发时,@Import是常见的注解,可以用来动态创建bean,今天我们先从源码分析原理,再用实战来验证Import的作用;
    文章概览
    本章由以下几部分组成:
    1. 从Enable前缀的注解谈起,揭示常见的Enable注解与Import注解的关系;
    2. 常见的四种Import注解用法列举;
    3. 分析spring源码,揭示Import注解的工作原理;
    4. 官方API文档中的疑问解答;
    5. 实战通过Import注解动态创建bean实例;
    从Enable前缀的注解谈起
    有很多注解都以Enable为前缀,例如配置异步调用的注解EnableAsync,其源码如下 :

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Import(AsyncConfigurationSelector.class)
    public @interface EnableAsync {
        Class<? extends Annotation> annotation() default Annotation.class;
    
        AdviceMode mode() default AdviceMode.PROXY;
    
        int order() default Ordered.LOWEST_PRECEDENCE;
    }

    从以上代码可见,使异步调用生效的关键是@Import(AsyncConfigurationSelector.class),通过此注解spring容器会创建AsyncConfigurationSelector实例并调用其selectImports方法,完成异步调用相关的配置;再多看几个Enable前缀的注解的源码,例如EnableBatchProcessing、EnableCaching、EnableDiscoveryClient等,也都是通过Import来生效的,这种方式值得我们学习,在业务开发中也能用类似方式来对bean实例做控制;
    常见的四种Import注解用法列举
    在@Import注解的参数中可以填写类名,例如@Import(Abc.class),根据类Abc的不同类型,spring容器有以下四种处理方式:
    1. 如果Abc类实现了ImportSelector接口,spring容器就会实例化Abc类,并且调用其selectImports方法;
    2. DeferredImportSelector是ImportSelector的子类,如果Abc类实现了DeferredImportSelector接口,spring容器就会实例化Abc类,并且调用其selectImports方法,和ImportSelector的实例不同的是,DeferredImportSelector的实例的selectImports方法调用时机晚于ImportSelector的实例,要等到@Configuration注解中相关的业务全部都处理完了才会调用(具体逻辑在ConfigurationClassParser.processDeferredImportSelectors方法中)。
    3. 如果Abc类实现了ImportBeanDefinitionRegistrar接口,spring容器就会实例化Abc类,并且调用其registerBeanDefinitions方法;
    4. 如果Abc没有实现ImportSelector、DeferredImportSelector、ImportBeanDefinitionRegistrar等其中的任何一个,spring容器就会实例化Abc类,官方说明在这里;
    分析spring源码,揭示Import注解的工作原理
    接下来通过spring源码来了解spring容器是如何处理Import注解的;
    1. 先看spring容器的初始化代码,定位AbstractApplicationContext类的refresh方法,里面会调用invokeBeanFactoryPostProcessors方法,如下图红框所示:

    2. 展开invokeBeanFactoryPostProcessors方法,继续追踪到了PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors方法,具体操作如下图红框所示:

    3. 对于上图分析的调用invokeBeanDefinitionRegistryPostProcessors方法时作为入参传入的bean,ConfigurationClassPostProcessor类的实例是符合过滤要求的:既实现了BeanDefinitionRegistryPostProcessor接口,又实现了PriorityOrdered接口,因此,在invokeBeanDefinitionRegistryPostProcessors方法中,ConfigurationClassPostProcessor类的postProcessBeanDefinitionRegistry方法被调用:
    4. ConfigurationClassPostProcessor类的postProcessBeanDefinitionRegistry方法中,如下图红框所示,processConfigBeanDefinitions方法负责处理@Configuration注解相关的业务:

    5. processConfigBeanDefinitions方法代码如下,请注意中文注释:

     //被确认为配置类的bean定义都放在集合configCandidates中
            Set<BeanDefinitionHolder> configCandidates = new LinkedHashSet<BeanDefinitionHolder>();
            //取所有bean的名称
            String[] candidateNames = registry.getBeanDefinitionNames();
            //逐个检查每个bean
            for (String beanName : candidateNames) {
                //取得每个bean的定义对象
                BeanDefinition beanDef = registry.getBeanDefinition(beanName);
                if (ConfigurationClassUtils.isFullConfigurationClass(beanDef) ||
                        ConfigurationClassUtils.isLiteConfigurationClass(beanDef)) {
                    if (logger.isDebugEnabled()) {
                        logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
                    }
                }
                //注意:ConfigurationClassUtils.checkConfigurationClassCandidate方法非常值得一看,里面的通过当前类的注解来判断是否为配置类
                else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
                    //例如有@Configuration注解的类,被判定为配置类,放入集合configCandidates中
                    configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
                }
            }
    
            // 如果一个配置类都没找到,就直接返回了
            if (configCandidates.isEmpty()) {
                return;
            }
    
            // Detect any custom bean name generation strategy supplied through the enclosing application context
            SingletonBeanRegistry singletonRegistry = null;
            if (registry instanceof SingletonBeanRegistry) {
                singletonRegistry = (SingletonBeanRegistry) registry;
                if (!this.localBeanNameGeneratorSet && singletonRegistry.containsSingleton(CONFIGURATION_BEAN_NAME_GENERATOR)) {
                    BeanNameGenerator generator = (BeanNameGenerator) singletonRegistry.getSingleton(CONFIGURATION_BEAN_NAME_GENERATOR);
                    this.componentScanBeanNameGenerator = generator;
                    this.importBeanNameGenerator = generator;
                }
            }
    
            //实例化ConfigurationClassParser对象,用来处理配置类
            ConfigurationClassParser parser = new ConfigurationClassParser(
                    this.metadataReaderFactory, this.problemReporter, this.environment,
                    this.resourceLoader, this.componentScanBeanNameGenerator, registry);
    
            Set<ConfigurationClass> alreadyParsed = new HashSet<ConfigurationClass>(configCandidates.size());
            do {
                //parse方法是处理配置类逻辑的核心代码
                parser.parse(configCandidates);
                parser.validate();

    6. 看ConfigurationClassParser类的parse方法:

    public void parse(Set<BeanDefinitionHolder> configCandidates) {
            //稍后执行的parse方法中,所有DeferredImportSelector实现类都会被放入集合deferredImportSelectors中
            this.deferredImportSelectors = new LinkedList<DeferredImportSelectorHolder>();
    
            for (BeanDefinitionHolder holder : configCandidates) {
                BeanDefinition bd = holder.getBeanDefinition();
                try {
                    if (bd instanceof AnnotatedBeanDefinition) {
                        //在这个parse方法中,所有DeferredImportSelector实现类都会被放入集合deferredImportSelectors中,它们的selectImports方法不会被执行,而其他ImportSelector实现类的selectImports都会被执行
                        parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
                    }
                    else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
                        parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
                    }
                    else {
                        parse(bd.getBeanClassName(), holder.getBeanName());
                    }
                }
                catch (BeanDefinitionStoreException ex) {
                    throw ex;
                }
                catch (Exception ex) {
                    throw new BeanDefinitionStoreException(
                            "Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
                }
            }
            //此方法内,会将集合deferredImportSelectors中的所有对象取出来执行其selectImports方法
            processDeferredImportSelectors();
        }

    7. 从上述代码中可以看出,DeferredImportSelector实现类的selectImports方法会在最后被调用,其余的关键逻辑应该在parse(AnnotationMetadata metadata, String beanName)这个关键方法中,顺着这个方法一直追踪下去,直到doProcessConfigurationClass方法,如下图红框所示,所有Import注解的处理,都在processImports方法中:

    8. processImports方法中包含了对ImportSelector实现类和ImportBeanDefinitionRegistrar实现类的处理,以及未实现这些接口的类的处理,我们逐个来分析吧,首先看ImportBeanDefinitionRegistrar实现类的处理,如下图红框位置,调用configClass.addImportBeanDefinitionRegistrar方法将ImportBeanDefinitionRegistrar实现类存入configClass的成员变量importBeanDefinitionRegistrars中,后面的ConfigurationClassPostProcessor类的processConfigBeanDefinitions方法中,this.reader.loadBeanDefinitions(configClasses);会调用这些ImportBeanDefinitionRegistrar实现类的registerBeanDefinitions方法:

    8. processImports方法中包含了对ImportSelector实现类和ImportBeanDefinitionRegistrar实现类的处理,以及未实现这些接口的类的处理,我们逐个来分析吧,首先看ImportBeanDefinitionRegistrar实现类的处理,如下图红框位置,调用configClass.addImportBeanDefinitionRegistrar方法将ImportBeanDefinitionRegistrar实现类存入configClass的成员变量importBeanDefinitionRegistrars中,后面的ConfigurationClassPostProcessor类的processConfigBeanDefinitions方法中,this.reader.loadBeanDefinitions(configClasses);会调用这些ImportBeanDefinitionRegistrar实现类的registerBeanDefinitions方法:

    9. 再来看processImports方法中对ImportSelector实现类的处理,这里略有些复杂,因为涉及到对processImports方法的迭代调用,请看下图红框旁边的红字说明:

    如上图所示,第二步就是在processImports方法中调用了processImports方法,再次进入processImports之后,会着ImportSelector实现类返回的bean名称直接走到第三步的位置,第三步处理的就是没有实现ImportSelector和ImportBeanDefinitionRegistrar这些接口的普通bean了;
    10. processImports方法对没有实现ImportSelector和ImportBeanDefinitionRegistrar这些接口的普通bean的处理是执行processConfigurationClass方法,将这些bean放入了成员变量configurationClasses中,如下图红框所示:

    11. processImports方法分析完毕,Import注解导入的bean都被保存在ConfigurationClassParser实例中,我们回到ConfigurationClassPostProcessor类的processConfigBeanDefinitions方法,如下图,this.reader.loadBeanDefinitions(configClasses);负责处理processImports方法找出的那些打算通过@Import注解来注册到spring容器的bean:

    12. 展开this.reader.loadBeanDefinitions(configClasses)方法,在ConfigurationClassBeanDefinitionReader类中,是对每个配置类逐个执行loadBeanDefinitionsForConfigurationClass方法:

    public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
            TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();
            for (ConfigurationClass configClass : configurationModel) {
                loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
            }
        }

    13. 展开方法,真相大白,实现了ImportBeanDefinitionRegistrar接口的实例,会执行其registerBeanDefinitions方法,其余普通的类,通过loadBeanDefinitionsFromImportedResources方法将其bean定义注册在spring环境:

    private void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass,
                TrackedConditionEvaluator trackedConditionEvaluator) {
    
            if (trackedConditionEvaluator.shouldSkip(configClass)) {
                String beanName = configClass.getBeanName();
                if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
                    this.registry.removeBeanDefinition(beanName);
                }
                this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName());
                return;
            }
    
            if (configClass.isImported()) {
                registerBeanDefinitionForImportedConfigurationClass(configClass);
            }
            for (BeanMethod beanMethod : configClass.getBeanMethods()) {
                loadBeanDefinitionsForBeanMethod(beanMethod);
            }
            //普通的类,通过loadBeanDefinitionsFromImportedResources方法将其bean定义注册在spring环境
            loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
            //实现了ImportBeanDefinitionRegistrar接口的实例,会在loadBeanDefinitionsFromRegistrars方法中执行其registerBeanDefinitions方法
            loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
        }

    14. 前面将普通类、ImportBeanDefinitionRegistrar实现类、ImportSelector实现类的分析已经完成,对于DeferredImportSelector实现类的处理在processDeferredImportSelectors方法中,其实和ImportSelector实现类的处理并无区别,只是处理时机比起ImportSelector实现类略晚,这里就不多说了;
    至此,通过Import注解注册bean的四种方式已经全部分析完毕,小结如下:

    • 1. 普通类(即没有实现ImportBeanDefinitionRegistrar、ImportSelector、DeferredImportSelector等接口的类)会通过ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsFromImportedResources方法将bean定义注册到spring容器;
    • 2. ImportSelector实现类,其selectImports方法返回的bean的名称,通过ConfigurationClassParser类的asSourceClass方法转成SourceClass对象,然后被当作普通类处理;
    • 3. DeferredImportSelector实现类的处理和ImportSelector实现类的处理并无区别,只是处理时机比起ImportSelector实现类略晚;
    • 4. ImportBeanDefinitionRegistrar实现类的registerBeanDefinitions方法会被调用,里面可以注册业务所需的bean定义;

    官方API文档中的疑问解答
    在官方API文档中,对ImportSelector接口的描述如下图所示,红框中的一段意思是: ImportSelector接口的实现类,如果同时也实现了EnvironmentAware、BeanFactoryAware、BeanClassLoaderAware、ResourceLoaderAware这些接口中的一个或几个,那么这些接口对应的方法优先执行,然后才会执行ImportSelector接口的selectImports:

    上图红框中的描述会让我们不禁疑惑:spring是如何做到的呢?一起来看源码吧:
    1. 再次打开ConfigurationClassParser类的processImports方法,如下图两个红框所示,对于@Import注解值中的类,只要实现了ImportBeanDefinitionRegistrar、ImportSelector、DeferredImportSelector等接口中的任何一个,都会调用invokeAwareMethods方法(如果实现的是ImportSelector或DeferredImportSelector接口,此时还没有执行selectImports方法):

    2. 展开invokeAwareMethods方法,真相大白,这里面检查是否实现了EnvironmentAware、BeanFactoryAware、BeanClassLoaderAware、ResourceLoaderAware等接口,如果实现了就调用对应的方法;

    private void invokeAwareMethods(Object importStrategyBean) {
        if (importStrategyBean instanceof Aware) {
            if (importStrategyBean instanceof EnvironmentAware) {
                ((EnvironmentAware) importStrategyBean).setEnvironment(this.environment);
            }
            if (importStrategyBean instanceof ResourceLoaderAware) {
                ((ResourceLoaderAware) importStrategyBean).setResourceLoader(this.resourceLoader);
            }
            if (importStrategyBean instanceof BeanClassLoaderAware) {
                ClassLoader classLoader = (this.registry instanceof ConfigurableBeanFactory ?
                        ((ConfigurableBeanFactory) this.registry).getBeanClassLoader() :
                        this.resourceLoader.getClassLoader());
                ((BeanClassLoaderAware) importStrategyBean).setBeanClassLoader(classLoader);
            }
            if (importStrategyBean instanceof BeanFactoryAware && this.registry instanceof BeanFactory) {
                ((BeanFactoryAware) importStrategyBean).setBeanFactory((BeanFactory) this.registry);
            }
        }
    }

    至此,源码分析工作已经结束,接下来实战@Import注解的使用;
    实战通过Import注解动态创建bean实例
    到了实战验证环节了,本次实战的内容是创建一个springboot工程,通过@Import注解将bean注册到spring容器。代码详见:https://gitee.com/gd1234/springboot-study。

  • 相关阅读:
    【leetcode】1215.Stepping Numbers
    【leetcode】1214.Two Sum BSTs
    【leetcode】1213.Intersection of Three Sorted Arrays
    【leetcode】1210. Minimum Moves to Reach Target with Rotations
    【leetcode】1209. Remove All Adjacent Duplicates in String II
    【leetcode】1208. Get Equal Substrings Within Budget
    【leetcode】1207. Unique Number of Occurrences
    【leetcode】689. Maximum Sum of 3 Non-Overlapping Subarrays
    【leetcode】LCP 3. Programmable Robot
    【leetcode】LCP 1. Guess Numbers
  • 原文地址:https://www.cnblogs.com/jelly12345/p/15568061.html
Copyright © 2011-2022 走看看