攀爬Spring珠穆拉玛峰:@Import、ImportAware的使用与原理分析

@Import解析

@Import提供了不同于@Bean的导入方式,本文将从用法和原理介绍这个由Spring提供的注解。

用法1:@Import+普通类

@Configuration
@Import(Teacher.class)
public class ImportConfig {}
public class Teacher {}

@Import引入一个普通类,可以将该类添加到Spring的单例池内。

用法2:@Import+实现了 ImportSelector的类

@Configuration
@Import(ComputerSelector.class)
public class ImportSelectorConfig {}
public class ComputerSelector implements ImportSelector {
   @Override
   public String[] selectImports(AnnotationMetadata importingClassMetadata) {
      return new String[]{"com.slc.imp.selector.Computer"};
   }

   @Override
   public Predicate<String> getExclusionFilter() {
      return DeferredImportSelector.super.getExclusionFilter();
   }
}
package com.slc.imp.selector;
public class Computer {}

@Import导入的类实现ImportSelector,可以通过selectImports指定需要导入的类的全类名,进而将类交给Spring管理。并且可以通过getExclusionFilter指定需要排除的部分。

用法3:@Import+实现了 ImportBeanDefinitionRegistrar的类

@Configuration
@Import(MyImportBeanDefinitionRegistrar.class)
public class ImportRegistrarConfig {}
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

	@Override
	public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {
		AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(School.class).getBeanDefinition();
		String beanName = importBeanNameGenerator.generateBeanName(beanDefinition, registry);
		registry.registerBeanDefinition(beanName,beanDefinition);

	}

	@Override
	public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
	}
}
public class School {}

@Import导入的类实现ImportBeanDefinitionRegistrar,该接口提供了两个可重写的方法。在两个方法内可以获取到Spring的BeanDefinitionRegistry,通过该类可以手动注入BeanDefinition,然后交给Spring去初始化BeanDefinition,进而将需要注入的类添加到Spring容器中。

两个方法区别不大,区别在于可以使用BeanNameGenerator生成beanName,或者也可以自己指定需要注入的类对应的名。获取的时候使用ByName方式即可。

用法4:@Import与ImportAware

首先定义一个注解,并使用@Import标注

@Retention(RetentionPolicy.RUNTIME)
@Import(SpecialConfigurationAware.class)
public @interface EnableAnnotation {
    String value() default "";
}

编写类并实现ImportAware接口

public class SpecialConfigurationAware implements ImportAware {
    private String value;

    @Override
    public void setImportMetadata(AnnotationMetadata importMetadata) {
        Map<String, Object> annotationAttributes = importMetadata.getAnnotationAttributes(EnableAnnotation.class.getName());
        value = annotationAttributes.get("value");
        System.out.println();
    }

    @Bean
    public Foo foo() {
        return new Foo(this.value);
    }
}

在任意一个配置类引入自定义注解

@Configuration@EnableAnnotation("appconfig1111")public class AwareConfiguration {}

这种方法可以获取到自定义注解标注的类上面的所有注解信息,在本例中即可以获取到AwareConfiguration类上面的所有注解信息。

比如@EnableAsync导入的类AsyncConfigurationSelector,里面就获取了对应注解的元信息。

解析原理

Spring加载的过程大概可以分为两个部分:解析各类Bean信息并转换成对应的BeanDefinition,加载BeanDefinition完成初始化并添加到Spring单例池中。

以注解式启动Spring为了,解析的过程即为扫描包下的各个注解,获取对应的类,封装成BeanDefinition,本文主要分析一下 “解析”这一过程。

首先是扫描,Spring是在 refresh#invokeBeanFactoryPostProcessors 里面完成扫描的过程的。

invokeBeanFactoryPostProcessors
protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
    PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
    if (beanFactory.getTempClassLoader() == null && beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
        beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
        beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
    }
}

委托给PostProcessorRegistrationDelegate完成BeanFactoryPostProcessors的调用。

贴一下重要部分的代码。

