Spring Boot源码剖析之Spring Boot源码剖析

Spring Boot 源码剖析

Spring Boot依赖管理

问题:(1)为什么导入dependency时不需要指定版本?
Spring Boot项目的父项目依赖spring-boot-starter-parent

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.5.1</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

spring-boot-starter-parent中定义了jdk版本,源文件编码方式,Maven打包编译版本等。

<properties>
  <java.version>1.8</java.version>
  <resource.delimiter>@</resource.delimiter>
  <maven.compiler.source>${java.version}</maven.compiler.source>
  <maven.compiler.target>${java.version}</maven.compiler.target>
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>

并且在build标签中定义了resource资源和pluginManagement

<resources>
  <resource>
    <directory>${basedir}/src/main/resources</directory>
    <filtering>true</filtering>
    <includes>
      <include>**/application*.yml</include>
      <include>**/application*.yaml</include>
      <include>**/application*.properties</include>
    </includes>
  </resource>
  <resource>
    <directory>${basedir}/src/main/resources</directory>
    <excludes>
      <exclude>**/application*.yml</exclude>
      <exclude>**/application*.yaml</exclude>
      <exclude>**/application*.properties</exclude>
    </excludes>
  </resource>
</resources>

里面定义了资源过滤,针对 application 的 yml 、 properties 格式进行了过滤,可以支持不同环境的配置,比如 application-dev.yml 、 application-test.yml 、 application-dev.properties 、 application-dev.properties 等等。

spring-boot-starter-parent项目的父项目是spring-boot-dependencies

<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-dependencies</artifactId>
  <version>2.5.1</version>
</parent>

在这个项目中定义了大量的依赖项目的版本

<properties>
  <activemq.version>5.16.2</activemq.version>
  <antlr2.version>2.7.7</antlr2.version>
  <appengine-sdk.version>1.9.89</appengine-sdk.version>
  <artemis.version>2.17.0</artemis.version>
  <aspectj.version>1.9.6</aspectj.version>
  <assertj.version>3.19.0</assertj.version>
  <atomikos.version>4.0.6</atomikos.version>
  <awaitility.version>4.0.3</awaitility.version>
  <build-helper-maven-plugin.version>3.2.0</build-helper-maven-plugin.version>
  <byte-buddy.version>1.10.22</byte-buddy.version>
......
</properties>

spring-boot-dependencies的dependencyManagement节点
在这里,dependencies定义了SpringBoot版本的依赖的组件以及相应版本。

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.apache.activemq</groupId>
      <artifactId>activemq-amqp</artifactId>
      <version>${activemq.version}</version>
    </dependency>
    <dependency>
      <groupId>org.apache.activemq</groupId>
      <artifactId>activemq-blueprint</artifactId>
      <version>${activemq.version}</version>
    </dependency>
    .......
</dependencyManagement>

因为存在这个依赖关系所以在我们创建的Spring Boot项目中部分依赖不需要写版本号。

(2)问题2: spring-boot-starter-parent父依赖启动器的主要作用是进行版本统一管理,那么项目
运行依赖的JAR包是从何而来的?

spring-boot-starter-web
查看spring-boot-starter-web依赖文件源码,核心代码具体如下

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter</artifactId>
  <version>2.5.1</version>
  <scope>compile</scope>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-json</artifactId>
  <version>2.5.1</version>
  <scope>compile</scope>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-tomcat</artifactId>
  <version>2.5.1</version>
  <scope>compile</scope>
</dependency>
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-web</artifactId>
  <version>5.3.8</version>
  <scope>compile</scope>
</dependency>
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-webmvc</artifactId>
  <version>5.3.8</version>
  <scope>compile</scope>
</dependency>

可见在spring-boot-starter-web依赖启动器中打包了web开发所需要的底层所有依赖。

因此在引入spring-boot-starter-web依赖启动器时,就可以实现web场景开发,而不需要额外导入tomcat服务器及其他web依赖文件。

Spring Boot除了提供Web依赖启动器之外,还提供了其他很多依赖启动器。具体可以到官网查找。

自动配置

自动配置:根据我们添加的jar包依赖,Spring Boot会将一些配置类的bean注册进ioc容器,我们可以在需要的地方直接使用。

Spring Boot是如何进行自动配置的,都把哪些组件进行了自动配置?

Spring Boot的启动入口是一个被@SpringBootApplication注解的类的main方法。

@SpringBootApplication

查看@SpringBootApplication源码:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package org.springframework.boot.autoconfigure;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.context.TypeExcludeFilter;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.core.annotation.AliasFor;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration // 标明该类为配置类 
@EnableAutoConfiguration // 启动自动配置功能 
@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
    // 根据class来排除特定的类,使其不能加入spring容器,传入参数value类型是class类型。 
    @AliasFor(
        annotation = EnableAutoConfiguration.class
    )
    Class<?>[] exclude() default {};

    // 根据classname 来排除特定的类,使其不能加入spring容器,传入参数value类型是class的全 类名字符串数组。 
    @AliasFor(
        annotation = EnableAutoConfiguration.class
    )
    String[] excludeName() default {};

    // 指定扫描包,参数是包名的字符串数组。
    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "basePackages"
    )
    String[] scanBasePackages() default {};

    // 扫描特定的包,参数类似是Class类型数组。
    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "basePackageClasses"
    )
    Class<?>[] scanBasePackageClasses() default {};

    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "nameGenerator"
    )
    Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;

    @AliasFor(
        annotation = Configuration.class
    )
    boolean proxyBeanMethods() default true;
}

从源码可以看出,@SpringBootApplication是一个组合注解,前面4个是注解的元数据信息,后面三个注解是核心:@SpringBootConfiguration、@EnableAutoConfiguration、
@ComponentScan

@SpringBootConfiguration

查看源码:


@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@Indexed
public @interface SpringBootConfiguration {
    @AliasFor(
        annotation = Configuration.class
    )
    boolean proxyBeanMethods() default true;
}

从源码看到,它是一个被@Configuration标记的注解,没有其他的代码。说明它其实就是一个@Configuration的包装,重新命名,功能相同。

被@SpringBootConfiguration 标记的类就是一个配置类。

@EnableAutoConfiguration

查看源码:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package org.springframework.boot.autoconfigure;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Import;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
// 自动配置包 
@AutoConfigurationPackage
// Spring的底层注解@Import,给容器中导入一个组件; 
// 导入的组件是AutoConfigurationPackages.Registrar.class
@Import({AutoConfigurationImportSelector.class})
// 告诉SpringBoot开启自动配置功能,这样自动配置才能生效。 
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
    // 返回不会被导入到 Spring 容器中的类
    Class<?>[] exclude() default {};
    // 返回不会被导入到 Spring 容器中的类名 
    String[] excludeName() default {};
}

@EnableAutoConfiguration是一个组合注解:是@AutoConfigurationPackage 和@Import的组合。


image.png

Spring中很多以Enable开头的注解,其作用就是借助@Import来收集并注册特定场景相关的Bean,并加载到IOC容器。

