jjzjj

3.【SpringBoot源码】SpringBoot自动配置原理

每天都要进步一点点 2024-03-26 原文

目录

一、简介

1)、@SpringBootConfiguration

2)、@ComponentScan

3)、@EnableAutoConfiguration

二、@AutoConfigurationPackage

三、@Import(AutoConfigurationImportSelector.class)

1)、AutoConfigurationImportSelector介绍

2)、自动配置流程

2-1)、ConfigurationClassParser#parse()

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

2-2-1)、第一步:register()

2-2-2)、第二步:processGroupImports()


本篇源码基于spring-boot-2.1.0.RELEASE版本进行分析,各个版本可能存在一些差别。

一、简介

SpringBoot的启动从main()方法开始:

@SpringBootApplication
public class SampleTomcatApplication {

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

}

在主启动类上,我们只需要加上@SpringBootApplication注解,就能成功启动springboot应用了,那么我们就从注解入手,看下它是如何帮我们配置好所需要的一些环境的。

@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 使用三个注解修饰:

1)、@SpringBootConfiguration

标记启动类是一个Spring的配置类。

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

}

2)、@ComponentScan

扫描启动类所在的包以及子包所有Bean组件并注册到IOC容器中。

3)、@EnableAutoConfiguration

实现自动装配的核心注解,内部包含两个注解:@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 {};

}

springboot的自动装配就是依靠@EnableAutoConfiguration这个注解实现的,而@EnableAutoConfiguration又是由下面两部分组成:

1、@AutoConfigurationPackage

2、@Import(AutoConfigurationImportSelector.class)

二、@AutoConfigurationPackage

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

}

可以看到,@AutoConfigurationPackage注解的功能是由@Import注解实现的,它是Spring框架的底层注解,它的作用就是给容器中导入某个组件类AutoConfigurationPackages.Registrar,我们看下这是个什么组件?

@AutoConfigurationPackage注解的作用是将添加该注解的类所在的package作为自动配置package进行管理。也就是说当SpringBoot应用启动时默认会将启动类所在的package作为自动配置的package,这样主程序类所在包及所有子包下的组件就会被扫描到spring容器中。 

// 实现了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实现了ImportBeanDefinitionRegistrar,并重写了registerBeanDefinitions()注册bean定义信息的方法,这样在刷新IOC容器,即执行refresh()方法,在invokeBeanFactoryPostProcessors(beanFactory)这一步,会执行BeanDefinitionRegistryPostProcessor这个后置处理器的增强方法时,执行loadBeanDefinitions()加载bean定义信息,就会扫描到AutoConfigurationPackages.Registrar这个类,然后就会回调AutoConfigurationPackages.Registrar中的registerBeanDefinitions()方法:

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

在本例中,SampleTomcatApplication在sample.tomcat包中,所以自动配置package为sample.tomcat。

@AutoConfigurationPackage和@ComponentScan的作用有什么区别?

  • @AutoConfigurationPackage :是为了给组件开发者使用的,组件开发者在一个路径下面有多个自动配置的类想加载,这个注解就不用对每个类单独添加 @Import 了,直接引入包路径更方便。
  • @ComponentScan :是为了用组件的开发者准备的,方便你对包路径进行自定义,比如你的一些 Bean 跟 SpringBootApplication 不在一个路径下面,或者多个不同路径下面,这个就起到作用了。

以上面的示例来说,其实@AutoConfigurationPackage就相当于在启动类加上@ComponentScan(basePackages = "sample.tomcat")。

三、@Import(AutoConfigurationImportSelector.class)

1)、AutoConfigurationImportSelector介绍

@EnableAutoConfiguration注解通过@Import(AutoConfigurationImportSelector.class)向容器中导入了AutoConfigurationImportSelector组件,它实现了DeferredImportSelector,并间接实现了ImportSelector接口,重写了selectImports()方法,帮助我们返回所有需要被注册为bean的类全限定类名的数组集合。

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

我们先来看下AutoConfigurationImportSelector的类图:

AutoConfigurationImportSelector实现的是DeferredImportSelector接口,而不是ImportSelector接口,两者的执行时间是不一样的:

  • 1、ImportSelector实例的selectImports方法的执行时机,是在@Configguration注解中的其他逻辑被处理之前,所谓的其他逻辑,包括对@ImportResource、@Bean这些注解的处理;
  • 2、DeferredImportSelector实例的selectImports方法的执行时机,是在@Configguration注解中的其他逻辑被处理完毕之后,所谓的其他逻辑,包括对@ImportResource、@Bean这些注解的处理;

