3. [SpringBoot source code] SpringBoot automatic configuration principle

Table of contents

1. Introduction

1)、@SpringBootConfiguration

2)、@ComponentScan

3)、@EnableAutoConfiguration

二、@AutoConfigurationPackage

三、@Import(AutoConfigurationImportSelector.class)

1), Introduction of AutoConfigurationImportSelector

2), automatic configuration process

2-1)、ConfigurationClassParser#parse()

2-2)、this.deferredImportSelectorHandler.process()

2-2-1), the first step: register()

2-2-2), the second step: processGroupImports()


The source code of this article is analyzed based on the spring-boot-2.1.0.RELEASE version, and there may be some differences in each version.

1. Introduction

The startup of SpringBoot starts from the main() method:

@SpringBootApplication
public class SampleTomcatApplication {

	public static void main(String[] args) {
		SpringApplication.run(SampleTomcatApplication.class, args);
	}

}

On the main startup class, we only need to add the @SpringBootApplication annotation to successfully start the springboot application. Then we will start with the annotation and see how it helps us configure some of the environments we need.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
// @SpringBootConfiguration内部使用@Configuration修饰,标记启动类是一个Spring的配置类
@SpringBootConfiguration
// @EnableAutoConfiguration: 实现自动装配的核心注解. 内部包含两个注解:@AutoConfigurationPackage + @Import(AutoConfigurationImportSelector.class)
@EnableAutoConfiguration
// 包扫描:扫描启动类所在的包以及子包所有Bean组件并注册到IOC容器中
@ComponentScan(excludeFilters = {
    @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
    @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
// SpringBoot项目启动的核心注解
// @SpringBootApplication = @SpringBootConfiguration + @EnableAutoConfiguration + @ComponentScan
public @interface SpringBootApplication {

    /**
* Exclude specific auto-configuration classes such that they will never be applied.
* @return the classes to exclude
*/
    // 等同于EnableAutoConfiguration注解的exclude属性
    @AliasFor(annotation = EnableAutoConfiguration.class)
    Class<?>[] exclude() default {};

    /**
* Exclude specific auto-configuration class names such that they will never be
* applied.
* @return the class names to exclude
* @since 1.3.0
*/
    // 等同于EnableAutoConfiguration注解的excludeName属性
    @AliasFor(annotation = EnableAutoConfiguration.class)
    String[] excludeName() default {};

    /**
* Base packages to scan for annotated components. Use {@link #scanBasePackageClasses}
* for a type-safe alternative to String-based package names.
* @return base packages to scan
* @since 1.3.0
*/
    // 等同于ComponentScan注解的basePackages属性
    @AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
    String[] scanBasePackages() default {};

    /**
* Type-safe alternative to {@link #scanBasePackages} for specifying the packages to
* scan for annotated components. The package of each class specified will be scanned.
* <p>
* Consider creating a special no-op marker class or interface in each package that
* serves no purpose other than being referenced by this attribute.
* @return base packages to scan
* @since 1.3.0
*/
    // 等同于ComponentScan注解的basePackageClasses属性
    @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
    Class<?>[] scanBasePackageClasses() default {};

}

@SpringBootApplication is decorated with three annotations:

1)、@SpringBootConfiguration

The tag startup class is a Spring configuration class.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
// 相当于@SpringBootConfiguration就是@Configuration
public @interface SpringBootConfiguration {

}

2)、@ComponentScan

Scan the package where the startup class is located and all Bean components in subpackages and register them in the IOC container.

3)、@EnableAutoConfiguration

The core annotation for automatic assembly contains two annotations: @AutoConfigurationPackage + @Import(AutoConfigurationImportSelector.class).

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
// 自动配置包
@AutoConfigurationPackage
// 使用@Import注解导入AutoConfigurationImportSelector类,实现了ImportSelector接口,重写了selectImports()方法,帮助我们返回所有需要被注册为bean的类全限定类名的数组集合
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

	String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

	/**
	 * 排除特定的自动配置类,使它们永远不会被应用
	 * Exclude specific auto-configuration classes such that they will never be applied.
	 * @return the classes to exclude
	 */
	Class<?>[] exclude() default {};

	/**
	 * 排除特定的自动配置类名称,使其永远不会被应用
	 * Exclude specific auto-configuration class names such that they will never be
	 * applied.
	 * @return the class names to exclude
	 * @since 1.3.0
	 */
	String[] excludeName() default {};

}