@EnableAutoConfiguration就是借助@Import来收集所有符合自动配置条件的bean定义,并加载到IoC容器。
它引入了AutoConfigurationImportSelector类。

下面我们继续深入到@AutoConfigurationPackage 和@Import这两个注解中去看看它们做了什么工作。

@AutoConfigurationPackage

查看源码:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package org.springframework.boot.autoconfigure;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.boot.autoconfigure.AutoConfigurationPackages.Registrar;
import org.springframework.context.annotation.Import;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({Registrar.class})// 导入Registrar中注册的组件 
public @interface AutoConfigurationPackage {
    String[] basePackages() default {};

    Class<?>[] basePackageClasses() default {};
}

@AutoConfigurationPackage 注解引入Registrar类

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
    Registrar() {
    }

    /**
     *    注册BeanDefinition
     */
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        // 根据注解元信息,拿到要扫描的包,扫描并注册到BeanDefinitionRegistry中
        AutoConfigurationPackages.register(registry, (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0]));
    }

    public Set<Object> determineImports(AnnotationMetadata metadata) {
        return Collections.singleton(new AutoConfigurationPackages.PackageImports(metadata));
    }
}

register方法:

public static void register(BeanDefinitionRegistry registry, String... packageNames) {
    // 第一次进到这个方法的时候private static final String BEAN = AutoConfigurationPackages.class.getName(); 并没有在register中注册。
    if (registry.containsBeanDefinition(BEAN)) {
        AutoConfigurationPackages.BasePackagesBeanDefinition beanDefinition = (AutoConfigurationPackages.BasePackagesBeanDefinition)registry.getBeanDefinition(BEAN);
        beanDefinition.addBasePackages(packageNames);
    } else {
        // 注册BeanDefinition
        registry.registerBeanDefinition(BEAN, new AutoConfigurationPackages.BasePackagesBeanDefinition(packageNames));
    }

}

在register中注册的是AutoConfigurationPackages的内部类BasePackagesBeanDefinition。
BasePackagesBeanDefinition源码:


static final class BasePackagesBeanDefinition extends GenericBeanDefinition {
    private final Set<String> basePackages = new LinkedHashSet();

    BasePackagesBeanDefinition(String... basePackages) {
        // Class对象是BasePackages
        this.setBeanClass(AutoConfigurationPackages.BasePackages.class);
        this.setRole(2);
        // 保存基础包
        this.addBasePackages(basePackages);
    }

    public Supplier<?> getInstanceSupplier() {
        return () -> {
            return new AutoConfigurationPackages.BasePackages(StringUtils.toStringArray(this.basePackages));
        };
    }

    private void addBasePackages(String[] additionalBasePackages) {
        this.basePackages.addAll(Arrays.asList(additionalBasePackages));
    }
}

static final class BasePackages {
    private final List<String> packages;
    private boolean loggedBasePackageInfo;

    BasePackages(String... names) {
        List<String> packages = new ArrayList();
        String[] var3 = names;
        int var4 = names.length;

        for(int var5 = 0; var5 < var4; ++var5) {
            String name = var3[var5];
            if (StringUtils.hasText(name)) {
                packages.add(name);
            }
        }

        this.packages = packages;
    }

    List<String> get() {
        if (!this.loggedBasePackageInfo) {
            if (this.packages.isEmpty()) {
                if (AutoConfigurationPackages.logger.isWarnEnabled()) {
                    AutoConfigurationPackages.logger.warn("@EnableAutoConfiguration was declared on a class in the default package. Automatic @Repository and @Entity scanning is not enabled.");
                }
            } else if (AutoConfigurationPackages.logger.isDebugEnabled()) {
                String packageNames = StringUtils.collectionToCommaDelimitedString(this.packages);
                AutoConfigurationPackages.logger.debug("@EnableAutoConfiguration was declared on a class in the package '" + packageNames + "'. Automatic @Repository and @Entity scanning is enabled.");
            }

            this.loggedBasePackageInfo = true;
        }

        return this.packages;
    }
}

BasePackagesBeanDefinition 继承了GenericBeanDefinition,说明它就是一个Spring的BeanDefinition。只是在这个BeanDefinition中保存的Class并不是AutoConfigurationPackages,而是
BasePackages。

因此@EnableAutoConfiguration中的@AutoConfigurationPackage使用@Import({Registrar.class})注解,向Ioc容器中注册了一个BasePackages的Bean定义。

用图来描述一下整个过程:


image.png

@Import({AutoConfigurationImportSelector.class})

@Import({AutoConfigurationImportSelector.class}) :将
AutoConfigurationImportSelector 这个类导入到 Spring 容器中,
AutoConfigurationImportSelector 可以帮助 Springboot 应用将所有符合条件的 @Configuration 配置都加载到当前 SpringBoot 创建并使用的 IOC 容器( ApplicationContext )中。


image

可以看到 AutoConfigurationImportSelector 重点是实现了 DeferredImportSelector 接口和各种 Aware 接口,然后 DeferredImportSelector 接口又继承了 ImportSelector 接口。
其不光实现了 ImportSelector 接口,还实现了很多其它的 Aware 接口,分别表示在某个时机会被回调。

自动配置实现逻辑的入口方法:

AutoConfigurationImportSelector将会被放到DeferredImportSelectorGrouping中的deferredImports集合中。并且在DeferredImportSelectorGrouping的getImports()方法中进行统一处理。

跟自动配置逻辑相关的入口方法在 DeferredImportSelectorGrouping 类的 getImports 方法处.

public Iterable<Entry> getImports() {
    Iterator var1 = this.deferredImports.iterator();
    // 遍历DeferredImportSelectorGrouping的deferredImports集合。AutoConfigurationImportSelector也在这个集合中。
    while(var1.hasNext()) {
        ConfigurationClassParser.DeferredImportSelectorHolder deferredImport = (ConfigurationClassParser.DeferredImportSelectorHolder)var1.next();
        
        // (1)利用AutoConfigurationGroup的process方法来处理自动配置的相关逻辑,决定导入哪些配置类
        this.group.process(deferredImport.getConfigurationClass().getMetadata(), deferredImport.getImportSelector());
    }
    // (2)经过上面的处理后,然后再进行选择导入哪些配置类
    return this.group.selectImports();
}

其中重要的两个步骤:

  • 利用AutoConfigurationGroup的process方法来处理自动配置的相关逻辑,决定导入哪些配置类
  • 经过上面的处理后,然后再进行选择导入哪些配置类
分析自动配置的主要逻辑

AutoConfigurationImportSelector的process方法:

// 这里用来处理自动配置类,比如过滤掉不符合匹配条件的自动配置类 
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
    Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector, () -> {
        return String.format("Only %s implementations are supported, got %s", AutoConfigurationImportSelector.class.getSimpleName(), deferredImportSelector.getClass().getName());
    });
    // (1)调用getAutoConfigurationEntry方法得到自动配置类放入 autoConfigurationEntry对象中 
    AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector)deferredImportSelector).getAutoConfigurationEntry(annotationMetadata);
    // (2)又将封装了自动配置类的autoConfigurationEntry对象装进autoConfigurationEntries集合 
    this.autoConfigurationEntries.add(autoConfigurationEntry);
    Iterator var4 = autoConfigurationEntry.getConfigurations().iterator();
    // (3)遍历刚获取的自动配置类,放入entries
    while(var4.hasNext()) {
        String importClassName = (String)var4.next();
        // 这里符合条件的自动配置类作为key,annotationMetadata作为值放进entries集合
        this.entries.putIfAbsent(importClassName, annotationMetadata);
    }

}

其中重要的三个步骤:

  • 调用getAutoConfigurationEntry方法得到自动配置类放入 autoConfigurationEntry对象中
  • 又将封装了自动配置类的autoConfigurationEntry对象装进autoConfigurationEntries集合
  • 遍历刚获取的自动配置类,放入entries

获取自动配置类方法getAutoConfigurationEntry:

protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
    if (!this.isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    } else {
        AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
        // (1)得到spring.factories文件配置的所有自动配置类
        List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
        // 利用LinkedHashSet移除重复的配置类 
        configurations = this.removeDuplicates(configurations);
        // 得到要排除的自动配置类,比如注解属性exclude的配置类 
        // 比如:@SpringBootApplication(exclude = FreeMarkerAutoConfiguration.class) 
        // 将会获取到exclude = FreeMarkerAutoConfiguration.class的注解数据
        Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
        // 检查要被排除的配置类,因为有些不是自动配置类,故要抛出异常
        this.checkExcludedClasses(configurations, exclusions);
        // (2)将要排除的配置类移除 
        configurations.removeAll(exclusions);
        // (3)对加载到的自动配置类按需要进行过滤
        configurations = this.getConfigurationClassFilter().filter(configurations);
        // (4)获取了符合条件的自动配置类后,此时触发AutoConfigurationImportEvent事件 
        // 目的是告诉ConditionEvaluationReport条件评估报告器对象来记录符合条件的自动配置类 
        // 该事件什么时候会被触发?--> 在刷新容器时调用invokeBeanFactoryPostProcessors后置处理器时触发
        this.fireAutoConfigurationImportEvents(configurations, exclusions);
        // (5)将符合条件和要排除的自动配置类封装进AutoConfigurationEntry对象,并返回 
        return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
    }
}

其中重要的五个步骤:

  • 得到spring.factories文件配置的所有自动配置类
  • 将要排除的配置类移除
  • 对加载到的自动配置类按需要进行过滤
  • 告诉ConditionEvaluationReport条件评估报告器对象来记录符合条件的自动配置类
  • 将符合条件和要排除的自动配置类封装进AutoConfigurationEntry对象,并返回

getCandidateConfigurations获取配置文件中的自动配置类过程分析
这个方法中重要的方法是loadFactoryNames,这个方法的作用是加载一些组件的名字。

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    // 加载组件名字
    List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
    Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
    return configurations;
}

进入loadFactoryNames方法,它只是做了简单验证,就委托另外一个方法loadSpringFactories 去加载组件名。


public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
    ClassLoader classLoaderToUse = classLoader;
    if (classLoader == null) {
        classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
    }

    String factoryTypeName = factoryType.getName();
    // 真正去加载组件名的方法
    return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}

继续进入loadSpringFactories方法:

private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
    Map<String, List<String>> result = (Map)cache.get(classLoader);
    if (result != null) {
        return result;
    } else {
        HashMap result = new HashMap();

        try {
        
            // 加载类路径下spring.factories文件,将其中设置的配置类的全路径信息封装 为Enumeration类对象
            Enumeration urls = classLoader.getResources("META-INF/spring.factories");

            //循环Enumeration类对象,根据相应的节点信息生成Properties对象,通过传入 的键获取值,在将值切割为一个个小的字符串转化为Array,方法result集合中
            while(urls.hasMoreElements()) {
                URL url = (URL)urls.nextElement();
                UrlResource resource = new UrlResource(url);
                Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                Iterator var6 = properties.entrySet().iterator();

                while(var6.hasNext()) {
                    Entry<?, ?> entry = (Entry)var6.next();
                    String factoryTypeName = ((String)entry.getKey()).trim();
                    String[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                    String[] var10 = factoryImplementationNames;
                    int var11 = factoryImplementationNames.length;

                    for(int var12 = 0; var12 < var11; ++var12) {
                        String factoryImplementationName = var10[var12];
                        ((List)result.computeIfAbsent(factoryTypeName, (key) -> {
                            return new ArrayList();
                        })).add(factoryImplementationName.trim());
                    }
                }
            }

            result.replaceAll((factoryType, implementations) -> {
                return (List)implementations.stream().distinct().collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
            });
            cache.put(classLoader, result);
            return result;
        } catch (IOException var14) {
            throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var14);
        }
    }
}

这个方法的主要作用是遍历整个ClassLoader下所有的jar包下的Spring.factories文件。


image

而在spring-boot-autoconfigure包的META-INF下的spring.factories中保存着springboot的默认提供的自动配置类。

image

我们下面总结下 getAutoConfigurationEntry 方法主要做的事情:
【1】从 spring.factories 配置文件中加载 EnableAutoConfiguration 自动配置类),获取的自动配
置类如图所示。
【2】若 @EnableAutoConfiguration 等注解标有要 exclude 的自动配置类,那么再将这个自动配置类排除掉;
【3】排除掉要 exclude 的自动配置类后,然后再调用 filter 方法进行进一步的过滤,再次排除一些
不符合条件的自动配置类;
【4】经过重重过滤后,此时再触发 AutoConfigurationImportEvent 事件,告诉
ConditionEvaluationReport 条件评估报告器对象来记录符合条件的自动配置类;
【5】 最后再将符合条件的自动配置类返回。

总结了 getAutoConfigurationEntry 方法主要的逻辑后,我们再来细看一下
AutoConfigurationImportSelector 的 filter 方法:

List<String> filter(List<String> configurations) {
    long startTime = System.nanoTime();
    
    // 将从spring.factories中获取的自动配置类转出字符串数组 
    String[] candidates = StringUtils.toStringArray(configurations);
    boolean skipped = false;
    Iterator var6 = this.filters.iterator();

    int i;
    while(var6.hasNext()) {
        AutoConfigurationImportFilter filter = (AutoConfigurationImportFilter)var6.next();
        
        // 判断各种filter来判断每个candidate(这里实质要通过candidate(自动配置类)拿到其标注的 
// @ConditionalOnClass,@ConditionalOnBean和@ConditionalOnWebApplication里面的注解值)是否匹配, 
        boolean[] match = filter.match(candidates, this.autoConfigurationMetadata);

        // 遍历match数组,注意match顺序跟candidates的自动配置类一一对应
        for(i = 0; i < match.length; ++i) {
            // 若有不匹配的话 
            if (!match[i]) {
                candidates[i] = null;
                // 标注skipped为true 
                skipped = true;
            }
        }
    }

    // 这里表示若所有自动配置类经过OnBeanCondition,OnClassCondition和 OnWebApplicationCondition过滤后,全部都匹配的话,则全部原样返回
    if (!skipped) {
        return configurations;
    } else {
        // 建立result集合来装匹配的自动配置类 
        List<String> result = new ArrayList(candidates.length);
        String[] var12 = candidates;
        int var14 = candidates.length;

        for(i = 0; i < var14; ++i) {
            String candidate = var12[i];
            // 符合条件的自动配置类,此时添加到result集合中 
            if (candidate != null) {
                result.add(candidate);
            }
        }

        if (AutoConfigurationImportSelector.logger.isTraceEnabled()) {
            int numberFiltered = configurations.size() - result.size();
            AutoConfigurationImportSelector.logger.trace("Filtered " + numberFiltered + " auto configuration class in " + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) + " ms");
        }
        // 最后返回符合条件的自动配置类
        return result;
    }
}