2)、自动配置流程

前面介绍到,AutoConfigurationImportSelector实现的是DeferredImportSelector接口,那么下面我们来看看它在哪里发挥作用的。

回到启动类的main()方法: 

SpringApplication.run(SampleTomcatApplication.class, args);

执行run()方法:

// 运行 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;
}

上述代码是springboot应用启动的核心流程,我们重点关注refreshContext(context),也就是刷新Spring的IOC容器方法,自动配置就是在里面实现的。

refreshContext()方法实际上是调用org.springframework.context.support.AbstractApplicationContext#refresh,这个方法是IOC容器刷新的核心方法,主要包括12个步骤:

  • a、prepareRefresh():容器刷新前的一些预处理工作;
  • b、obtainFreshBeanFactory():创建DefaultListableBeanFactory工厂,给bean工厂设置一些属性,加载配置文件信息,封装成bean定义信息;
  • c、prepareBeanFactory(beanFactory):设置bean工厂的一些属性,如添加一些BeanPostProcessor增强器等
  • d、postProcessBeanFactory(beanFactory):模板方法,留给子类扩展实现;
  • e、invokeBeanFactoryPostProcessors(beanFactory):执行BeanFactoryPostProcessor的postProcessBeanFactory()增强方法;
  • f、registerBeanPostProcessors(beanFactory):注册BeanPostProcessor增强器,注意这里只是注册,真正是在初始化阶段的前后执行;
  • g、initMessageSource():初始化MessageSource,国际化处理;
  • h、initApplicationEventMulticaster():初始化事件多播器;
  • i、onRefresh():模板方法,留给子类扩展实现;
  • j、registerListeners():注册一些监听器;
  • k、finishBeanFactoryInitialization(beanFactory):完成非懒加载的单例bean对象的实例化,包括反射创建bean对象、属性填充、循环依赖的处理、bean的初始化等等;
  • l、finishRefresh():容器刷新完成之后的一些处理工作;

重点关注上述的第五步:invokeBeanFactoryPostProcessors(beanFactory):执行BeanFactoryPostProcessor的postProcessBeanFactory()增强方法;这一步会执行invokeBeanDefinitionRegistryPostProcessors()方法,然后执行BeanDefinitionRegistryPostProcessor后置处理器的postProcessBeanDefinitionRegistry()增强方法。

其中就包含了ConfigurationClassPostProcessor的处理,加载bean定义信息、ConfigurationClassParser解析自动配置相关的类。

调用栈如下:

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主要完成对@Configuration注解的处理,类图如下:

我们查看ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry方法的源码: 

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

在processConfigBeanDefinitions(registry)中通过ConfigurationClassParser#parse()方法解析bean定义信息:

查看ConfigurationClassParser#parse()方法:

parse()方法主要包括两个步骤:

2-1)、ConfigurationClassParser#parse()

主要是调用ConfigurationClassParser#processConfigurationClass来完成解析配置类:

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

真正解析配置类是在ConfigurationClassParser#doProcessConfigurationClass()方法,这里涉及解析很多自动配置相关的注解,如@PropertySource、@ComponentScan、@Import、@ImportResource等。 

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

自动配置注解的@Import就是在这里解析的。查看ConfigurationClassParser#processImports(),看下具体是如何解析@Import注解的:

查看源码可知,如果@Import导入的是DeferredImportSelector类型,则会将selector先保存到deferredImportSelectorHandler中,并不会马上执行ImportSelector的selectImports()方法,等到所有的注解都解析完成后,再执行;而非DeferredImportSelector类型,则直接调用ImportSelector的selectImports()方法,然后注入对应的Bean对象;

org.springframework.context.annotation.ConfigurationClassParser#parse(java.util.Set<org.springframework.beans.factory.config.BeanDefinitionHolder>)方法解析完注解bean定义信息后,才会执行延迟ImportSelector的bean导入,即调用this.deferredImportSelectorHandler.process()

这里也印证了AutoConfigurationImportSelector实现的DeferredImportSelector的执行时机,是在@Configguration注解中的其他逻辑被处理完毕之后,包括对@ImportResource、@Bean这些注解的处理。

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

回到org.springframework.context.annotation.ConfigurationClassParser#parse(java.util.Set<org.springframework.beans.factory.config.BeanDefinitionHolder>)方法的最后一行: 

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