The automatic assembly of springboot is realized by @EnableAutoConfiguration annotation, and @EnableAutoConfiguration is composed of the following two parts:

1、@AutoConfigurationPackage

2、@Import(AutoConfigurationImportSelector.class)

二、@AutoConfigurationPackage

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
// 向容器中导入了AutoConfigurationPackages.Registrar组件
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {

}

It can be seen that the function of the @AutoConfigurationPackage annotation is realized by the @Import annotation, which is the underlying annotation of the Spring framework. Its function is to import a certain component class AutoConfigurationPackages.Registrar into the container. Let's see what component it is?

The role of the @AutoConfigurationPackage annotation is to manage the package where the class that adds the annotation is located as an automatic configuration package. That is to say, when the SpringBoot application starts, the package where the startup class is located will be used as the automatically configured package by default, so that the package where the main program class is located and the components under all sub-packages will be scanned into the spring container. 

// 实现了ImportBeanDefinitionRegistrar、DeterminableImports接口,重写了registerBeanDefinitions()、determineImports()方法
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

    // 注册bean定义
    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata,
            BeanDefinitionRegistry registry) {
        register(registry, new PackageImport(metadata).getPackageName());
    }

    // 返回一组需要注入的对象
    @Override
    public Set<Object> determineImports(AnnotationMetadata metadata) {
        return Collections.singleton(new PackageImport(metadata));
    }

}

AutoConfigurationPackages.Registrar implements ImportBeanDefinitionRegistrar, and rewrites the method of registerBeanDefinitions() to register bean definition information, so that when refreshing the IOC container, the refresh() method is executed. In the step of invokeBeanFactoryPostProcessors(beanFactory), the post processor of BeanDefinitionRegistryPostProcessor will be executed When the enhancement method is executed, loadBeanDefinitions() is executed to load the bean definition information, and the AutoConfigurationPackages.Registrar class will be scanned, and then the registerBeanDefinitions() method in AutoConfigurationPackages.Registrar will be called back:

register(registry, new PackageImport(metadata).getPackageName());

// 注册自动包规则的bean定义信息
public static void register(BeanDefinitionRegistry registry, String... packageNames) { // sample.tomcat
    // 判断bean定义信息注册表中是否存在org.springframework.boot.autoconfigure.AutoConfigurationPackages
    if (registry.containsBeanDefinition(BEAN)) {
        // 如果该bean已经注册,则将要注册包名称添加进去
        BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
        ConstructorArgumentValues constructorArguments = beanDefinition
                .getConstructorArgumentValues();
        constructorArguments.addIndexedArgumentValue(0,
                addBasePackages(constructorArguments, packageNames));
    }
    else {  // 如果该bean尚未注册,则注册该bean,参数中提供的包名称会被设置到bean定义中去
        // 创建简单类型的bean定义信息GenericBeanDefinition
        GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
        // bean类 : AutoConfigurationPackages$BasePackages
        beanDefinition.setBeanClass(BasePackages.class);
        // bean实例构造函数第一个参数 : 被使用类所在包名 (比如@SpringBootApplication注解所在类的包名)
        beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0,
                packageNames);
        // bean 角色 : 基础设施
        beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
        // 注册bean定义信息, bean名称 : org.springframework.boot.autoconfigure.AutoConfigurationPackages
        registry.registerBeanDefinition(BEAN, beanDefinition);
    }
}

In this example, SampleTomcatApplication is in the sample.tomcat package, so the package is automatically configured as sample.tomcat.

What is the difference between the role of @AutoConfigurationPackage and @ComponentScan?

  • @AutoConfigurationPackage : It is for component developers who want to load multiple auto-configured classes under one path. This annotation does not need to add @Import to each class separately. It is more convenient to directly import the package path.
  • @ComponentScan : It is prepared for component developers, so that you can customize the package path. For example, if some of your beans and SpringBootApplication are not under the same path, or under multiple different paths, this will work.