AutoConfigurationImportSelector 的 filter 方法主要做的事情就是调用AutoConfigurationImportFilter 接口的match 方法来判断每一个自动配置类上的条件注解(若有的话) @ConditionalOnClass ,@ConditionalOnBean 或 @ConditionalOnWebApplication 是否满足条件,若满足,则返回true,说明匹配,若不满足,则返回false说明不匹配。

我们现在知道 AutoConfigurationImportSelector 的 filter 方法主要做了什么事情就行了,现在先不用研究的过深

有选择的导入自动配置类
this.group.selectImports 方法是如何进一步有选择的导入自动配置类的。

public Iterable<Entry> selectImports() {
    if (this.autoConfigurationEntries.isEmpty()) {
        return Collections.emptyList();
    } else {
        // 这里得到所有要排除的自动配置类的set集合
        Set<String> allExclusions = (Set)this.autoConfigurationEntries.stream().map(AutoConfigurationImportSelector.AutoConfigurationEntry::getExclusions).flatMap(Collection::stream).collect(Collectors.toSet());
        // 这里得到经过滤后所有符合条件的自动配置类的set集合
        Set<String> processedConfigurations = (Set)this.autoConfigurationEntries.stream().map(AutoConfigurationImportSelector.AutoConfigurationEntry::getConfigurations).flatMap(Collection::stream).collect(Collectors.toCollection(LinkedHashSet::new));
        
        // 移除掉要排除的自动配置类 
        processedConfigurations.removeAll(allExclusions);
        // 对标注有@Order注解的自动配置类进行排序
        return (Iterable)this.sortAutoConfigurations(processedConfigurations, this.getAutoConfigurationMetadata()).stream().map((importClassName) -> {
            return new Entry((AnnotationMetadata)this.entries.get(importClassName), importClassName);
        }).collect(Collectors.toList());
    }
}

selectImports 方法的自动配置类再进一步排除 exclude 的自动配置类,然后再排序
最后,我们再总结下SpringBoot自动配置的原理,主要做了以下事情:

  1. 从spring.factories配置文件中加载自动配置类;
  2. 加载的自动配置类中排除掉 @EnableAutoConfiguration 注解的 exclude 属性指定的自动配置类;
  3. 然后再用 AutoConfigurationImportFilter 接口去过滤自动配置类是否符合其标注注解(若有标注的话) @ConditionalOnClass , @ConditionalOnBean 和 @ConditionalOnWebApplication 的条件,若都符合的话则返回匹配结果;
  4. 然后触发 AutoConfigurationImportEvent 事件,告诉 ConditionEvaluationReport 条件评估报告器对象来分别记录符合条件和 exclude 的自动配置类。
  5. 最后spring再将最后筛选后的自动配置类导入IOC容器中

画图梳理一下这个流程


image.png

总结:

  • 在当前Spring Boot工程中,各个组件(jar包),都会存在一个名称为META-INF的目录,目录中有一个sping.factories文件。
  • 在这个文件中会配置工厂类的全路径如MyBatis中配置了org.mybatis.spring.boot.autoconfigure.MyBatisAutoConfiguration。
  • Spring Boot 通过@EnableAutoConfiguration注解收集配置工厂类
  • 由Spring创建bean实例存到IoC容器中。
image

条件注解

@Conditional是Spring4新提供的注解,它的作用是按照一定的条件进行判断,满足条件给容器注册bean。

  • @ConditionalOnBean:仅仅在当前上下文中存在某个对象时,才会实例化一个Bean。
  • @ConditionalOnClass:某个class位于类路径上,才会实例化一个Bean。
  • @ConditionalOnExpression:当表达式为true的时候,才会实例化一个Bean。基于SpEL表达式 的条件判断。
  • @ConditionalOnMissingBean:仅仅在当前上下文中不存在某个对象时,才会实例化一个Bean。
  • @ConditionalOnMissingClass:某个class类路径上不存在的时候,才会实例化一个Bean。
  • @ConditionalOnNotWebApplication:不是web应用,才会实例化一个Bean。
  • @ConditionalOnWebApplication:当项目是一个Web项目时进行实例化。
  • @ConditionalOnNotWebApplication:当项目不是一个Web项目时进行实例化。
  • @ConditionalOnProperty:当指定的属性有指定的值时进行实例化。
  • @ConditionalOnJava:当JVM版本为指定的版本范围时触发实例化。
  • @ConditionalOnResource:当类路径下有指定的资源时触发实例化。
  • @ConditionalOnJndi:在JNDI存在的条件下触发实例化。
  • @ConditionalOnSingleCandidate:当指定的Bean在容器中只有一个,或者有多个但是指定了首 选的Bean时触发实例化。

@ComponentScan

  • @ConponentScan用于配置扫描Spring的扫包基础路径的注解。默认扫描被这个注解标记的类的目录及子目录。
  • @SpringBootApplication中包含了@ConponentScan,因此它具备@ConponentScan相同的功能,因此被@SpringBootApplication注解标记了的类的类路径及其子路径上的bean都能被扫描到。这也是为什么非这个目录及其子目录中的bean不能被扫描的原因。

SpringApplication初始化过程

@SpringBootApplication中的@ComponentScan是在什么时候被扫描到的?
SpringBoot项目的main方法:


@SpringBootApplication
@EnableConfigurationProperties
public class SpringBootDemoApplication {

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

}

从run方法开始追踪:

public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
    return run(new Class[]{primarySource}, args);
}

调用了另外一个run方法:

public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
    return (new SpringApplication(primarySources)).run(args);
}

这个run方法构建了一个SpringApplication类的实例,我们继续来看下SpringApplication类的构造函数