public static void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {
        //...	...
        List<BeanDefinitionRegistryPostProcessor> currentRegistryProcessors = new ArrayList<>();
        // 1.首先,调用实现 PriorityOrdered 的 BeanDefinitionRegistryPostProcessors。    
        String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
        for (String ppName : postProcessorNames) {
            if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
                //获取BeanDefinitionRegistryPostProcessor 接口定义的类,            
                // Spring内部定义的扫描类,优先级高于 BeanFactoryPostProcessor,为 BeanFactoryPostProcessor的子类            
                currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
                processedBeans.add(ppName);
            }
        }
        sortPostProcessors(currentRegistryProcessors, beanFactory);
        registryProcessors.addAll(currentRegistryProcessors);
        //调用处理器,完成扫描    
        invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
        //...	...
        String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanFactoryPostProcessor.class, true, false);
        List<BeanFactoryPostProcessor> priorityOrderedPostProcessors = new ArrayList<>();
        List<String> orderedPostProcessorNames = new ArrayList<>();
        List<String> nonOrderedPostProcessorNames = new ArrayList<>();
        for (String ppName : postProcessorNames) {
            if (processedBeans.contains(ppName)) {
                /**                 
                 * skip - already processed in first phase above				 
                 * * 已经先调用完毕了,比如ConfigurationClassPostProcessor,该类实现了 BeanDefinitionRegistryPostProcessor 接口				 
                 * * 而BeanDefinitionRegistryPostProcessor是 BeanFactoryPostProcessor的子类,所以这个集合里面还是会得到该类的名称,				 
                 * * 所以此处跳过(不做处理)				 
                 * */
            } else if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
                priorityOrderedPostProcessors.add(beanFactory.getBean(ppName, BeanFactoryPostProcessor.class));
            } else if (beanFactory.isTypeMatch(ppName, Ordered.class)) {
                orderedPostProcessorNames.add(ppName);
            } else {
                nonOrderedPostProcessorNames.add(ppName);
            }
        }
        List<BeanFactoryPostProcessor> nonOrderedPostProcessors = new ArrayList<>(nonOrderedPostProcessorNames.size());
        for (String postProcessorName : nonOrderedPostProcessorNames) {
            nonOrderedPostProcessors.add(beanFactory.getBean(postProcessorName, BeanFactoryPostProcessor.class));
        }
        invokeBeanFactoryPostProcessors(nonOrderedPostProcessors, beanFactory);
        //...	
    }
  • 分别按优先级调用BeanDefinitionRegistryPostProcessorBeanFactoryPostProcessor接口的处理器。
  • 前者为后者的子类,两个接口都算是工厂级别的处理器,实现BeanDefinitionRegistryPostProcessor类会被最早调用,

从名字不难得出,Spring的本意是想先调用负责注册BeanDefinition的处理器,然后再调用别的工厂处理器。并且Spring在源码里面也标注了如果需要扩展的话,建议实现BeanFactoryPostProcessor,而不是BeanDefinitionRegistryPostProcessor

private static void invokeBeanDefinitionRegistryPostProcessors(Collection<? extends BeanDefinitionRegistryPostProcessor> postProcessors, BeanDefinitionRegistry registry) {
   for (BeanDefinitionRegistryPostProcessor postProcessor : postProcessors) {
       postProcessor.postProcessBeanDefinitionRegistry(registry);
   }
}

我们来看扫描部分的代码,在前文关于Spring的启动流程里面有说过,Spring最早的时候会注册五到六个内部用的Bean,其中一个就用到了这里。在整个扫描阶段第一个被使用的BeanFactory处理器是ConfigurationClassPostProcessor

postProcessBeanDefinitionRegistry
public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor, PriorityOrdered, ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware {
    //......
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
        int registryId = System.identityHashCode(registry);
        if (this.registriesPostProcessed.contains(registryId)) {
            throw new IllegalStateException("postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);
        }
        if (this.factoriesPostProcessed.contains(registryId)) {
            throw new IllegalStateException("postProcessBeanFactory already called on this post-processor against " + registry);
        }
        this.registriesPostProcessed.add(registryId);
        processConfigBeanDefinitions(registry);
    }
    //......
    @Override
    public int getOrder() {
        return Ordered.LOWEST_PRECEDENCE;
    }
}

同时实现了BeanDefinitionRegistryPostProcessorPriorityOrdered,并且优先级数值为Ordered.LOWEST_PRECEDENCInteger.MAX_VALUE,就是为了确保这是第一个被调用了,可想而知其中重要性。在这个处理器内部Spring完成了配置类的解析。继续关键部分代码。