Taking the example above, in fact @AutoConfigurationPackage is equivalent to adding @ComponentScan(basePackages = "sample.tomcat") to the startup class.

三、@Import(AutoConfigurationImportSelector.class)

1), Introduction of AutoConfigurationImportSelector

The @EnableAutoConfiguration annotation imports the AutoConfigurationImportSelector component into the container through @Import(AutoConfigurationImportSelector.class), which implements the DeferredImportSelector, indirectly implements the ImportSelector interface, rewrites the selectImports() method, and helps us return all classes that need to be registered as beans An array collection of fully qualified class names.

public class AutoConfigurationImportSelector
    implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware,
    BeanFactoryAware, EnvironmentAware, Ordered {
        //...
    }

Let's first look at the class diagram of AutoConfigurationImportSelector:

AutoConfigurationImportSelector implements the DeferredImportSelector interface instead of the ImportSelector interface, and the execution time of the two is different:

  • 1. The execution timing of the selectImports method of the ImportSelector instance is before other logic in the @Configuration annotation is processed . The so-called other logic includes the processing of annotations such as @ImportResource and @Bean;
  • 2. The execution timing of the selectImports method of the DeferredImportSelector instance is after the other logic in the @Configuration annotation is processed . The so-called other logic includes the processing of @ImportResource and @Bean annotations;

2), automatic configuration process

As mentioned earlier, AutoConfigurationImportSelector implements the DeferredImportSelector interface, so let's see where it works.

Back to the main() method of the startup class: 

SpringApplication.run(SampleTomcatApplication.class, args);

Execute the run() method:

// 运行 Spring 应用程序,创建并刷新一个新的ApplicationContext
public ConfigurableApplicationContext run(String... args) {
    // 创建一个任务执行观察器,用于统计run启动过程花了多少时间
    StopWatch stopWatch = new StopWatch();
    // 记录开始时间
    stopWatch.start();
    ConfigurableApplicationContext context = null;
    // exceptionReporters集合用来存储异常报告器,用来报告SpringBoot启动过程的异常
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    // 设置了一个名为java.awt.headless的系统属性, 其实是想设置该应用程序,即使没有检测到显示器,也允许其启动. 对于服务器来说,是不需要显示器的,所以要这样设置.
    configureHeadlessProperty();
    // 从spring.factories配置文件中加载到EventPublishingRunListener对象并赋值给SpringApplicationRunListeners
    // EventPublishingRunListener对象主要用来发布SpringBoot启动过程中内置的一些生命周期事件,标志每个不同启动阶段
    SpringApplicationRunListeners listeners = getRunListeners(args);
    // 发布启动事件
    listeners.starting();
    try {
        // 创建ApplicationArguments对象,封装了args参数
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        // 准备环境,包括系统变量、环境变量、命令行参数、默认变量等
        ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
        // 配置需要忽略的BeanInfo信息
        configureIgnoreBeanInfo(environment);
        // 启动时控制台打印Banner
        Banner printedBanner = printBanner(environment);
        // 根据不同类型创建不同类型的spring容器ApplicationContext应用程序上下文
        context = createApplicationContext();
        // 加载spring.factories配置文件配置的异常报告器
        exceptionReporters = getSpringFactoriesInstances(
                SpringBootExceptionReporter.class,
                new Class[] { ConfigurableApplicationContext.class }, context);
        // 准备上下文,刷新容器前的一些操作
        prepareContext(context, environment, listeners, applicationArguments, printedBanner);
        // 刷新应用上下文。完成Spring IOC容器的初始化
        refreshContext(context);
        // 在刷新上下文后调用的钩子,这个方法是一个模板方法
        afterRefresh(context, applicationArguments);
        // 停止记录执行时间
        stopWatch.stop();
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass)
                    .logStarted(getApplicationLog(), stopWatch);
        }
        // 事件广播,启动完成了
        listeners.started(context);
        // 执行ApplicationRunner、CommandLineRunner的run方法,实现spring容器启动成功后需要执行的一些逻辑
        callRunners(context, applicationArguments);
    } catch (Throwable ex) {
        handleRunFailure(context, ex, exceptionReporters, listeners);
        throw new IllegalStateException(ex);
    }

    try {
        listeners.running(context);
    } catch (Throwable ex) {
        handleRunFailure(context, ex, exceptionReporters, null);
        throw new IllegalStateException(ex);
    }
    return context;
}