下面详细分析两个重要步骤。

2-2-1)、第一步:register()

register()方法主要作用是注册分组,DeferredImportSelectorGroupingHandler根据Group来分组,用于区分不同的ImportSelector。

  • 1、groupings: key:组类型(在这里 AutoConfigurationGroup) value:组;
  • 2、configurationClasses:key:配置类的注解属性 value:配置类信息;

2-2-2)、第二步: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()处理包括两步:

2-2-2-1)、process()方法

AutoConfigurationImportSelector内部定义了AutoConfigurationGroup,它实现了DeferredImportSelector.Group接口,并重写了process()和selectImports()方法。

所以,这里就会执行的是重写后的AutoConfigurationImportSelector.AutoConfigurationGroup#process()方法: 

// 这里用来处理自动配置类,比如过滤掉不符合匹配条件的自动配置类
@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);
    }
}

AutoConfigurationImportSelector#getAutoConfigurationEntry()方法,获取符合条件的自动配置类,避免加载不必要的自动配置类从而造成内存浪费。

// 获取符合条件的自动配置类,避免加载不必要的自动配置类从而造成内存浪费
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 获取候选的自动配置类。 

实际上是通过类加载器加载"META-INF/spring.factories"文件中EnableAutoConfiguration对应的自动配置类全类名。

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

自动配置的类全类名拿到后,执行selectImports()。

2-2-2-2)、selectImports()方法 

获取所有要排除的自动配置类,然后删除不符合条件的那些自动配置类,最后做了一下排序。

至此,getImports()方法执行结束,然后遍历所有获取到的自动配置类的全限定类名,挨个调用processImports()方法注入bean。

我们回到org.springframework.context.annotation.ConfigurationClassParser#processImports()方法:

调用addImportBeanDefinitionRegistrar()方法将bean定义注册到importBeanDefinitionRegistrars中,最后通过ImportBeanDefinitionRegistrar#registerBeanDefinitions()方法注册对应的bean。

我们看org.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitions的如下代码,parse()方法前面已经分析完成了,获取到了自动配置的bean的定义信息,接下来会执行:this.reader.loadBeanDefinitions(configClasses);加载bean定义信息,注册bean到spring容器中。

到这里,SpringBoot的自动配置原理就分析完了。