processConfigBeanDefinitions
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
        List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
        String[] candidateNames = registry.getBeanDefinitionNames();
        for (String beanName : candidateNames) {
            BeanDefinition beanDef = registry.getBeanDefinition(beanName);
            if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
                }
            }
            // 检查是否是一个配置类,			
            // 如果加了@Configuration,那么对应的BeanDefinition为full;			
            // 如果加了@Bean,@Component,@ComponentScan,@Import,@ImportResource这些注解,则为lite。			
            else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
                configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
            }
        }        
        // Return immediately if no @Configuration classes were found		
        if (configCandidates.isEmpty()) {
            return;
        }
        // Sort by previously determined @Order value, if applicable		
        configCandidates.sort((bd1, bd2) -> {
            int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
            int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
            return Integer.compare(i1, i2);
        });        // Detect any custom bean name generation strategy supplied through the enclosing application context		
        SingletonBeanRegistry sbr = null;
        if (registry instanceof SingletonBeanRegistry) {
            sbr = (SingletonBeanRegistry) registry;
            if (!this.localBeanNameGeneratorSet) {
                BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR);
                if (generator != null) {
                    this.componentScanBeanNameGenerator = generator;
                    this.importBeanNameGenerator = generator;
                }
            }
        }
        if (this.environment == null) {
            this.environment = new StandardEnvironment();
        }        // Parse each @Configuration class		
        ConfigurationClassParser parser = new ConfigurationClassParser(this.metadataReaderFactory, this.problemReporter, this.environment, this.resourceLoader, this.componentScanBeanNameGenerator, registry);
        Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
        Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
        do {
        	//关键代码1
            parser.parse(candidates);
            parser.validate();
            Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
            configClasses.removeAll(alreadyParsed);
            // Read the model and create bean definitions based on its content			
            if (this.reader == null) {
                this.reader = new ConfigurationClassBeanDefinitionReader(registry, this.sourceExtractor, this.resourceLoader, this.environment, this.importBeanNameGenerator, parser.getImportRegistry());
            }
            //关键代码2
            this.reader.loadBeanDefinitions(configClasses);
            alreadyParsed.addAll(configClasses);
            candidates.clear();
            if (registry.getBeanDefinitionCount() > candidateNames.length) {
                String[] newCandidateNames = registry.getBeanDefinitionNames();
                Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));
                Set<String> alreadyParsedClasses = new HashSet<>();
                for (ConfigurationClass configurationClass : alreadyParsed) {
                    alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
                }
                for (String candidateName : newCandidateNames) {
                    if (!oldCandidateNames.contains(candidateName)) {
                        BeanDefinition bd = registry.getBeanDefinition(candidateName);
                        if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) && !alreadyParsedClasses.contains(bd.getBeanClassName())) {
                            candidates.add(new BeanDefinitionHolder(bd, candidateName));
                        }
                    }
                }
                candidateNames = newCandidateNames;
            }
        } while (!candidates.isEmpty());
        // Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes		
        if (sbr != null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
            sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
        }
        if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {
            // Clear cache in externally provided MetadataReaderFactory; this is a no-op			
            // for a shared cache since it'll be cleared by the ApplicationContext.			
            ((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();
        }
    }

这个方法里面主要是两行代码:

  • parser.parse(candidates):创建一个解析器,让parser 解析 candidates,获取配置类,并标注每个配置类的信息。
  • this.reader.loadBeanDefinitions(configClasses):加载配置类,包含@Import,@ImportResource等资源的处理。

在解析对应包下的各个注解后,Spring会封装成ConfigurationClass,并且通过do while语句,不断比对当前已经解析的配置类和新的配置类,确保每次解析完成的ConfiguartionClass不会再引进新的配置类。最终将所有的的配置类解析完毕。

parser.parse(candidates)

点进parse方法看Spring是如何解析的。

 public void parse(Set<BeanDefinitionHolder> configCandidates) {
        for (BeanDefinitionHolder holder : configCandidates) {
            BeanDefinition bd = holder.getBeanDefinition();
            try {
                if (bd instanceof AnnotatedBeanDefinition) {
                    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 (Throwable ex) {
                throw new BeanDefinitionStoreException("Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
            }
        }
        this.deferredImportSelectorHandler.process();
    }

在上一个方法里面的:

else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory))

会判断当前BeanDefinitionNames对应的类是否是配置类。在最开始的时候,BeanDefnitionNames只有Spring内部自己注册的BeanDefinition以外,就是我们手动传的AppConfig,实际上也只有这个类会进行的之后的流程。

最开始的时候只有AppConfig是配置类。因为AppConfig标注了@Configuration

    protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
        processConfigurationClass(new ConfigurationClass(metadata, beanName), DEFAULT_EXCLUSION_FILTER);
    }

由于是注解式启动,AppConfig对应的BeanDefinition属于AnnotatedBeanDefinition
继续跟进,跳过一些暂时不需要关注的地方,最终进入到doProcessConfigurationClass方法。

doProcessConfigurationClass
 protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter) throws IOException {
        if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
            // Recursively process any member (nested) classes first      
            processMemberClasses(configClass, sourceClass, filter);
        }
        // Process any @PropertySource annotations   
        for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(sourceClass.getMetadata(), PropertySources.class, org.Springframework.context.annotation.PropertySource.class)) {
            if (this.environment instanceof ConfigurableEnvironment) {
                processPropertySource(propertySource);
            } else {
                logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() + "]. Reason: Environment must implement ConfigurableEnvironment");
            }
        }   // 处理 @ComponentScan 注解   
        Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
        if (!componentScans.isEmpty() && !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
            for (AnnotationAttributes componentScan : componentScans) {
                // The config class is annotated with @ComponentScan -> perform the scan immediately         
                Set<BeanDefinitionHolder> scannedBeanDefinitions = this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
                // Check the set of scanned definitions for any further config classes and parse recursively if needed         
                for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
                    BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
                    if (bdCand == null) {
                        bdCand = holder.getBeanDefinition();
                    }
                    if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
                        parse(bdCand.getBeanClassName(), holder.getBeanName());
                    }
                }
            }
        }
        // 处理 @Import annotations   processImports(configClass, sourceClass, getImports(sourceClass), filter, true);   
        // Process any @ImportResource annotations  
        AnnotationAttributes importResource = AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
        if (importResource != null) {
            String[] resources = importResource.getStringArray("locations");
            Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
            for (String resource : resources) {
                String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
                configClass.addImportedResource(resolvedResource, readerClass);
            }
        }   // 处理 @Bean    
        Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
        for (MethodMetadata methodMetadata : beanMethods) {
            configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
        }
        // Process default methods on interfaces  
        processInterfaces(configClass, sourceClass);
        // Process superclass, if any   
        if (sourceClass.getMetadata().hasSuperClass()) {
            String superclass = sourceClass.getMetadata().getSuperClassName();
            if (superclass != null && !superclass.startsWith("java") && !this.knownSuperclasses.containsKey(superclass)) {
                this.knownSuperclasses.put(superclass, configClass);
                // Superclass found, return its annotation metadata and recurse         
                return sourceClass.getSuperClass();
            }
        }   // No superclass -> processing is complete   
        return null;
    }
  • 解析@PropertySource注解;
  • 配置类包含@ComponentScan时,解析@ComponentScan,并对@ComponentScan标注的扫描路径进行解析
    (会不断递归,如果再次发现@ComponentScan,会再次重复扫描)
  • 解析@Import
  • 解析@ImportResource
  • 解析@Bean
  • 解析接口上的默认方法(支持jdk8的新特性:接口可以定义default方法,Spring支持使用@Bean标注接口的default方法来注入Bean)