The above code is the core process of springboot application startup. We focus on refreshContext (context), which is to refresh Spring's IOC container method, and automatic configuration is implemented in it.

The refreshContext() method actually calls org.springframework.context.support.AbstractApplicationContext#refresh, which is the core method of IOC container refresh and mainly includes 12 steps:

  • a. prepareRefresh(): Some preprocessing work before the container is refreshed;
  • b. obtainFreshBeanFactory(): Create a DefaultListableBeanFactory factory, set some properties for the bean factory, load configuration file information, and encapsulate it into bean definition information;
  • c. prepareBeanFactory(beanFactory): Set some properties of the bean factory, such as adding some BeanPostProcessor enhancers, etc.
  • d. postProcessBeanFactory(beanFactory): template method, reserved for subclass extension implementation;
  • e. invokeBeanFactoryPostProcessors(beanFactory): Execute the postProcessBeanFactory() enhancement method of BeanFactoryPostProcessor;
  • f. registerBeanPostProcessors(beanFactory): Register the BeanPostProcessor enhancer, note that this is just registration, and it is actually executed before and after the initialization phase;
  • g. initMessageSource(): initialize MessageSource, internationalization processing;
  • h. initApplicationEventMulticaster(): Initialize the event multicaster;
  • i. onRefresh(): Template method, reserved for subclass extension implementation;
  • j. registerListeners(): Register some listeners;
  • k. finishBeanFactoryInitialization(beanFactory): complete the instantiation of non-lazy-loaded singleton bean objects, including reflection creation of bean objects, attribute filling, processing of circular dependencies, bean initialization, etc.;
  • l. finishRefresh(): Some processing work after the container is refreshed;

Focus on the fifth step above: invokeBeanFactoryPostProcessors(beanFactory): execute the postProcessBeanFactory() enhancement method of BeanFactoryPostProcessor; this step will execute the invokeBeanDefinitionRegistryPostProcessors() method, and then execute the postProcessBeanDefinitionRegistry() enhancement method of the BeanDefinitionRegistryPostProcessor post processor .

It includes the processing of ConfigurationClassPostProcessor , loading bean definition information, and ConfigurationClassParser parsing classes related to automatic configuration.

The call stack is as follows:

org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(ConfigurationClassPostProcessor.java:232)
	org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(PostProcessorRegistrationDelegate.java:275)
		org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:95)
			org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:691)
				org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:528)

BeanDefinitionRegistryPostProcessor mainly completes the processing of @Configuration annotations. The class diagram is as follows:

Let's look at the source code of the ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry method: 

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

Parse the bean definition information through the ConfigurationClassParser#parse() method in processConfigBeanDefinitions (registry):

Check out the ConfigurationClassParser#parse() method:

The parse() method mainly includes two steps:

2-1)、ConfigurationClassParser#parse()

Mainly call ConfigurationClassParser#processConfigurationClass to complete the parsing configuration class:

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

The actual parsing of the configuration class is in the ConfigurationClassParser#doProcessConfigurationClass() method, which involves parsing many annotations related to automatic configuration, such as @PropertySource, @ComponentScan, @Import, @ImportResource, etc. 

protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
        throws IOException {

    if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
        // Recursively process any member (nested) classes first
        processMemberClasses(configClass, sourceClass);
    }

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

    // Process any @ComponentScan annotations
    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());
                }
            }
        }
    }

    // Process any @Import annotations
    processImports(configClass, sourceClass, getImports(sourceClass), 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);
        }
    }

    // Process individual @Bean methods
    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;
}

The @Import of the automatic configuration annotation is resolved here. Check out ConfigurationClassParser#processImports() to see how the @Import annotation is parsed:

Looking at the source code, we can see that if @Import imports the DeferredImportSelector type, the selector will be saved to the deferredImportSelectorHandler first, and the selectImports() method of the ImportSelector will not be executed immediately, and will be executed after all the annotations are parsed; instead of DeferredImportSelector type, directly call the selectImports() method of ImportSelector, and then inject the corresponding Bean object;