SpringApplication构造函数

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    this.sources = new LinkedHashSet();
    this.bannerMode = Mode.CONSOLE;
    this.logStartupInfo = true;
    this.addCommandLineProperties = true;
    this.addConversionService = true;
    this.headless = true;
    this.registerShutdownHook = true;
    this.additionalProfiles = Collections.emptySet();
    this.isCustomEnvironment = false;
    this.lazyInitialization = false;
    this.applicationContextFactory = ApplicationContextFactory.DEFAULT;
    this.applicationStartup = ApplicationStartup.DEFAULT;
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
    // 1、推断应用类型:servlet、reactive、none
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    this.bootstrapRegistryInitializers = this.getBootstrapRegistryInitializersFromSpringFactories();
    // 2、初始化META-INF/spring.factories文件中配置的ApplicationContextInitializer
    this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
    // 3、初始化classpath下的所有已配置的ApplicationListener
    this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
    // 4、推断出main方法的类名。
    this.mainApplicationClass = this.deduceMainApplicationClass();
}

判断应用类型

private static final String[] SERVLET_INDICATOR_CLASSES = new String[]{"javax.servlet.Servlet", "org.springframework.web.context.ConfigurableWebApplicationContext"};

static WebApplicationType deduceFromClasspath() {
    if (ClassUtils.isPresent("org.springframework.web.reactive.DispatcherHandler", (ClassLoader)null) && !ClassUtils.isPresent("org.springframework.web.servlet.DispatcherServlet", (ClassLoader)null) && !ClassUtils.isPresent("org.glassfish.jersey.servlet.ServletContainer", (ClassLoader)null)) {
        return REACTIVE;
    } else {
        String[] var0 = SERVLET_INDICATOR_CLASSES;
        int var1 = var0.length;

        for(int var2 = 0; var2 < var1; ++var2) {
            String className = var0[var2];
            if (!ClassUtils.isPresent(className, (ClassLoader)null)) {
                return NONE;
            }
        }

        return SERVLET;
    }
}

判断应用类型主要分三个步骤:

  • 1、判断类路径中有没有DispatcherHandler,DispatcherServlet,ServletContainer:如果有DispatcherHandler,没有DispatcherServlet,ServletContainer则是REACTIVE(响应式编程)类型的应用。
  • 2、如果不满足条件1,如果类路径不包含Servlet类或不包含ConfigurableWebApplicationContext类,则是NONE(不是一个web应用)类型应用。
  • 如果条件1、2都不满足,那它就是一个Servlet(web应用,需要启动内置servlet容器)应用。

初始化ApplicationContextInitializer

this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
    ClassLoader classLoader = this.getClassLoader();
    // 从MATA-INF/spring.factories文件中读取所有ApplicationContextInitializer类全路径为key的所有类全路径
    Set<String> names = new LinkedHashSet(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    // 反射实例化这些ApplicationContextInitializer类
    List<T> instances = this.createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
    AnnotationAwareOrderComparator.sort(instances);
    return instances;
}

主要完成了两步操作:

  • 从MATA-INF/spring.factories文件中读取所有ApplicationContextInitializer类全路径为key的所有类全路径。
  • 反射实例化上一步获取到的类,并放入到一个集合中。

初始化classpath下的所有ApplicationListener

this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
``
加载过程跟初始化器一样
主要完成了两步操作:
- 从MATA-INF/spring.factories文件中读取所有ApplicationListener类全路径为key的所有类全路径。
- 反射实例化上一步获取到的类,并放入到一个集合中。