processImports(configClass, sourceClass, getImports(sourceClass), filter, true)

processImports方法内解析@Import,这里有一个需要注意的点:getImports,在进入processImports前,会先调用getImports方法。

这个方法的逻辑非常简单:递归获取当前配置类上面的所有注解,包括注解的元注解,

 private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass, Collection<SourceClass> importCandidates, Predicate<String> exclusionFilter, boolean checkForCircularImports) {
        if (importCandidates.isEmpty()) {
            return;
        }
        if (checkForCircularImports && isChainedImportOnStack(configClass)) {
            this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
        } else {
            this.importStack.push(configClass);
            try {
                for (SourceClass candidate : importCandidates) {
                    if (candidate.isAssignable(ImportSelector.class)) {
                        // Candidate class is an ImportSelector -> delegate to it to determine imports               
                        Class<?> candidateClass = candidate.loadClass();
                        //反射获取@Import导入的类               
                        ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class, this.environment, this.resourceLoader, this.registry);
                        Predicate<String> selectorFilter = selector.getExclusionFilter();
                        if (selectorFilter != null) {
                            exclusionFilter = exclusionFilter.or(selectorFilter);
                        }
                        if (selector instanceof DeferredImportSelector) {
                            this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
                        } else {
                            String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
                            Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter);
                            processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);
                        }
                    } else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
                        // Candidate class is an ImportBeanDefinitionRegistrar ->               
                        // delegate to it to register additional bean definitions               
                        Class<?> candidateClass = candidate.loadClass();
                        ImportBeanDefinitionRegistrar registrar = ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class, this.environment, this.resourceLoader, this.registry);
                        configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
                    } else {
                        // Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->               
                        // process it as an @Configuration class               
                        this.importStack.registerImport(currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
                        MultiValueMap<String, AnnotationMetadata> imports = this.importStack.getImports();
                        System.out.println(imports);
                        processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);
                    }
                }
            } catch (BeanDefinitionStoreException ex) {
                throw ex;
            } catch (Throwable ex) {
                throw new BeanDefinitionStoreException("Failed to process import candidates for configuration class [" + configClass.getMetadata().getClassName() + "]", ex);
            } finally {
                this.importStack.pop();
            }
        }
    }