After the org.springframework.context.annotation.ConfigurationClassParser#parse(java.util.Set<org.springframework.beans.factory.config.BeanDefinitionHolder>) method parses the annotation bean definition information, the bean import of the delayed ImportSelector will be executed, that is Call this.deferredImportSelectorHandler.process() .

This also confirms that the execution timing of the DeferredImportSelector implemented by AutoConfigurationImportSelector is after the other logic in the @Configuration annotation is processed, including the processing of annotations such as @ImportResource and @Bean.

2-2)、this.deferredImportSelectorHandler.process()

Back to the last line of the org.springframework.context.annotation.ConfigurationClassParser#parse(java.util.Set<org.springframework.beans.factory.config.BeanDefinitionHolder>) method: 

this.deferredImportSelectorHandler.process();
public void process() {
    // 获取到上一步暂存在deferredImportSelectors中的ImportSelector
    List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
    this.deferredImportSelectors = null;
    try {
        if (deferredImports != null) {
            // 创建延迟ImportSelector的分组处理器,内部是分组的
            DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
            // 排序
            deferredImports.sort(DEFERRED_IMPORT_COMPARATOR);
            // 第一步:register()
            deferredImports.forEach(handler::register);
            // 第二步:processGroupImports()
            handler.processGroupImports();
        }
    }
    finally {
        this.deferredImportSelectors = new ArrayList<>();
    }
}

Two important steps are analyzed in detail below.

2-2-1), the first step: register()

The main function of the register() method is to register groups, and DeferredImportSelectorGroupingHandler is grouped according to Group to distinguish different ImportSelectors.

  • 1. groupings: key: group type (here AutoConfigurationGroup) value: group;
  • 2. configurationClasses: key: annotation attribute of configuration class value: configuration class information;

2-2-2), the second step: processGroupImports()

看下grouping.getImports()。ConfigurationClassParser.DeferredImportSelectorGrouping#getImports(): 

public Iterable<Group.Entry> getImports() {
    for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
        this.group.process(deferredImport.getConfigurationClass().getMetadata(),
                deferredImport.getImportSelector());
    }
    return this.group.selectImports();
}

getImports() processing consists of two steps:

2-2-2-1), process() method

AutoConfigurationImportSelector internally defines AutoConfigurationGroup, which implements the DeferredImportSelector.Group interface, and rewrites the process() and selectImports() methods.

So, what will be executed here is the rewritten AutoConfigurationImportSelector.AutoConfigurationGroup#process() method: 

// 这里用来处理自动配置类,比如过滤掉不符合匹配条件的自动配置类
@Override
public void process(AnnotationMetadata annotationMetadata,
        DeferredImportSelector deferredImportSelector) {
    Assert.state(
            deferredImportSelector instanceof AutoConfigurationImportSelector,
            () -> String.format("Only %s implementations are supported, got %s",
                    AutoConfigurationImportSelector.class.getSimpleName(),
                    deferredImportSelector.getClass().getName()));
    // 自动装配的方法入口

    // 调用getAutoConfigurationEntry()方法得到自动配置类并封装成autoConfigurationEntry对象
    AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
            .getAutoConfigurationEntry(getAutoConfigurationMetadata(),
                    annotationMetadata);
    // 将封装了自动配置类的autoConfigurationEntry对象装进autoConfigurationEntries集合
    this.autoConfigurationEntries.add(autoConfigurationEntry);
    // 遍历所有自动配置类
    for (String importClassName : autoConfigurationEntry.getConfigurations()) {
        // 将符合条件的自动配置类存入到map中:Map<String, AnnotationMetadata> entries
        this.entries.putIfAbsent(importClassName, annotationMetadata);
    }
}

The AutoConfigurationImportSelector#getAutoConfigurationEntry() method is used to obtain qualified auto-configuration classes and avoid loading unnecessary auto-configuration classes and causing memory waste.