#### 根据调用栈,推断出main方法的类
```java
this.mainApplicationClass = this.deduceMainApplicationClass();
private Class<?> deduceMainApplicationClass() {
    try {
        StackTraceElement[] stackTrace = (new RuntimeException()).getStackTrace();
        StackTraceElement[] var2 = stackTrace;
        int var3 = stackTrace.length;

        for(int var4 = 0; var4 < var3; ++var4) {
            StackTraceElement stackTraceElement = var2[var4];
            if ("main".equals(stackTraceElement.getMethodName())) {
                return Class.forName(stackTraceElement.getClassName());
            }
        }
    } catch (ClassNotFoundException var6) {
    }

    return null;
}

这一步的主要功能是:

  • 根据调用栈,找到main方法所在类的class对象,存储到成员变量。

SpringApplication.run方法流程介绍

SpringApplication对象构建完后调用run方法,run方法代码如下:

public ConfigurableApplicationContext run(String... args) {
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    DefaultBootstrapContext bootstrapContext = this.createBootstrapContext();
    ConfigurableApplicationContext context = null;
    this.configureHeadlessProperty();
    //从META-INF/spring.factories中获取监听器 
    //1、获取并启动监听器
    SpringApplicationRunListeners listeners = this.getRunListeners(args);
    listeners.starting(bootstrapContext, this.mainApplicationClass);

    try {
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        //2、构造应用上下文环境
        ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments);
        this.configureIgnoreBeanInfo(environment);
        Banner printedBanner = this.printBanner(environment);
        //3、初始化应用上下文
        context = this.createApplicationContext();
        context.setApplicationStartup(this.applicationStartup);
        //4、刷新应用上下文前的准备阶段 
        this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
        //5、刷新应用上下文
        this.refreshContext(context);
        //6、刷新应用上下文后的扩展接口
        this.afterRefresh(context, applicationArguments);
        stopWatch.stop();
        if (this.logStartupInfo) {
            (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
        }

        listeners.started(context);
        this.callRunners(context, applicationArguments);
    } catch (Throwable var10) {
        this.handleRunFailure(context, var10, listeners);
        throw new IllegalStateException(var10);
    }

    try {
        listeners.running(context);
        return context;
    } catch (Throwable var9) {
        this.handleRunFailure(context, var9, (SpringApplicationRunListeners)null);
        throw new IllegalStateException(var9);
    }
}

在以上的代码中,启动过程中的重要步骤共分为六步 :

  • 1、获取并启动监听器
  • 2、构造应用上下文环境
  • 3、初始化应用上下文
  • 4、刷新应用上下文前的准备阶段
  • 5、刷新应用上下文
  • 6、刷新应用上下文后的扩展接口

获取并启动监听器

事件机制在Spring是很重要的一部分内容,通过事件机制我们可以监听Spring容器中正在发生的一些事件,同样也可以自定义监听事件。Spring的事件为Bean和Bean之间的消息传递提供支持。当一个对象处理完某种任务后,通知另外的对象进行某些处理,常用的场景有进行某些操作后发送通知,消息、邮件等情况。

获取监听器的入口代码:

SpringApplicationRunListeners listeners = this.getRunListeners(args);
listeners.starting(bootstrapContext, this.mainApplicationClass);

进入到方法中:


private SpringApplicationRunListeners getRunListeners(String[] args) {
   Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
   return new SpringApplicationRunListeners(logger,
         // 从META-INF/spring.factories文件中获取监听器
         getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args),
         this.applicationStartup);
}

从代码中看到,在构建SpringApplicationRunListeners实例之前调用了一个我们非常熟悉的方法:getSpringFactoriesInstances,该方法的主要作用是从spring.factories文件中加载SpringApplicationRunListener类全路径为key的所有类,并实例化。

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
   ClassLoader classLoader = getClassLoader();
   // Use names and ensure unique to protect against duplicates
   // 根据传入的type类型,使用type的类名全路径作为key,到META-INF/spring.factories文件中去查找值,并放到names中
   Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
   // 将查找到的类通过反射实例化
   List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
   // 按order注解顺序排序
   AnnotationAwareOrderComparator.sort(instances);
   return instances;
}

获取到监听器后,启动监听器:

listeners.starting(bootstrapContext, this.mainApplicationClass);

构造应用上下文环境

用上下文环境包括什么呢?包括计算机的环境,Java环境,Spring的运行环境,Spring项目的配置(在SpringBoot中就是那个熟悉的application.properties/yml)等等。


private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
    //创建并配置相应的环境 
    ConfigurableEnvironment environment = this.getOrCreateEnvironment();
    //根据用户配置,配置 environment系统环境 
    this.configureEnvironment((ConfigurableEnvironment)environment, applicationArguments.getSourceArgs());
    // 启动相应的监听器,其中一个重要的监听器 ConfigFileApplicationListener 就是加载项目配置文件的监听器。
    ConfigurationPropertySources.attach((Environment)environment);
    listeners.environmentPrepared(bootstrapContext, (ConfigurableEnvironment)environment);
    DefaultPropertiesPropertySource.moveToEnd((ConfigurableEnvironment)environment);
    Assert.state(!((ConfigurableEnvironment)environment).containsProperty("spring.main.environment-prefix"), "Environment prefix cannot be set via properties.");
    this.bindToSpringApplication((ConfigurableEnvironment)environment);
    if (!this.isCustomEnvironment) {
        environment = (new EnvironmentConverter(this.getClassLoader())).convertEnvironmentIfNecessary((ConfigurableEnvironment)environment, this.deduceEnvironmentClass());
    }

    ConfigurationPropertySources.attach((Environment)environment);
    return (ConfigurableEnvironment)environment;
}

getOrCreateEnvironment方法做了什么?

private ConfigurableEnvironment getOrCreateEnvironment() {
   if (this.environment != null) {
      return this.environment;
   }
   switch (this.webApplicationType) {
   case SERVLET:
      // 如果应用类型是SERVLET,则创建Servlet类型的环境
      return new ApplicationServletEnvironment();
   case REACTIVE:
      // 如果应用类型是响应式编程的,则创建Reactive类型的环境
      return new ApplicationReactiveWebEnvironment();
   default:
      return new ApplicationEnvironment();
   }
}

ApplicationServletEnvironment类型的继承关系:


image
/**
 * Template method delegating to
 * {@link #configurePropertySources(ConfigurableEnvironment, String[])} and
 * {@link #configureProfiles(ConfigurableEnvironment, String[])} in that order.
 * Override this method for complete control over Environment customization, or one of
 * the above for fine-grained control over property sources or profiles, respectively.
 * @param environment this application's environment
 * @param args arguments passed to the {@code run} method
 * @see #configureProfiles(ConfigurableEnvironment, String[])
 * @see #configurePropertySources(ConfigurableEnvironment, String[])
 */
protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
   if (this.addConversionService) {
      environment.setConversionService(new ApplicationConversionService());
   }
   // 将main方法的参数分组到SimpleCommandLinePropertySource中
   configurePropertySources(environment, args);
   // 激活相关的配置文件
   configureProfiles(environment, args);
}

初始化应用上下文

/**
 *用于创建 {@link ApplicationContext} 的策略方法
 * Strategy method used to create the {@link ApplicationContext}. By default this
 * method will respect any explicitly set application context class or factory before
 * falling back to a suitable default.
 * @return the application context (not yet refreshed)
 * @see #setApplicationContextClass(Class)
 * @see #setApplicationContextFactory(ApplicationContextFactory)
 */
protected ConfigurableApplicationContext createApplicationContext() {
    // 根据应用类型创建对应的 ApplicationContext实例
   return this.applicationContextFactory.create(this.webApplicationType);
}

调试进入:

org.springframework.boot.ApplicationContextFactory

ApplicationContextFactory DEFAULT = (webApplicationType) -> {
   try {
      switch (webApplicationType) {
      case SERVLET:
          // 应用类型是Servlet时,new一个AnnotationConfigServletWebServerApplicationContext上下文实例对象
         return new AnnotationConfigServletWebServerApplicationContext();
      case REACTIVE:
         return new AnnotationConfigReactiveWebServerApplicationContext();
      default:
         return new AnnotationConfigApplicationContext();
      }
   }
   catch (Exception ex) {
      throw new IllegalStateException("Unable create a default ApplicationContext instance, "
            + "you may need a custom ApplicationContextFactory", ex);
   }
};

AnnotationConfigServletWebServerApplicationContext的类继承关系:


image

GenericApplicationContext是AnnotationConfigServletWebServerApplicationContext的父类。

创建对象的时候,创建了一个reader和一个scanner:

/**
 * Create a new {@link AnnotationConfigServletWebServerApplicationContext} that needs
 * to be populated through {@link #register} calls and then manually
 * {@linkplain #refresh refreshed}.
 */
public AnnotationConfigServletWebServerApplicationContext() {
   this.reader = new AnnotatedBeanDefinitionReader(this);
   this.scanner = new ClassPathBeanDefinitionScanner(this);
}

另外在调用AnnotationConfigServletWebServerApplicationContext构造函数之前,要先调用父类的构造函数:

public GenericApplicationContext() {
    this.customClassLoader = false;
    this.refreshed = new AtomicBoolean();
    this.beanFactory = new DefaultListableBeanFactory();
}

AnnotationConfigServletWebServerApplicationContext中的IoC容器在其父类的构造方法中被实例化。

image

刷新应用上下文前的准备阶段

刷新应用上下文前的准备阶段,就是想上下文context中设置一些属性,完成bean对象的创建。

private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
      ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
      ApplicationArguments applicationArguments, Banner printedBanner) {
   // 设置容器环境
   context.setEnvironment(environment);
   // 执行容器后置处理器,向context中设置值
   postProcessApplicationContext(context);
   // 遍历初始化器,执行初始化
   applyInitializers(context);
   // 向各个监听器去发送容器准备好的事件
   listeners.contextPrepared(context);
   bootstrapContext.close(context);
   if (this.logStartupInfo) {
      logStartupInfo(context.getParent() == null);
      logStartupProfileInfo(context);
   }
   // Add boot specific singleton beans
   ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
   beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
   if (printedBanner != null) {
      beanFactory.registerSingleton("springBootBanner", printedBanner);
   }
   if (beanFactory instanceof DefaultListableBeanFactory) {
      ((DefaultListableBeanFactory) beanFactory)
            .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
   }
   if (this.lazyInitialization) {
      context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
   }
   // Load the sources
   // 获取所有启动类
   Set<Object> sources = getAllSources();
   Assert.notEmpty(sources, "Sources must not be empty");
   load(context, sources.toArray(new Object[0]));
   listeners.contextLoaded(context);
}

Set<Object> sources = getAllSources();拿到启动类,我们来看下它是怎么拿到的?

public Set<Object> getAllSources() {
   Set<Object> allSources = new LinkedHashSet<>();
   // primarySources在SpringApplication的run方法中传入的
   if (!CollectionUtils.isEmpty(this.primarySources)) {
      allSources.addAll(this.primarySources);
   }
   if (!CollectionUtils.isEmpty(this.sources)) {
      allSources.addAll(this.sources);
   }
   return Collections.unmodifiableSet(allSources);
}

从源码中看到:它是将primarySources和sources两个集合合并后返回。因此primarySources和sources中存放的就是启动类。
下面看看它们在哪里被赋值的:
在SpringApplication的构造方法中,将构造方法中的参数直接添加到了primarySources集合中。

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
   this.resourceLoader = resourceLoader;
   Assert.notNull(primarySources, "PrimarySources must not be null");
   this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
   this.webApplicationType = WebApplicationType.deduceFromClasspath();
   this.bootstrapRegistryInitializers = getBootstrapRegistryInitializersFromSpringFactories();
   setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
   setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
   this.mainApplicationClass = deduceMainApplicationClass();
}

另外有一个方法也涉及到往这个集合中添加值:

public void addPrimarySources(Collection<Class<?>> additionalPrimarySources) {
   this.primarySources.addAll(additionalPrimarySources);
}

但是通过findUseage发现,在启动流程中,这个方法还没有被调用过。因此,当前状态下primarySources集合中只有SpringApplication.run(SpringBootDemoApplication.class, args);
参数中传入的启动类。

另外一个集合sources的只在一个方法中赋值:

public void setSources(Set<String> sources) {
   Assert.notNull(sources, "Sources must not be null");
   this.sources = new LinkedHashSet<>(sources);
}

这个方法只被SampleSpringXmlApplication类中的main方法调用,而Spring Boot的启动类是通过SpringApplication的run方法启动的。因此sources集合是空的。

Set<Object> sources = getAllSources();在Spring Boot工程启动过程中,拿到的是SpringBoot启动类,就是传入到SpringApplication.run()方法的第一个参数。

验证:
断点打在run方法


image

primarySources 集合中只有一个SpringBootDemoApplication类对象


image

这个类对象正好是我们的启动类


image

断点打到getAllResource


image

allSources中只有我们的启动类对象,sources集合为空,验证了我们的结论。
image

接着看load()方法

/**
 * Load beans into the application context.
 * @param context the context to load beans into
 * @param sources the sources to load
 */
protected void load(ApplicationContext context, Object[] sources) {
   if (logger.isDebugEnabled()) {
      logger.debug("Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
   }

   // 创建BeanDefinitionLoader
   BeanDefinitionLoader loader = createBeanDefinitionLoader(getBeanDefinitionRegistry(context), sources);
   if (this.beanNameGenerator != null) {
      loader.setBeanNameGenerator(this.beanNameGenerator);
   }
   if (this.resourceLoader != null) {
      loader.setResourceLoader(this.resourceLoader);
   }
   if (this.environment != null) {
      loader.setEnvironment(this.environment);
   }
   loader.load();
}

在创建BeanDefinitionLoader之前,先获取BeanDefinitionRegistery:

private BeanDefinitionRegistry getBeanDefinitionRegistry(ApplicationContext context) {
   if (context instanceof BeanDefinitionRegistry) {
      return (BeanDefinitionRegistry) context;
   }
   if (context instanceof AbstractApplicationContext) {
      return (BeanDefinitionRegistry) ((AbstractApplicationContext) context).getBeanFactory();
   }
   throw new IllegalStateException("Could not locate BeanDefinitionRegistry");
}

其实就是将context中的IOC容器直接拿出来,并且转成BeanDefinitionRegistry。在前面看AnnotationConfigServletWebApplicationContext类继承关系的时候知道,它实现了BeanDefinitionRegistry接口,因此可以强转。并且可以验证这里就是AnnotationConfigServletWebApplicationContext。


image

BeanDefinitionRegistry定义了很重要的方法registerBeanDefinition(),该方法将BeanDefinition 注册进DefaultListableBeanFactory容器的beanDefinitionMap中。

现在再来看创建BeanDefinitionLoader的过程:

protected BeanDefinitionLoader createBeanDefinitionLoader(BeanDefinitionRegistry registry, Object[] sources) {
   return new BeanDefinitionLoader(registry, sources);
}

构造方法:

/**
 * Create a new {@link BeanDefinitionLoader} that will load beans into the specified
 * {@link BeanDefinitionRegistry}.
 * @param registry the bean definition registry that will contain the loaded beans
 * @param sources the bean sources
 */
BeanDefinitionLoader(BeanDefinitionRegistry registry, Object... sources) {
   Assert.notNull(registry, "Registry must not be null");
   Assert.notEmpty(sources, "Sources must not be empty");
   this.sources = sources;
   //注解形式的Bean定义读取器 比如:@Configuration @Bean @Component @Controller @Service等等
   this.annotatedReader = new AnnotatedBeanDefinitionReader(registry);
   //XML形式的Bean定义读取器
   this.xmlReader = (XML_ENABLED ? new XmlBeanDefinitionReader(registry) : null);
   this.groovyReader = (isGroovyPresent() ? new GroovyBeanDefinitionReader(registry) : null);
   //类路径扫描器
   this.scanner = new ClassPathBeanDefinitionScanner(registry);
   //扫描器添加排除过滤器
   this.scanner.addExcludeFilter(new ClassExcludeFilter(sources));
}

在该方法中创建了reader和scanner,这两个很熟悉的东西,后面肯定会使用reader去读取配置,scaner进行包扫描。

接着看loader.load();方法:

/**
 * Load the sources into the reader.
 */
void load() {
    // 遍历启动类,分别进行load
   for (Object source : this.sources) {
      load(source);
   }
}

private void load(Object source) {
   Assert.notNull(source, "Source must not be null");
   // 从calss加载
   if (source instanceof Class<?>) {
      load((Class<?>) source);
      return;
   }
   // 从resource加载
   if (source instanceof Resource) {
      load((Resource) source);
      return;
   }
   // 从package加载
   if (source instanceof Package) {
      load((Package) source);
      return;
   }
   // 从CharSequence加载
   if (source instanceof CharSequence) {
      load((CharSequence) source);
      return;
   }
   throw new IllegalArgumentException("Invalid source type " + source.getClass());
}

Spring Boot应用启动时会从class加载。

private void load(Class<?> source) {
   if (isGroovyPresent() && GroovyBeanDefinitionSource.class.isAssignableFrom(source)) {
      // Any GroovyLoaders added in beans{} DSL can contribute beans here
      GroovyBeanDefinitionSource loader = BeanUtils.instantiateClass(source, GroovyBeanDefinitionSource.class);
      ((GroovyBeanDefinitionReader) this.groovyReader).beans(loader.getBeans());
   }
   
   // 判断启动类是否符合注册条件
   if (isEligible(source)) {
      this.annotatedReader.register(source);
   }
}

什么是符合注册条件的:

private boolean isEligible(Class<?> type) {
    // 不是匿名的,就是没有名字的类。不是groovy闭包。不是没有构造方法的
   return !(type.isAnonymousClass() || isGroovyClosure(type) || hasNoConstructors(type));
}

这里,启动类是符合注册条件的。
所以启动类会被注册到IoC容器中。

this.annotatedReader.register(source);

而annotatedReader是BeanDefinitionLoader构造函数中被实例化的,并且它是Spring Framework中的类,所以注册过程是由Spring Framework完成的

BeanDefinitionLoader(BeanDefinitionRegistry registry, Object... sources) {
   Assert.notNull(registry, "Registry must not be null");
   Assert.notEmpty(sources, "Sources must not be empty");
   this.sources = sources;
   // AnnotatedBeanDefinitionReader是Spring Framework中的组件
   this.annotatedReader = new AnnotatedBeanDefinitionReader(registry);
   this.xmlReader = (XML_ENABLED ? new XmlBeanDefinitionReader(registry) : null);
   this.groovyReader = (isGroovyPresent() ? new GroovyBeanDefinitionReader(registry) : null);
   this.scanner = new ClassPathBeanDefinitionScanner(registry);
   this.scanner.addExcludeFilter(new ClassExcludeFilter(sources));
}

刷新应用上下文

刷新上下文代码:

private void refreshContext(ConfigurableApplicationContext context) {
   if (this.registerShutdownHook) {
      shutdownHook.registerApplicationContext(context);
   }
   // 核心方法
   refresh(context);
}

从源码可以看到,刷新上下文的核心方法是refresh方法

/**
 * Refresh the underlying {@link ApplicationContext}.
 * @param applicationContext the application context to refresh
 */
protected void refresh(ConfigurableApplicationContext applicationContext) {
   applicationContext.refresh();
}

直接直接调用了application的refresh方法

public void refresh() throws BeansException, IllegalStateException {
    synchronized(this.startupShutdownMonitor) {
        StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
        // 第⼀步:刷新前的预处理
        this.prepareRefresh();
        /* 
         第⼆步: 
         获取BeanFactory;默认实现是DefaultListableBeanFactory 
         加载BeanDefition 并注册到 BeanDefitionRegistry 
         */
        ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
        // 第三步:BeanFactory的预准备⼯作(BeanFactory进⾏⼀些设置,⽐如context的类加载器等)
        this.prepareBeanFactory(beanFactory);

        try {
            // 第四步:BeanFactory准备⼯作完成后进⾏的后置处理⼯作
            this.postProcessBeanFactory(beanFactory);
            StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
            // 第五步:实例化并调⽤实现了BeanFactoryPostProcessor接⼝的Bean
            this.invokeBeanFactoryPostProcessors(beanFactory);
            // 第六步:注册BeanPostProcessor(Bean的后置处理器),在创建bean的前后等执⾏
            this.registerBeanPostProcessors(beanFactory);
            beanPostProcess.end();
            // 第七步:初始化MessageSource组件(做国际化功能;消息绑定,消息解析);
            this.initMessageSource();
            // 第⼋步:初始化事件派发器
            this.initApplicationEventMulticaster();
            // 第九步:⼦类重写这个⽅法,在容器刷新的时候可以⾃定义逻辑
            this.onRefresh();
            // 第⼗步:注册应⽤的监听器。就是注册实现了ApplicationListener接⼝的监听器bean
            this.registerListeners();
            /* 
             第⼗⼀步: 
             初始化所有剩下的⾮懒加载的单例bean 
             初始化创建⾮懒加载⽅式的单例Bean实例(未设置属性) 
             填充属性 
             初始化⽅法调⽤(⽐如调⽤afterPropertiesSet⽅法、init-method⽅法) 
             调⽤BeanPostProcessor(后置处理器)对实例bean进⾏后置处 
             */
            this.finishBeanFactoryInitialization(beanFactory);
            /* 
             第⼗⼆步: 
             完成context的刷新。主要是调⽤LifecycleProcessor的onRefresh()⽅法,并且发布事 
            件 (ContextRefreshedEvent) 
             */
            this.finishRefresh();
        } catch (BeansException var10) {
            if (this.logger.isWarnEnabled()) {
                this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var10);
            }

            this.destroyBeans();
            this.cancelRefresh(var10);
            throw var10;
        } finally {
            this.resetCommonCaches();
            contextRefresh.end();
        }

    }
}

invokeBeanFactoryPostProcessors(beanFactory);(重点)
IoC容器的初始化过程包括三个步骤,在invokeBeanFactoryPostProcessors()方法中完成了IoC容
器初始化过程的三个步骤。
1,第一步:Resource定位
在SpringBoot中,我们都知道他的包扫描是从主类所在的包开始扫描的,prepareContext()
方法中,会先将主类解析成BeanDefinition,然后在refresh()方法的
invokeBeanFactoryPostProcessors()方法中解析主类的BeanDefinition获取basePackage的路
径。这样就完成了定位的过程。其次SpringBoot的各种starter是通过SPI扩展机制实现的自动装
配,SpringBoot的自动装配同样也是在invokeBeanFactoryPostProcessors()方法中实现的。还有
一种情况,在SpringBoot中有很多的@EnableXXX注解,细心点进去看的应该就知道其底层是
@Import注解,在invokeBeanFactoryPostProcessors()方法中也实现了对该注解指定的配置类的
定位加载。
常规的在SpringBoot中有三种实现定位,第一个是主类所在包的,第二个是SPI扩展机制实现
的自动装配(比如各种starter),第三种就是@Import注解指定的类。(对于非常规的不说了)

2,第二步:BeanDefinition的载入
在第一步中说了三种Resource的定位情况,定位后紧接着就是BeanDefinition的分别载入。
所谓的载入就是通过上面的定位得到的basePackage,SpringBoot会将该路径拼接成:
classpath:com/lagou/**/.class这样的形式,然后一个叫做
xPathMatchingResourcePatternResolver的类会将该路径下所有的.class文件都加载进来,然后
遍历判断是不是有@Component注解,如果有的话,就是我们要装载的BeanDefinition。大致过
程就是这样的了。

3、第三个过程:注册BeanDefinition
这个过程通过调用上文提到的BeanDefinitionRegister接口的实现来完成。这个注册过程把载入
过程中解析得到的BeanDefinition向IoC容器进行注册。通过上文的分析,我们可以看到,在IoC容
器中将BeanDefinition注入到一个ConcurrentHashMap中,IoC容器就是通过这个HashMap来持
有这些BeanDefinition数据的。比如DefaultListableBeanFactory 中的beanDefinitionMap属性。
OK,总结完了,接下来我们通过代码看看具体是怎么实现的。

刷新应用上下文后的扩展接口

/**
 * Called after the context has been refreshed.
 * @param context the application context
 * @param args the application arguments
 */
protected void afterRefresh(ConfigurableApplicationContext context, ApplicationArguments args) {
}

扩展接口,设计模式中的模板方法,默认为空实现。如果有自定义需求,可以重写该方法。比如打印一些启动结束log,或者一些其它后置处理。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容