我们可以看到处理@Import是,Spring会对其导入的类做以下几种判断:

  • 是否实现了ImportSelector,以及是否更进一步实现了DeferredImportSelector,(DeferredImportSelectorImportSelector的子接口,有延迟加载的左右,主要是加载时机的不同。后面会更进一步解析这一部分的处理)
  • 是否实现了ImportBeanDefinitionRegistrar
  • 都没有实现,此时作为普通类进行处理;

整理一下processImports干了些什么:

  1. 先通过getImports获取当前配置类的@Import导入的类,即@Import的value值;
  2. 通过importStack这一属性进行配置类循环依赖的判断,如果不存在配置类的循环依赖,则继续解析(后面会说明如何进行的判断)
  3. 因为@Import可以导入多个类,所以循环处理importCandidates
  4. 判断导入类,如果导入类实现了ImportSelector,先反射获取@Import引入的类;
  5. 如果该类没有实现DeferredImportSelector,则直接调用selectImports,获取@Import引入的类指定的全限定类名的数组(真正引入的类),封装该类作为配置类;
  6. 递归调用 processImports解析该配置类,因为导入的配置类(真正引入的类)可能继续实现了ImportSelector或者ImportBeanDefinitionRegistrar或者是一个使用了@Bean的配置类,所以需要递归。
  7. 如果实现了DeferredImportSelector,则会将真正引入的类存放到deferredImportSelectors集合里面,暂时不做处理
  8. 如果实现了ImportBeanDefinitionRegistrar,则存放到importBeanDefinitionRegistrars集合里面
  9. 解析完毕

上面讲述的过程比较晦涩,主要是在解析的过程内会出现很多递归的情况,需要结合代码多看一下。

解析了@Import之后,会接着解析@ImportResource@Bean、接口默认方法。

回到parser.parse(candidates)
public void parse(Set<BeanDefinitionHolder> configCandidates) {
   for (BeanDefinitionHolder holder : configCandidates) {
      BeanDefinition bd = holder.getBeanDefinition();
      try {
         if (bd instanceof AnnotatedBeanDefinition) {
            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 (Throwable ex) {
         throw new BeanDefinitionStoreException(
               "Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
      }
   }

   this.deferredImportSelectorHandler.process();
}

此时调用之前所有实现了deferredImportSelector接口类,再执行一遍都processImports操作

回到processConfigBeanDefinitions
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
 ...
 ...
    parser.parse(candidates);
    parser.validate();

    Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
    configClasses.removeAll(alreadyParsed);

    // Read the model and create bean definitions based on its content
    if (this.reader == null) {
        this.reader = new ConfigurationClassBeanDefinitionReader(
            registry, this.sourceExtractor, this.resourceLoader, this.environment,
            this.importBeanNameGenerator, parser.getImportRegistry());
    }
    this.reader.loadBeanDefinitions(configClasses);
 ...
 ...
}

最终回到 parser.parse(candidates)方法,此时所有的@Import全部解析完毕

在这里插入图片描述

调用parse.parse其实只是各个注解的解析操作,主要是为ConfigClass设置属性,并没有完全加载成BeanDefinition(部分配置类会先加载到BeanDefinitonMap中)

  • 比如这个配置类如果是被@Import导入的,则属性:importedBy标注是被哪个配置类引入的,
  • 比如这个配置类如果有@Bean注解,则属性:beanMethods就会指明有哪些方法。
  • 比如这个配置类是@Import,而@Import导入的类实现的是ImportBeanDefinitionRegistrar,则属性:importBeanDefinitionRegistrars就是哪个实现了ImportBeanDefinitionRegistrar的类
loadBeanDefinitions
public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
   TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();
   for (ConfigurationClass configClass : configurationModel) {
      loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
   }
}

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(configClass.getImportedResources());
		loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}