有关3.【SpringBoot源码】SpringBoot自动配置原理的更多相关文章

  1. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  2. ruby-on-rails - 独立 ruby​​ 脚本的配置文件 - 2

    我有一个在Linux服务器上运行的ruby​​脚本。它不使用rails或任何东西。它基本上是一个命令行ruby​​脚本,可以像这样传递参数:./ruby_script.rbarg1arg2如何将参数抽象到配置文件(例如yaml文件或其他文件)中?您能否举例说明如何做到这一点?提前谢谢你。 最佳答案 首先,您可以运行一个写入YAML配置文件的独立脚本:require"yaml"File.write("path_to_yaml_file",[arg1,arg2].to_yaml)然后,在您的应用中阅读它:require"yaml"arg

  3. ruby - RuntimeError(自动加载常量 Apps 多线程时检测到循环依赖 - 2

    我收到这个错误:RuntimeError(自动加载常量Apps时检测到循环依赖当我使用多线程时。下面是我的代码。为什么会这样?我尝试多线程的原因是因为我正在编写一个HTML抓取应用程序。对Nokogiri::HTML(open())的调用是一个同步阻塞调用,需要1秒才能返回,我有100,000多个页面要访问,所以我试图运行多个线程来解决这个问题。有更好的方法吗?classToolsController0)app.website=array.join(',')putsapp.websiteelseapp.website="NONE"endapp.saveapps=Apps.order("

  4. Ruby Sinatra 配置用于生产和开发 - 2

    我已经在Sinatra上创建了应用程序,它代表了一个简单的API。我想在生产和开发上进行部署。我想在部署时选择,是开发还是生产,一些方法的逻辑应该改变,这取决于部署类型。是否有任何想法,如何完成以及解决此问题的一些示例。例子:我有代码get'/api/test'doreturn"Itisdev"end但是在部署到生产环境之后我想在运行/api/test之后看到ItisPROD如何实现? 最佳答案 根据SinatraDocumentation:EnvironmentscanbesetthroughtheRACK_ENVenvironm

  5. UE4 源码阅读:从引擎启动到Receive Begin Play - 2

    一、引擎主循环UE版本:4.27一、引擎主循环的位置:Launch.cpp:GuardedMain函数二、、GuardedMain函数执行逻辑:1、EnginePreInit:加载大多数模块int32ErrorLevel=EnginePreInit(CmdLine);PreInit模块加载顺序:模块加载过程:(1)注册模块中定义的UObject,同时为每个类构造一个类默认对象(CDO,记录类的默认状态,作为模板用于子类实例创建)(2)调用模块的StartUpModule方法2、FEngineLoop::Init()1、检查Engine的配置文件找出使用了哪一个GameEngine类(UGame

  6. Vscode+Cmake配置并运行opencv环境(Windows和Ubuntu大同小异) - 2

    之前在培训新生的时候,windows环境下配置opencv环境一直教的都是网上主流的vsstudio配置属性表,但是这个似乎对新生来说难度略高(虽然个人觉得完全是他们自己的问题),加之暑假之后对cmake实在是爱不释手,且这样配置确实十分简单(其实都不需要配置),故斗胆妄言vscode下配置CV之法。其实极为简单,图比较多所以很长。如果你看此文还配不好,你应该思考一下是不是自己的问题。闲话少说,直接开始。0.CMkae简介有的人到大二了都不知道cmake是什么,我不说是谁。CMake是一个开源免费并且跨平台的构建工具,可以用简单的语句来描述所有平台的编译过程。它能够根据当前所在平台输出对应的m

  7. ruby-on-rails - 从应用程序中自定义文件夹内的命名空间自动加载 - 2

    我们目前正在为ROR3.2开发自定义cms引擎。在这个过程中,我们希望成为我们的rails应用程序中的一等公民的几个类类型起源,这意味着它们应该驻留在应用程序的app文件夹下,它是插件。目前我们有以下类型:数据源数据类型查看我在app文件夹下创建了多个目录来保存这些:应用/数据源应用/数据类型应用/View更多类型将随之而来,我有点担心应用程序文件夹被这么多目录污染。因此,我想将它们移动到一个子目录/模块中,该子目录/模块包含cms定义的所有类型。所有类都应位于MyCms命名空间内,目录布局应如下所示:应用程序/my_cms/data_source应用程序/my_cms/data_ty

  8. 神州数码无线产品(AC+AP)配置 - 2

    注意:本文主要掌握DCN自研无线产品的基本配置方法和注意事项,能够进行一般的项目实施、调试与运维AP基本配置命令AP登录用户名和密码均为:adminAP默认IP地址为:192.168.1.10AP默认情况下DHCP开启AP静态地址配置:setmanagementstatic-ip192.168.10.1AP开启/关闭DHCP功能:setmanagementdhcp-statusup/downAP设置默认网关:setstatic-ip-routegeteway192.168.10.254查看AP基本信息:getsystemgetmanagementgetmanaged-apgetrouteAP配

  9. hadoop安装之保姆级教程(二)之YARN的配置 - 2

    1.1.1 YARN的介绍 为克服Hadoop1.0中HDFS和MapReduce存在的各种问题⽽提出的,针对Hadoop1.0中的MapReduce在扩展性和多框架⽀持⽅⾯的不⾜,提出了全新的资源管理框架YARN. ApacheYARN(YetanotherResourceNegotiator的缩写)是Hadoop集群的资源管理系统,负责为计算程序提供服务器计算资源,相当于⼀个分布式的操作系统平台,⽽MapReduce等计算程序则相当于运⾏于操作系统之上的应⽤程序。 YARN被引⼊Hadoop2,最初是为了改善MapReduce的实现,但是因为具有⾜够的通⽤性,同样可以⽀持其他的分布式计算模

  10. Ruby 默认将 IRB 配置为 Pretty_Inspect - 2

    我是ruby​​的新手,正在配置IRB。我喜欢pretty-print(需要'pp'),但总是输入pp来漂亮地打印它似乎很麻烦。我想做的是默认情况下让它漂亮地打印出来,所以如果我有一个var,比如说,'myvar',然后键入myvar,它会自动调用pretty_inspect而不是常规检查。我从哪里开始?理想情况下,我将能够向我的.irbrc文件添加一个自动调用的方法。有什么想法吗?谢谢! 最佳答案 irb中默认pretty-print对象正是hirb被迫去做。Theseposts解释hirb如何将几乎所有内容转换为ascii表。虽

随机推荐