// 获取符合条件的自动配置类,避免加载不必要的自动配置类从而造成内存浪费
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
    // 再次判断是否开启自动配置功能,判断是否有配置spring.boot.enableautoconfiguration属性,默认为true。
    if (!isEnabled(annotationMetadata)) {
        // 未开启自动配置,直接返回{}
        return EMPTY_ENTRY;
    }
    // 获取@EnableAutoConfiguration注解的 exclude 和 excludeName 属性
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
    // 获取META-INF/spring.factories文件配置的所有自动配置类,实际上是通过SpringFactoriesLoader.loadFactoryNames()获取
    List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
    // 删除重复的自动配置类,实际上就是将List包装成LinkedHashSet去重
    configurations = removeDuplicates(configurations);

    // 获取到需要排除自动装配的类、类名称,以及配置文件中(属性名:spring.autoconfigure.exclude)配置的类
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);

    // 检查configurations中哪些是需要排除的
    checkExcludedClasses(configurations, exclusions);

    // 真正从候选自动配置类集合中移除掉需要排除的那些配置类
    configurations.removeAll(exclusions);

    // 执行AutoConfigurationImportFilter过滤器match方法判断是否符合@ConditionalOnBean,@ConditionalOnClass或@ConditionalOnWebApplication,循环所有的候选的自动配置组件,判断有没有哪些组件不符合条件的,将它们排除掉
    configurations = filter(configurations, autoConfigurationMetadata);

    // 获取了符合条件的自动配置类后,触发AutoConfigurationImportEvent事件,目的是告诉ConditionEvaluationReport条件评估报告器对象来记录符合条件的自动配置类
    fireAutoConfigurationImportEvents(configurations, exclusions);
    // 最终的返回的结果,封装成AutoConfigurationEntry
    return new AutoConfigurationEntry(configurations, exclusions);
}

AutoConfigurationImportSelector#getCandidateConfigurations Get candidate auto-configuration classes. 

In fact, the full class name of the automatic configuration class corresponding to EnableAutoConfiguration in the "META-INF/spring.factories" file is loaded through the class loader.

public Iterable<Entry> selectImports() {
    if (this.autoConfigurationEntries.isEmpty()) {
        return Collections.emptyList();
    }
    // 获取所有要排除的自动配置类
    Set<String> allExclusions = this.autoConfigurationEntries.stream()
            .map(AutoConfigurationEntry::getExclusions)
            .flatMap(Collection::stream).collect(Collectors.toSet());
    // 获取经过滤后所有符合条件的自动配置类
    Set<String> processedConfigurations = this.autoConfigurationEntries.stream()
            .map(AutoConfigurationEntry::getConfigurations)
            .flatMap(Collection::stream)
            .collect(Collectors.toCollection(LinkedHashSet::new));
    // 删除需要排除的那些自动配置类
    processedConfigurations.removeAll(allExclusions);

    // 自动配置类排序
    return sortAutoConfigurations(processedConfigurations,
            getAutoConfigurationMetadata())
                    .stream()
                    .map((importClassName) -> new Entry(
                            this.entries.get(importClassName), importClassName))
                    .collect(Collectors.toList());
}

After getting the full class name of the automatically configured class, execute selectImports().

2-2-2-2), selectImports() method 

Get all the automatic configuration classes to be excluded, then delete those automatic configuration classes that do not meet the conditions, and finally sort them.

At this point, the execution of the getImports() method is completed, and then iterates through the fully qualified class names of all obtained automatic configuration classes, and calls the processImports() method one by one to inject beans.

Let's go back to the org.springframework.context.annotation.ConfigurationClassParser#processImports() method:

Call the addImportBeanDefinitionRegistrar() method to register the bean definition to importBeanDefinitionRegistrars, and finally register the corresponding bean through the ImportBeanDefinitionRegistrar#registerBeanDefinitions() method.

Let's look at the following code of org.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitions. The parse() method has been analyzed before, and the definition information of the automatically configured bean has been obtained. Next, it will be executed: this.reader.loadBeanDefinitions(configClasses) ; Load the bean definition information and register the bean into the spring container.

At this point, the analysis of SpringBoot's automatic configuration principle is over.

Guess you like

Origin blog.csdn.net/Weixiaohuai/article/details/128854079