调用this.reader.loadBeanDefinitions(configClasses),此时才会处理所有注解对应的操作,即加载到BeanDefinitionMap中。

注解式@Import:为导入类做一个开关

这块儿内容的标题不知道怎么取,总的来说就是@Import的一个特别用法,但是用的非常多。在Spring的源码里面我们可以看见很多@EnableXXX的注解。当使用这个注解时就会导入一些类到Spring单例池(容器)中。如果不用该注解就不会导入。

很多文章以及文章的评论里面都出现过这样一段内容:“当@Import作为元注解修饰其他注解的时候,到底需不要需不需要加@Configuration

我的看法是 不需要

为了说清楚这个东西,有一个方法需要再强调一下:getImports:Spring在扫描一个配置类时,会递归判断该配置类上面的所有元注解。

使用@EnableXXX时,往往@EnableXXX标注的类也被@Configuration标注,这两个注解通常是一起用的。因为getImports所以@EnableXXX里面的@Import也可以被解析到。

关于DeferredImportSelector的作用

起延迟加载的作用,普通的ImportSelector早早就被解析成ConfigurationClass,添加到ConfigurationClassParserconfigurationClasses里面。直到所有的配置类都被解析完毕后,才会调用存储DeferredImportSelector的集合,然后循环处理。

public void parse(Set<BeanDefinitionHolder> configCandidates) {
		for (BeanDefinitionHolder holder : configCandidates) {
			BeanDefinition bd = holder.getBeanDefinition();
			try {
				if (bd instanceof AnnotatedBeanDefinition) {
					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 (Throwable ex) {
				throw new BeanDefinitionStoreException(
						"Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
			}
		}
		//所有配置类都解析完了,才会调用它
		this.deferredImportSelectorHandler.process();
	}

简单来说,和ImportSelector的区别就是加载时机不同。

  • ImportSelector是在解析任意一个配置类都会进行的判断,属于解析配置类的 进行时状态;
  • DeferredImportSelector则是所有配置类解析完毕才解析,属于解析配置类的 最后状态;

光说区别,不说应用场景是耍流氓。

应用场景:SpringBoot的默认配置类。

SpringBoot在引入各个starter的时候,会默认给程序员导入许多基础类,如果程序员没有重新定义基础类的话,那么直接使用starter里面提供的基础类即可。

SpringBoot就是通过DeferredImportSelector做到这一点的(除此之外,还要加上@Condition)

参看starter里面的各个默认配置类,会发现大量的@Condition,该注解的作用是判断单例池中是否已经存在程序员自己定义的Bean时,如果已经存在,就不再加载该starter的默认配置类。

但是有一个问题是:如何确保 程序员定义的类注册进单例池的时间 一定早于starter提供的配置呢?

换个角度:如何确保starter提供的配置的注册时间一定晚于程序员定义的类?

Map<ConfigurationClass, ConfigurationClass> configurationClasses = new LinkedHashMap<>()

因为DeferredImportSelector是最后被解析出来的配置类,他是最后添加进配置类的map集合当中的。而这个map集合,它是有序的。

所以程序员定义的Bean总是早于DeferredImportSelector被解析为ConfigurationClass,进而更早的解析成BeanDefiniton,进而更早的注册到spring单例池中(一级缓存)

配置文件的循环依赖时如何产生和解决的?

processImports里面调用的一个方法:isChainedImportOnStack是如何作用的。

先说一下配置文件的循环依赖是如何产生的:(之所以强调 是配置文件的循环依赖,是为了区分自动装载的循环依赖)

比如:

@Configuration
@Import(ConfiguationB.class)
public class ConfiguationA {
}

@Configuration
@Import(ConfiguationC.class)
public class ConfiguationB {
}

@Configuration
@Import(ConfiguationA.class)
public class ConfiguationC {
}

像这种情况,配置A引入配置B,配置B引入配置C,配置C引入A,这样就构造了循环,解析的时候就会出现问题(会不断递归),甚至A配置类Imoprt本身类,也会问题。

解决方法:存储到ConfigurationClassParserImportStack中,借由这个类进行判断。

点进ImportStack这个类:

private static class ImportStack extends ArrayDeque<ConfigurationClass> implements ImportRegistry {
   private final MultiValueMap<String, AnnotationMetadata> imports = new LinkedMultiValueMap<>();
   ...  
}

可以知道这是一个双端队列。并且内部维护一个MultiValueMap

从解析第一个配置类开始,每次解析@Import是都先将当前被解析的配置类添加到importStack的map中。

this.importStack.registerImport(
      currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());

以上面的情况作为例子:

  • 解析A,将B类添加到importStack的map中,调用processConfigurationClass解析B类,
  • 解析B,将C类添加到importStack的map中,调用processConfigurationClass解析C类,
  • 解析C,将A类添加到importStack的map中,调用processConfigurationClass解析C类,此时isChainedImportOnStack将会判断发生了循环依赖
private boolean isChainedImportOnStack(ConfigurationClass configClass) {
		if (this.importStack.contains(configClass)) {
			String configClassName = configClass.getMetadata().getClassName();
			AnnotationMetadata importingClass = this.importStack.getImportingClassFor(configClassName);
			while (importingClass != null) {
				if (configClassName.equals(importingClass.getClassName())) {
					return true;
				}
				importingClass = this.importStack.getImportingClassFor(importingClass.getClassName());
			}
		}
		return false;
	}

这个方法的判断逻辑很简单:判断当前解析的配置类是否已经出现在了importStack的map中,如果已经出现了,则证明出现了循环依赖。

简单总结一下原理:通过一个map来存放已经做过处理的配置类,如果递归解析的某个阶段发现当前处理的配置类已经被解析了,那么就是发生了循环依赖。

ImportAware的原理

ImportAware的解析原理并不在processImports方法中,它是通过spring的后置处理器解决的。

首先是在调用postProcessBeanFactory的时候,spring会注册ImportAwareBeanPostProcessor,这个后置处理器就是用来处理ImportAware的。

@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
   int factoryId = System.identityHashCode(beanFactory);
   if (this.factoriesPostProcessed.contains(factoryId)) {
      throw new IllegalStateException(
            "postProcessBeanFactory already called on this post-processor against " + beanFactory);
   }
   this.factoriesPostProcessed.add(factoryId);
   if (!this.registriesPostProcessed.contains(factoryId)) {
      // BeanDefinitionRegistryPostProcessor hook apparently not supported...
      // Simply call processConfigurationClasses lazily at this point then.
      processConfigBeanDefinitions((BeanDefinitionRegistry) beanFactory);
   }
   //对Configuration类进行增强
   enhanceConfigurationClasses(beanFactory);
   //添加 ImportAwareBeanPostProcessor 后置处理器,用来支持ImportAware的解析,
   beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
}

调用后置处理器

protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {
    ...
    Object wrappedBean = bean;
    if (mbd == null || !mbd.isSynthetic()) {
       wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
    }
    ...
}


public Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName)
    throws BeansException {

    Object result = existingBean;
    //getBeanPostProcessors()会获取到之前添加的ImportAwareBeanPostProcessor
    for (BeanPostProcessor processor : getBeanPostProcessors()) {
        Object current = processor.postProcessBeforeInitialization(result, beanName);
        if (current == null) {
            return result;
        }
        result = current;
    }
    return result;
}

//ImportAwareBeanPostProcessor里面的postProcessBeforeInitialization
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
    if (bean instanceof ImportAware) {
        ImportRegistry ir = this.beanFactory.getBean(IMPORT_REGISTRY_BEAN_NAME, ImportRegistry.class);
        AnnotationMetadata importingClass = ir.getImportingClassFor(ClassUtils.getUserClass(bean).getName());
        if (importingClass != null) {
            ((ImportAware) bean).setImportMetadata(importingClass);
        }
    }
    return bean;
}

在调用该处理器时,会获取ImportRegistry,这个ImportRegistry实际上就是之前parser里面的ImportStack

ImportStack那么是在什么时候注册进spring容器里面的呢?

答案:在刚解析完所有配置的时候,spring就会手动注册进单例池,供ImportAwareBeanPostProcessor使用。

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry){
	...
    // Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes
    if (sbr != null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
       sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
    }
	...
}

ImportAwareBeanPostProcessor在容器中取得ImportRegistry时(即ImportStack),就可以获取ImportStack存储的map里面的值了。这个值就是导入ImportAware的类上的所有注解信息。

需要注意的地方1:多处引用导致不能获取到元信息

注意这里有一个坑:我们先查看getImportingClassFor是如何取的注解元信息。

public AnnotationMetadata getImportingClassFor(String importedClass) {
   List<AnnotationMetadata> annotationMetadata = this.imports.get(importedClass);
   return CollectionUtils.lastElement(annotationMetadata);
}

CollectionUtils.lastElement:取最后一个元素。

需要注意的是 Spring 是用 MultiValueMap 来存储被Import的配置类的。

private final MultiValueMap<String, AnnotationMetadata> imports = new LinkedMultiValueMap<>();

MultiValueMap 是spring自己实现的一个特殊map,一个键可以对应多个值(其实就是用list来作为值,添加新值的时候就添加到list里面即可)。上面说了这个map的作用是用来存储被Import的类是被哪个配置类导入的。举个例子:

@Configuration
@Import(Teacher.class)
public class ImportConfig {}

public class Teacher {}

这两个类在解析完存储到map时,键为:com.slc.imp.base.Teacher ,值为: 【ImportConfig类上的所有注解信息 】(list=1)
从名字可以知道这是一个可以存储 同一个键却有多个值的特殊 map 集合。那么 spring 为什么要用 MultiValueMap 来存储 imports 呢?
这是因为可能会出现被import的类被多次引入。比如Teacher类,被ConfigB引入,此时,map里面的Teacher这一条对应的value就会再增加一个元注解。

即:key:com.slc.imp.base.Teacher ,value: 【ImportConfig类上的所有注解信息, ConfigB类上的所有注解信息】(list=2)

然后,getImportingClassFor调用CollectionUtils.lastElement时,总是取得最后一个元素,这样就会造成前面引入这个注解类的元信息获取不到了。

  • 比如定义@EnableA1和@EnableA2,两个注解都@import(A.class)
  • A类实现了ImportAware接口
  • @EnableA1@EnableA2在不同的配置类上面引入。

此时@EnableA2晚于@@EnableA1被解析,那么A类的importMetadata,就是使用@EnableA2的配置类上面的注解元信息。

所以:建议使用ImportAware时,一个@EnableXXX对应一个ImportAware

需要注意的地方2:为什么说ImportAware需要和@import搭配使用?

@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
    if (bean instanceof ImportAware) {
        ImportRegistry ir = this.beanFactory.getBean(IMPORT_REGISTRY_BEAN_NAME, ImportRegistry.class);
        AnnotationMetadata importingClass = ir.getImportingClassFor(ClassUtils.getUserClass(bean).getName());
        if (importingClass != null) {
            ((ImportAware) bean).setImportMetadata(importingClass);
        }
    }
    return bean;
}

ImportAwareBeanPostProcessor的后置方法里面,只判断了当前类是否是ImportAware的子接口,并没有判断是否是@Import的相关逻辑。似乎只要这个Bean只要实现ImportAware就可以进行逻辑处理了。

如果不通过@Import引入Teacher,换成@Bean,引入Teacher,是不是也可以?

ImportStackImportRegistry的子接口,查看spring源码对ImportRegistry的解释: Registry of imported class,翻译过来就是:导入类注册表。看来ImportRegistry就是专门为@Import的解析做支持的,查看spring对importstack的使用,可以发现只有在解析@Import时会往importstack里面加数据。

用上面这个例子来看,加入通过@Bean来注入Teacher,那么此时自然不会走@Import的解析路线,importstack里面根本就没有对应的值,所以此时importingClass为空,根本就不调用TeachersetImportMetadata

做个总结:只有@Import导入的ImportAware实现类,会进行回调。

需要注意的地方3:ImportAware的AnnotationMetadata是什么?

AnnotationMetadata定义解释:导入ImportAware的配置类 的所有注解的信息(会封装成AnnotationMetadata)

  • @Enablexxx+@Import,比如这种使用方法,获取的是标注@Enablexxx的配置类上面的 所有注解信息

通常情况下我们使用@Enablexxx,其实只是为了获取@Enablexxx的属性值,但实际上获取到的不仅仅是@Enablexxx的属性内容,还有其他不需要关注的注解的内容。

举个例子:

@Abc("1")
@Def("2")
@Configuation
@Enablexxx("3")
pubic  Class A{
    
}

在上面这种情况下 AnnotationMetadata 就包含 @Abc、@Def、@Configuation、@Enablexxx(“3”) 这几个注解的元信息。
所以在 需要注意的地方1 里面的例子,明确指定了@EnableA1@EnableA2在不同的配置类上面引入。因为如果是同一个配置类上面引入的,那么AnnotationMetadata还是可以获取到两个注解的信息,似乎就没有发生覆盖问题。(实际上只是碰巧)

核心关注点:AnnotationMetadata不是只获取@EnableXXX,而是“导入这个类”的配置类的所有注解,只是后者的所有注解包含@Enablexxx而已。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值