03_SpringBoot源码分析1


参考资料

https://www.processon.com/view/61a045b81efad425fd6c263a?fromnew=1
https://www.yuque.com/atguigu/springboot/qb7hy2
https://www.yuque.com/niuweijiu/dhwvbv/gfqu63

https://www.yuque.com/sheyanjun/program
image.png
SpringBoot 源码剖析 · 语雀 (2022_4_1 22_24_13).html

拉勾教育 SpringBoot 源码剖析
https://www.bilibili.com/video/BV1Jo4y1m7hY
SpringBoot 的自动装配原理
https://www.processon.com/view/61e7cd2507912906af09fa62?fromnew=1

源码环境搭建

https://github.com/spring-projects/spring-boot/tree/v2.2.9.RELEASE
推荐 2.2.9.RELEASE 版本是用 maven 编译的

源码中带有注释的工程:
https://musetransfer.com/s/7kvgx0dqt(有效期至 2023 年 3 月 27 日)|【Muse】你有一份文件待查收,请点击链接获取文件
环境准备
jdk 1.8+
maven 基本环境要求 3.5+

C:\Users\xiong>java -version
java version "14.0.1" 2020-04-14
Java(TM) SE Runtime Environment (build 14.0.1+7)
Java HotSpot(TM) 64-Bit Server VM (build 14.0.1+7, mixed mode, sharing)

C:\Users\xiong>mvn -version
Apache Maven 3.5.2 (138edd61fd100ec658bfa2d307c43b76940a5d7d; 2017-10-18T15:58:13+08:00)
Maven home: D:\softwares\apache-maven-3.5.2\bin\..
Java version: 14.0.1, vendor: Oracle Corporation
Java home: C:\Program Files\Java\jdk-14.0.1
Default locale: zh_CN, platform encoding: GBK
OS name: "windows 10", version: "10.0", arch: "amd64", family: "windows"

编译源码

1、进入 spring-boot 源码根目录,执行 mvn 命令

//跳过测试用例 会下载大量jar包
mvn clean install -DskipTests -Pfast

参考:https://www.cnblogs.com/h--d/p/14775266.html
2、导入 IDEA
编译完成后将 spring-boot 导入到 IDEA,会出现下面问题
image.png
image.png
3、新建一个 Module
image.png

image.png
注意:要将 pom.xml 中的 version 改为 2.2.9.RELEASE,这样我们引入的 spring-boot 启动类和方法都是源码中的了,跟踪代码时可以进入有注释的源码中(这样就可以直接在源码中做注释了)

SpringBoot 源码剖析

1、依赖管理

1.1 为什么导入 dependency 时不需要指定版本?

在 Spring Boot 入门程序中,项目 pom.xml 文件有两个核心依赖,
分别是 spring-boot-starter-parent 和 spring-boot-starter-web,关于这两个依赖的相关介绍具体如下
spring-boot-starter-parent

<!-- Spring Boot父项目依赖管理 -->
<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>2.2.9.RELEASE</version>
  <relativePath/> 		<!-- lookup parent from repository -->
</parent>

上述代码中,将 spring-boot-starter-parent 依赖作为 Spring Boot 项目的统一父项目依赖管理,并将项目版本号统一为 2.2.9.RELEASE,该版本号根据实际开发需求是可以修改的
使用“Ctrl+鼠标左键”进入并查看 spring-boot-starter-parent 底层源文件,先看 spring-boot-starter-parent 做了哪些事
首先看 spring-boot-starter-parent 的 properties 节点

<parent>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-dependencies</artifactId>
	<version>${revision}</version>
	<relativePath>../../spring-boot-dependencies</relativePath>
</parent>

<artifactId>spring-boot-starter-parent</artifactId>
<packaging>pom</packaging>
<name>Spring Boot Starter Parent</name>
<description>Parent pom providing dependency and plugin management for applications
	built with Maven</description>

<properties>
	<main.basedir>${basedir}/../../..</main.basedir>
	<java.version>1.8</java.version>
	<resource.delimiter>@</resource.delimiter> <!-- delimiter that doesn't clash with Spring ${} placeholders -->
	<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
	<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
	<maven.compiler.source>${java.version}</maven.compiler.source>
	<maven.compiler.target>${java.version}</maven.compiler.target>
</properties>

在这里 spring-boot-starter-parent 定义了:
(1)工程的 Java 版本为 1.8 。
(2)工程代码的编译源文件编码格式为 UTF-8
(3)工程编译后的文件编码格式为 UTF-8
(4)Maven 打包编译的版本

接下来再来看 spring-boot-starter-parent 的「build」节点,分别定义了 resources 资源和 pluginManagement

<resources>
  <resource>
    <filtering>true</filtering>
    <directory>${basedir}/src/main/resources</directory>
    <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>

我们详细看一下 resources 节点,里面定义了资源过滤,针对 application 的 yml 、properties 格式进行了过滤,可以支持不同环境的配置,比如 application-dev.yml 、application-test.yml 等等。
pluginManagement 则是引入了相应的插件和对应的版本依赖
最后来看 spring-boot-starter-parent 的父依赖 spring-boot-dependencies 的 properties 节点
我们看定义 POM,这个才是 SpringBoot 项目的真正管理依赖的项目,里面定义了 SpringBoot 相关的版本

<properties>
		<main.basedir>${basedir}/../..</main.basedir>
		<!-- Dependency versions -->
		<activemq.version>5.15.13</activemq.version>
		<antlr2.version>2.7.7</antlr2.version>
		<appengine-sdk.version>1.9.81</appengine-sdk.version>
		<artemis.version>2.10.1</artemis.version>
		<aspectj.version>1.9.6</aspectj.version>
		<assertj.version>3.13.2</assertj.version>
		<atomikos.version>4.0.6</atomikos.version>
		<awaitility.version>4.0.3</awaitility.version>
		<bitronix.version>2.1.4</bitronix.version>
		<byte-buddy.version>1.10.13</byte-buddy.version>
		<caffeine.version>2.8.5</caffeine.version>
		<cassandra-driver.version>3.7.2</cassandra-driver.version>
		<classmate.version>1.5.1</classmate.version>
		<commons-codec.version>1.13</commons-codec.version>
		<commons-dbcp2.version>2.7.0</commons-dbcp2.version>
		<commons-lang3.version>3.9</commons-lang3.version>
  ...
</properties>

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

<dependencyManagement>
		<dependencies>
			<!-- Spring Boot -->
			<dependency>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot</artifactId>
				<version>${revision}</version>
			</dependency>
			<dependency>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-test</artifactId>
				<version>${revision}</version>
			</dependency>
			<dependency>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-test-autoconfigure</artifactId>
				<version>${revision}</version>
			</dependency>
			<dependency>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-actuator</artifactId>
				<version>${revision}</version>
			</dependency>
		...
	</dependencies>
</dependencyManagement>

spring-boot-starter-parent 通过继承 spring-boot-dependencies 从而实现了 SpringBoot 的版本依赖管理,所以我们的 SpringBoot 工程继承 spring-boot-starter-parent 后已经具备版本锁定等配置了,这也就是在 Spring Boot 项目中部分依赖不需要写版本号的原因

1.2 项目运行依赖的 JAR 包是从何而来的?

spring-boot-starter-parent 父依赖启动器的主要作用是进行版本统一管理,那么项目运行依赖的 JAR 包是从何而来的?
查看 spring-boot-starter-web 依赖文件源码,核心代码具体如下

<dependencies>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-json</artifactId>		#json
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-tomcat</artifactId>			#tomcat
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-validation</artifactId>
		<exclusions>
			<exclusion>
				<groupId>org.apache.tomcat.embed</groupId>
				<artifactId>tomcat-embed-el</artifactId>
			</exclusion>
		</exclusions>
	</dependency>
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-web</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-webmvc</artifactId>
	</dependency>
</dependencies>

从上述代码可以发现,spring-boot-starter-web 依赖启动器的主要作用是打包了 Web 开发场景所需的底层所有依赖(基于依赖传递,当前项目也存在对应的依赖 jar 包),正是如此,在 pom.xml 中引入 spring-boot-starter-web 依赖启动器时,就可以实现 Web 场景开发,而不需要额外导入 Tomcat 服务器以及其他 Web 依赖文件等。

当然,这些引入的依赖文件的版本号还是由 spring-boot-starter-parent 父依赖进行的统一管理。
Spring Boot 除了提供有上述介绍的 Web 依赖启动器外,还提供了其他许多开发场景的相关依赖,
我们可以打开Spring Boot 官方文档查找
image.png
列出了 Spring Boot 官方提供的部分场景依赖启动器,这些依赖启动器适用于不同的场景开发,使用时只需要 pom.xml 文件中导入对应的依赖启动器即可。
需要说明的是,Spring Boot 官方并不是针对所有场景开发的技术框架都提供了场景启动器,例如阿里巴巴的 Druid 数据源等,Spring Boot 官方就没有提供对应的依赖启动器。为了充分利用 Spring Boot 框架的优势,在 Spring Boot 官方没有整合这些技术框架的情况下,Druid 等技术框架所在的开发团队主动与 Spring Boot 框架进行了整合,实现了各自的依赖启动器,例如 druid-spring-boot-starter 等。
注意:

  • spring 官方提供的启动器命名规则为:spring-boot-starter-xxx
  • 其他第三方的启动器和我们自定义的 Starter 要遵循的命名规则为:xxx-spring-boot-starter
  • 我们在 pom.xml 文件中引入这些第三方的依赖启动器时,切记要配置对应的版本号。

2、自动配置@SpringBootApplication

自动配置:根据我们添加的 jar 包依赖,spring 会自动将一些配置类的 bean 注册进 IOC 容器,我们可以在需要的地方直接
通过@Autowired 或者@Resource 注解来使用它。
问题:Spring Boot 到底是如何进行自动配置的,都把哪些组件进行了自动配置?

Spring Boot 应用的启动入口是@SpringBootApplication 注解标注类中的 main()方法,
@SpringBootApplication : SpringBoot 应用标注在某个类上说明这个类是 SpringBoot 的主配置类, SpringBoot 应用就运行这个类的 main() 方法 从这里开始启动

@SpringBootApplication

下面,查看@SpringBootApplication 内部源码进行分析 ,核心代码具体如下

//1、
@SpringBootApplication
public class SpringbootMytestApplication {
	public static void main(String[] args) {
		SpringApplication.run(SpringbootMytestApplication.class, args);
	}
}

//2、@SpringBootApplication是一个组合注解
@Target({ElementType.TYPE}) //注解的适用范围,Type表示注解可以描述在类、接口、注解或枚举中
@Retention(RetentionPolicy.RUNTIME) //表示注解的生命周期,Runtime运行时
@Documented //表示注解可以记录在javadoc中
@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 {};
}

image.png
可以添加 exclude excludeName 参数,排除掉指定类

从上述源码可以看出,@SpringBootApplication 注解是一个组合注解,前面 4 个是注解的元数据信息, 我们主要看后面 3 个注解:@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan 三个核心注解,关于这三个核心注解的相关说明具体如下

@SpringBootConfiguration
@SpringBootConfiguration : SpringBoot 的配置类,标注在某个类上,表示这是一个 SpringBoot 的配置类。
查看@SpringBootConfiguration 注解源码,核心代码具体如下。

//3、对@Configuration注解的封装
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented

@Configuration
public @interface SpringBootConfiguration {
}

从上述源码可以看出,@SpringBootConfiguration 注解内部有一个核心注解@Configuration,该注解是 Spring 框架提供的,表示当前类为一个配置类(XML 配置文件的注解表现形式),并可以被组件扫描器扫描。由此可见,@SpringBootConfiguration 注解的作用与@Configuration 注解相同,都是标识一个可以被组件扫描器扫描的配置类,只不过@SpringBootConfiguration 是被 Spring Boot 进行了重新封装命名而已

@EnableAutoConfiguration(重点)

package org.springframework.boot.autoconfigure;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited

@AutoConfigurationPackage		//自动配置包
@Import(AutoConfigurationImportSelector.class)	//spring的底层注解@Import,将组件导入到IOC容器中
// 告诉SpringBoot开启自动配置功能,这样自动配置才能生效。
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    // 返回不会被导入到 Spring 容器中的类
    Class<?>[] exclude() default {};

    // 返回不会被导入到 Spring 容器中的类名
    String[] excludeName() default {};
}

@AutoConfigurationPackage

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited

// Spring的底层注解@Import,给容器中导入一个组件
// 导入的组件是AutoConfigurationPackages.Registrar.class
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {

}

@AutoConfigurationPackage :自动配置包,它也是一个组合注解,其中最重要的注解是@Import(AutoConfigurationPackages.Registrar.class) ,它是 Spring 框架的底层注解,它的作用就是给容器中导入某个组件类,例如@Import(AutoConfigurationPackages.Registrar.class) ,它就是将 Registrar 这个组件类导入到容器中,可查看 Registrar 类中 registerBeanDefinitions 方法:

@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
                                    BeanDefinitionRegistry registry) {
    // 将注解标注的元信息传入,获取到相应的包名
    register(registry, new PackageImport(metadata).getPackageName());
}

我们对 new PackageImport(metadata).getPackageName() 进行检索,看看其结果是什么?
image.png
再看 register 方法

public static void register(BeanDefinitionRegistry registry, String... packageNames) {
    // 这里参数 packageNames 缺省情况下就是一个字符串,是使用了注解
    // @SpringBootApplication 的 Spring Boot 应用程序入口类所在的包
    if (registry.containsBeanDefinition(BEAN)) {
        // 如果该bean已经注册,则将要注册包名称添加进去
        BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
        ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues();
        constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames));
    }
    else {
        //如果该bean尚未注册,则注册该bean,参数中提供的包名称会被设置到bean定义中去
        GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
        beanDefinition.setBeanClass(BasePackages.class);
        beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0,packageNames);
        beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
        registry.registerBeanDefinition(BEAN, beanDefinition);
    }
}

image.png
AutoConfigurationPackages.Registrar 这个类就干一个事,注册一个 Bean ,这个 Bean 就是 org.springframework.boot.autoconfigure.AutoConfigurationPackages.BasePackages ,它有一个参数,这个参数是使用了@AutoConfigurationPackage 这个注解的类所在的包路径,保存自动配置类以供之后的使用,比如给 JPA entity 扫描器用来扫描开发人员通过注解@Entity 定义的 entity 类。

@Import(AutoConfigurationImportSelector.class)

@Import({AutoConfigurationImportSelector.class}) :将 AutoConfigurationImportSelector 这个类导入到 Spring 容器中,AutoConfigurationImportSelector 可以帮助 Springboot 应用将所有符合条件的@Configuration 配置都加载到当前 SpringBoot 创建并使用的 IOC 容器( ApplicationContext )中。
image.png
可以看到 AutoConfigurationImportSelector 重点是实现了 DeferredImportSelector 接口和各种 Aware 接口,然后 DeferredImportSelector 接口又继承了 ImportSelector 接口。
其不光实现了 ImportSelector 接口,还实现了很多其它的 Aware 接口,分别表示在某个时机会被回调。

确定自动配置实现逻辑的入口方法:
跟自动配置逻辑相关的入口方法在 DeferredImportSelectorGrouping 类的 getImports 方法处(ConfigurationClassParser.java),因此我们就从 DeferredImportSelectorGrouping 类的 getImports 方法来开始分析 SpringBoot 的自动配置源码好了。
先看一下 getImports 方法代码:

// ConfigurationClassParser.java
public Iterable<Group.Entry> getImports() {
    // 遍历DeferredImportSelectorHolder对象集合deferredImports,
    // deferredImports集合装了各种ImportSelector,这里装的是AutoConfigurationImportSelector
    for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
        // 【1】,利用AutoConfigurationGroup的process方法来处理自动配置的相关逻辑,
        //       决定导入哪些配置类(这个是我们分析的重点,自动配置的逻辑全在这了)
        this.group.process(deferredImport.getConfigurationClass().getMetadata(),
                           deferredImport.getImportSelector());
    }
    // 【2】,经过上面的处理后,然后再进行选择导入哪些配置类
    return this.group.selectImports();
}

标【1】处的的代码是我们分析的重中之重,自动配置的相关的绝大部分逻辑全在这里了。那么this.group.process(deferredImport.getConfigurationClass().getMetadata(),deferredImport.getImportSelector()) ;主要做的事情就是在 this.group 即 AutoConfigurationGroup 对象的 process 方法中,传入的 AutoConfigurationImportSelector 对象来选择一些符合条件的自动配置类,过滤掉一些不符合条件的自动配置类,就是这么个事情。

ConfigurationClassParser.java是一个非常重要的类(需要重点关注!!!)
image.png
再进入到 AutoConfigurationImportSelector$AutoConfigurationGroup 的 process 方法:
image.png
通过图中我们可以看到,跟自动配置逻辑相关的入口方法在 process 方法中。

分析自动配置的主要逻辑

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

    // 【1】调用getAutoConfigurationEntry方法得到自动配置类放入autoConfigurationEntry对象中
    AutoConfigurationEntry autoConfigurationEntry =
        ((AutoConfigurationImportSelector) deferredImportSelector)
        .getAutoConfigurationEntry(getAutoConfigurationMetadata(),
                                   annotationMetadata);

    // 【2】又将封装了自动配置类的autoConfigurationEntry对象装进autoConfigurationEntries集合
    this.autoConfigurationEntries.add(autoConfigurationEntry);

    // 【3】遍历刚获取的自动配置类
    for (String importClassName : autoConfigurationEntry.getConfigurations()) {
        // 这里符合条件的自动配置类作为key,annotationMetadata作为值放进entries集合
        this.entries.putIfAbsent(importClassName, annotationMetadata);
    }
}

上面代码中我们再来看标【1】的方法getAutoConfigurationEntry ,这个方法主要是用来获取自动配置类有关,承担了自动配置的主要逻辑。直接上代码:

// AutoConfigurationImportSelector.java
// 获取符合条件的自动配置类,避免加载不必要的自动配置类从而造成内存浪费
protected AutoConfigurationEntry getAutoConfigurationEntry(
    AutoConfigurationMetadata autoConfigurationMetadata,

    AnnotationMetadata annotationMetadata) {
    // 获取是否有配置spring.boot.enableautoconfiguration属性,默认返回true
    if (!isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    }

    // 获得@Congiguration标注的Configuration类即被审视introspectedClass的注解数据,
    // 比如:@SpringBootApplication(exclude = FreeMarkerAutoConfiguration.class)
    // 将会获取到exclude = FreeMarkerAutoConfiguration.class和excludeName=""的注解数据
    AnnotationAttributes attributes = getAttributes(annotationMetadata);

    // 【1】得到spring.factories文件配置的所有自动配置类
    List<String> configurations = getCandidateConfigurations(annotationMetadata,
                                                             attributes);
    // 利用LinkedHashSet移除重复的配置类
    configurations = removeDuplicates(configurations);

    // 得到要排除的自动配置类,比如注解属性exclude的配置类
    // 比如:@SpringBootApplication(exclude = FreeMarkerAutoConfiguration.class)
    // 将会获取到exclude = FreeMarkerAutoConfiguration.class的注解数据
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);

    // 检查要被排除的配置类,因为有些不是自动配置类,故要抛出异常
    checkExcludedClasses(configurations, exclusions);

    // 【2】将要排除的配置类移除
    configurations.removeAll(exclusions);

    // 【3】因为从spring.factories文件获取的自动配置类太多,如果有些不必要的自动配置类都加载进内存,会造成内存浪费,因此这里需要进行过滤
    // 注意这里会调用AutoConfigurationImportFilter的match方法来判断是否符合
    // @ConditionalOnBean,@ConditionalOnClass或@ConditionalOnWebApplication,后面会重点分析一下
    configurations = filter(configurations, autoConfigurationMetadata);

    // 【4】获取了符合条件的自动配置类后,此时触发AutoConfigurationImportEvent事件,
    // 目的是告诉ConditionEvaluationReport条件评估报告器对象来记录符合条件的自动配置类
    // 该事件什么时候会被触发?--> 在刷新容器时调用invokeBeanFactoryPostProcessors后置处理器时触发
    fireAutoConfigurationImportEvents(configurations, exclusions);

    // 【5】将符合条件和要排除的自动配置类封装进AutoConfigurationEntry对象,并返回
    return new AutoConfigurationEntry(configurations, exclusions);
}

深入 getCandidateConfigurations 方法
这个方法中有一个重要方法 loadFactoryNames ,这个方法是让 SpringFactoryLoader 去加载一些组件的名字。

//1、
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
                                                  AnnotationAttributes attributes) {

    // 这个方法需要传入两个参数getSpringFactoriesLoaderFactoryClass()和getBeanClassLoader()
    // getSpringFactoriesLoaderFactoryClass()这个方法返回的是EnableAutoConfiguration.class
    // getBeanClassLoader()这个方法返回的是beanClassLoader(类加载器)
    List<String> configurations =
        SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
                                               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;
}

继续点开 loadFactory 方法

//2、
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
        String factoryTypeName = factoryType.getName();
        return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
    }

//3、
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
        MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
        if (result != null) {
            return result;
        } else {
            try {
                Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
                LinkedMultiValueMap result = new LinkedMultiValueMap();

                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[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                        int var10 = var9.length;

                        for(int var11 = 0; var11 < var10; ++var11) {
                            String factoryImplementationName = var9[var11];
                            result.add(factoryTypeName, factoryImplementationName.trim());
                        }
                    }
                }

                cache.put(classLoader, result);
                return result;
            } catch (IOException var13) {
                throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
            }
        }
    }

从代码中我们可以知道,在这个方法中会遍历整个 ClassLoader 中所有 jar 包下的 **META-INF/spring.factories **文件。
spring.factories 里面保存着 springboot 的默认提供的自动配置类。

image.png

看一下我们 Pom.xml 引入的一些 starter

image.png

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

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

// AutoConfigurationImportSelector.java
filter()过滤方法

// AutoConfigurationImportSelector.java

private List<String> filter(List<String> configurations,
                            AutoConfigurationMetadata autoConfigurationMetadata) {
    long startTime = System.nanoTime();

    // 将从spring.factories中获取的自动配置类转出字符串数组
    String[] candidates = StringUtils.toStringArray(configurations);

    // 定义skip数组,是否需要跳过。注意skip数组与candidates数组顺序一一对应
    boolean[] skip = new boolean[candidates.length];
    boolean skipped = false;

    // getAutoConfigurationImportFilters方法:
    // 拿到OnBeanCondition,OnClassCondition和OnWebApplicationCondition
    // 然后遍历这三个条件类去过滤从spring.factories加载的大量配置类
    for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) {
        // 调用各种aware方法,将beanClassLoader,beanFactory等注入到filter对象中,
        // 这里的filter对象即OnBeanCondition,OnClassCondition或OnWebApplicationCondition
        invokeAwareMethods(filter);

        // 各种filter来判断每个candidate(这里实质要通过candidate(自动配置类)拿到其标注的
        // @ConditionalOnClass,@ConditionalOnBean和@ConditionalOnWebApplication里面的注解值)是否匹配,
        // 注意candidates数组与match数组一一对应
        /**********************【主线,重点关注】********************************/
        boolean[] match = filter.match(candidates, autoConfigurationMetadata);

        // 遍历match数组,注意match顺序跟candidates的自动配置类一一对应
        for (int i = 0; i < match.length; i++) {
            // 若有不匹配的话
            if (!match[i]) {
                // 不匹配的将记录在skip数组,标志skip[i]为true,也与candidates数组一一对应
                skip[i] = true;

                // 因为不匹配,将相应的自动配置类置空
                candidates[i] = null;

                // 标注skipped为true
                skipped = true;
            }
        }
    }

    //这里表示若所有自动配置类经过OnBeanCondition,OnClassCondition和OnWebApplicationCondition过滤后
    //全部都匹配的话,则全部原样返回
    if (!skipped) {
        return configurations;
    }

    // 建立result集合来装匹配的自动配置类
    List<String> result = new ArrayList<>(candidates.length);
    for (int i = 0; i < candidates.length; i++) {
        // 若skip[i]为false,则说明是符合条件的自动配置类,此时添加到result集合中
        if (!skip[i]) {
            result.add(candidates[i]);
        }
    }

    // 打印日志
    if (logger.isTraceEnabled()) {
        int numberFiltered = configurations.size() - result.size();
        logger.trace("Filtered " + numberFiltered + " auto configuration class in "
                     + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime)
                     + " ms");
    }

    // 最后返回符合条件的自动配置类
    return new ArrayList<>(result);
}

image.png
AutoConfigurationImportSelector 的 filter 方法主要做的事情就是调用 AutoConfigurationImportFilter 接口的 match 方法来判断每一个自动配置类上的条件注解(若有的话)
@ConditionalOnClass、@ConditionalOnBean、@ConditionalOnWebApplication 是否满足条件,
若满足,则返回 true,说明匹配,若不满足,则返回 false 说明不匹配。
我们现在知道 AutoConfigurationImportSelector 的 filter 方法主要做了什么事情就行了,现在先不用研究的过深。

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

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

// AutoConfigurationImportSelector$AutoConfigurationGroup.java
public Iterable<Entry> selectImports() {
    if (this.autoConfigurationEntries.isEmpty()) {
        return Collections.emptyList();
    }

    // 这里得到所有要排除的自动配置类的set集合
    Set<String> allExclusions = this.autoConfigurationEntries.stream()
        .map(AutoConfigurationEntry::getExclusions)
        .flatMap(Collection::stream).collect(Collectors.toSet());

    // 这里得到经过滤后所有符合条件的自动配置类的set集合
    Set<String> processedConfigurations = this.autoConfigurationEntries.stream()
        .map(AutoConfigurationEntry::getConfigurations)
        .flatMap(Collection::stream)
        .collect(Collectors.toCollection(LinkedHashSet::new));

    // 移除掉要排除的自动配置类
    processedConfigurations.removeAll(allExclusions);

    // 对标注有@Order注解的自动配置类进行排序,
    return sortAutoConfigurations(processedConfigurations,
                                  getAutoConfigurationMetadata())
        .stream()
        .map((importClassName) -> new Entry(
            this.entries.get(importClassName), importClassName))
        .collect(Collectors.toList());
}

可以看到, selectImports 方法主要是针对经过排除掉 exclude 的和被 AutoConfigurationImportFilter 接口过滤后的满足
条件的自动配置类再进一步排除 exclude 的自动配置类,然后再排序
最后,我们再总结下 SpringBoot 自动配置的原理,主要做了以下事情:
1、从 spring.factories 配置文件中加载自动配置类;
2、加载的自动配置类中排除掉@EnableAutoConfiguration 注解的 exclude 属性指定的自动配置类;
3、然后再用 AutoConfigurationImportFilter 接口去过滤自动配置类是否符合其标注注解(若有标注的话)@ConditionalOnClass , @ConditionalOnBean 和 @ConditionalOnWebApplication 的条件,若都符合的话则返回匹配结果;
4、然后触发 AutoConfigurationImportEvent 事件,告诉 ConditionEvaluationReport 条件评估报告器对象来分别记录符合条件和 exclude 的自动配置类。
5、最后 spring 再将最后筛选后的自动配置类导入 IOC 容器中
image.png

image.png
image.png

src\main\java\org\springframework\boot\autoconfigure\AutoConfigurationPackages.java 的 Registrar 这个类就干一个事,注册一个 Bean 这个 Bean 就是 org.springframework.boot.autoconfigure.AutoConfigurationPackages.BasePackages ,它有一个参数 这个参数使用了@AutoConfigurationPackages 这个注解的类所在的包路径,保存自动配置类以供之后的使用,比如给 JPA entity 扫描器用来扫描程序员通过注解@Entity 定义的实体类

HttpEncodingAutoConfiguration自动配置

以 HttpEncodingAutoConfiguration ( Http 编码自动配置)为例解释自动配置原理

package org.springframework.boot.autoconfigure.web.servlet;

import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.http.HttpProperties;
import org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.boot.web.servlet.filter.OrderedCharacterEncodingFilter;
import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.web.filter.CharacterEncodingFilter;

// 表示这是一个配置类,和以前编写的配置文件一样,也可以给容器中添加组件
@Configuration(proxyBeanMethods = false)

// 启动指定类的ConfigurationProperties功能;将配置文件中对应的值和HttpEncodingProperties绑定起来;
@EnableConfigurationProperties(HttpProperties.class)

// Spring底层@Conditional注解,根据不同的条件,如果满足指定的条件,整个配置类里面的配置就会生效。
// 判断当前应用是否是web应用,如果是,当前配置类生效
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)

// 判断当前项目有没有这个CharacterEncodingFilter : SpringMVC中进行乱码解决的过滤器
@ConditionalOnClass(CharacterEncodingFilter.class)

// 判断配置文件中是否存在某个配置 spring.http.encoding.enabled 如果不存在,判断也是成立的
// matchIfMissing = true 表示即使我们配置文件中不配置spring.http.encoding.enabled=true,也是默认生效的
@ConditionalOnProperty(prefix = "spring.http.encoding", value = "enabled", matchIfMissing = true)
public class HttpEncodingAutoConfiguration {
	// 它已经和SpringBoot配置文件中的值进行映射了
	private final HttpProperties.Encoding properties;

	// 只有一个有参构造器的情况下,参数的值就会从容器中拿
	public HttpEncodingAutoConfiguration(HttpProperties properties) {
		this.properties = properties.getEncoding();
	}

	@Bean  //给容器中添加一个组件,这个组件中的某些值需要从properties中获取
	@ConditionalOnMissingBean  //判断容器中没有这个组件
	public CharacterEncodingFilter characterEncodingFilter() {
		CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
		filter.setEncoding(this.properties.getCharset().name());
		filter.setForceRequestEncoding(this.properties.shouldForce(Type.REQUEST));
		filter.setForceResponseEncoding(this.properties.shouldForce(Type.RESPONSE));
		return filter;
	}

	@Bean
	public LocaleCharsetMappingsCustomizer localeCharsetMappingsCustomizer() {
		return new LocaleCharsetMappingsCustomizer(this.properties);
	}

	private static class LocaleCharsetMappingsCustomizer
			implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory>, Ordered {

		private final HttpProperties.Encoding properties;

		LocaleCharsetMappingsCustomizer(HttpProperties.Encoding properties) {
			this.properties = properties;
		}

		@Override
		public void customize(ConfigurableServletWebServerFactory factory) {
			if (this.properties.getMapping() != null) {
				factory.setLocaleCharsetMappings(this.properties.getMapping());
			}
		}

		@Override
		public int getOrder() {
			return 0;
		}
	}
}

根据当前不同的条件判断,决定这个配置类是否生效。
一旦这个配置类生效,这个配置类就会给容器中添加各种组件;这些组件的属性是从对应的 properties 类中获取的,这些类里面的每一个属性又是和配置文件绑定的。

# 我们能配置的属性都是来源于这个功能的properties类
spring.http.encoding.enabled=true
spring.http.encoding.charset=utf-8
spring.http.encoding.force=true

所有在配置文件中能配置的属性都是在 xxxProperties 类中封装着,配置文件能配置什么就可以参照某个功能对应的这个属性类。

// 从配置文件中获取指定的值和bean的属性进行绑定
@ConfigurationProperties(prefix = "spring.http.encoding")
public class HttpEncodingProperties {
	...
}

精髓

  1. SpringBoot 启动会加载大量的自动配置类
  2. 我们看我们需要实现的功能有没有 SpringBoot 默认写好的自动配置类
  3. 我们再来看这个自动配置类中到底配置了哪些组件(只要我们有我们要用的组件,我们就不需要再来配置了)
  4. 给容器中自动配置类添加组件的时候,会从 properties 类中获取某些属性,我们就可以在配置文件中指定这些属性的值。
  5. xxxAutoConfiguration :自动配置类,用于给容器中添加组件从而代替之前我们手动完成大量繁琐的配置。
  6. xxxProperties : 封装了对应自动配置类的默认属性值,如果我们需要自定义属性值,只需要根据 xxxProperties 寻找相关属性在配置文件设值即可。

@ComponentScan

主要是从定义的扫描路径中,找出标识了需要装配的类自动装配到 spring 的 bean 容器中。
常用属性如下:

  • basePackages、value:指定扫描路径,如果为空则以@ComponentScan 注解的类所在的包为基本的扫描路径
  • basePackageClasses:指定具体扫描的类
  • includeFilters:指定满足 Filter 条件的类
  • excludeFilters:指定排除 Filter 条件的类
  • includeFilters 和 excludeFilters 的 FilterType 可选:ANNOTATION=注解类型 默认、ASSIGNABLE_TYPE(指定固定类)、ASPECTJ(ASPECTJ 类型)、REGEX(正则表达式)、CUSTOM(自定义类型),自定义的 Filter 需要实现 TypeFilter 接口

@ComponentScan 的配置如下:

@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes =
                                          TypeExcludeFilter.class),
                                 @Filter(type = FilterType.CUSTOM, classes =
                                         AutoConfigurationExcludeFilter.class) })

借助 excludeFilters 将 TypeExcludeFillter 及 FilterType 这两个类进行排除
当前@ComponentScan 注解没有标注 basePackages 及 value,所以扫描路径默认为@ComponentScan 注解的类所在的包为基本的扫描路径(也就是标注了@SpringBootApplication 注解的项目启动类所在的路径)

抛出疑问:
@EnableAutoConfiguration 注解是通过@Import 注解加载了自动配置固定的 bean
@ComponentScan 注解自动进行注解扫描
那么真正根据包扫描,把组件类生成实例对象存到 IOC 容器中,又是怎么来完成的?

3、run 方法执行流程

@SpringBootApplication //来标注一个主程序类,说明这是一个Spring Boot应用
public class MyTestMVCApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyTestMVCApplication.class, args);
    }
}


public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
    // 调用重载方法
    return run(new Class<?>[] { primarySource }, args);
}


public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
    // 两件事:1.初始化SpringApplication
    //	2.执行run方法
    return new SpringApplication(primarySources).run(args);
}

SpringApplication() 构造方法

继续查看源码, SpringApplication 实例化过程,首先是进入带参数的构造方法,最终回来到两个参数的构造方法。

public SpringApplication(Class<?>... primarySources) {
    this(null, primarySources);
}

@SuppressWarnings({"unchecked", "rawtypes"})
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    //设置资源加载器为null
    this.resourceLoader = resourceLoader;

    //断言加载资源类不能为null
    Assert.notNull(primarySources, "PrimarySources must not be null");

    //将primarySources数组转换为List,最后放到LinkedHashSet集合中
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));

    //【1.1 推断应用类型,后面会根据类型初始化对应的环境。常用的一般都是servlet环境 】
    this.webApplicationType = WebApplicationType.deduceFromClasspath();

    //【1.2 初始化classpath下 META-INF/spring.factories中已配置的 ApplicationContextInitializer 】
    setInitializers((Collection)getSpringFactoriesInstances(ApplicationContextInitializer.class));

    //【1.3 初始化classpath下所有已配置的 ApplicationListener 】
    setListeners((Collection)getSpringFactoriesInstances(ApplicationListener.class));

    //【1.4 根据调用栈,推断出 main 方法的类名 】
    this.mainApplicationClass = deduceMainApplicationClass();
}

deduceFromClasspath()

private static final String[] SERVLET_INDICATOR_CLASSES =
{ "javax.servlet.Servlet", "org.springframework.web.context.ConfigurableWebApplicationContext" };
private static final String WEBMVC_INDICATOR_CLASS =
    "org.springframework.web.servlet.DispatcherServlet";
private static final String WEBFLUX_INDICATOR_CLASS =
    "org.springframework.web.reactive.DispatcherHandler";
private static final String JERSEY_INDICATOR_CLASS =
    "org.glassfish.jersey.servlet.ServletContainer";
private static final String SERVLET_APPLICATION_CONTEXT_CLASS =
    "org.springframework.web.context.WebApplicationContext";
private static final String REACTIVE_APPLICATION_CONTEXT_CLASS =
    "org.springframework.boot.web.reactive.context.ReactiveWebApplicationContext";
/**
* 判断 应用的类型
* NONE: 应用程序不是web应用,也不应该用web服务器去启动
* SERVLET: 应用程序应作为基于 servlet 的web应用程序运行,并应启动嵌入式 servlet web(tomcat)服务器。
* REACTIVE: 应用程序应作为 reactive 的web应用程序运行,并应启动嵌入式 reactive web服务器。
* @return
*/
static WebApplicationType deduceFromClasspath() {
    //classpath下必须存在org.springframework.web.reactive.DispatcherHandler
    if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
        && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
        return WebApplicationType.REACTIVE;
    }

    for (String className : SERVLET_INDICATOR_CLASSES) {
        //classpath环境下不存在javax.servlet.Servlet或者org.springframework.web.context.ConfigurableWebApplicationContext
        if (!ClassUtils.isPresent(className, null)) {
            return WebApplicationType.NONE;
        }
    }
    return WebApplicationType.SERVLET;
}

返回类型是 WebApplicationType 的枚举类型, WebApplicationType 有三个枚举,具体的判断逻辑如下:

  1. WebApplicationType.REACTIVE:classpath 下存在 org.springframework.web.reactive.DispatcherHandler
  2. WebApplicationType.SERVLET:classpath 下存在 javax.servlet.Servlet 或者 org.springframework.web.context.ConfigurableWebApplicationContext
  3. WebApplicationType.NONE:不满足以上条件。

setInitializers((Collection)getSpringFactoriesInstances(ApplicationContextInitializer.class))

初始化 classpath 下 META-INF/spring.factories 中已配置的 ApplicationContextInitializer

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
    return getSpringFactoriesInstances(type, new Class<?>[]{});
}
/**
* 通过指定的classloader 从META-INF/spring.factories获取指定的Spring的工厂实例
*/
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
                                                      Class<?>[] parameterTypes,
                                                      Object... args) {
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();

    // Use names and ensure unique to protect against duplicates
    //通过指定的classLoader从 META-INF/spring.factories 的资源文件中,
    //读取 key 为 type.getName() 的 value
    Set<String> names = new LinkedHashSet<> (SpringFactoriesLoader.loadFactoryNames(type, classLoader));

    //创建Spring工厂实例
    List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);

    //对Spring工厂实例排序(org.springframework.core.annotation.Order注解指定的顺序)
    AnnotationAwareOrderComparator.sort(instances);
    return instances;
}

看看 getSpringFactoriesInstances 都干了什么,看源码,有一个方法很重要 loadFactoryNames()这个方法很重要,这个方法是 spring-core 中提供的从 META-INF/spring.factories 中获取指定的类(key)的同一入口方法。
在这里,获取的是 key 为 org.springframework.context.ApplicationContextInitializer 的类。
debug 看看都获取到了哪些
image.png
上面说了,是从 classpath 下 META-INF/spring.factories 中获取,我们验证一下:
image.png
发现在上图所示的两个工程中找到了 debug 中看到的结果。
ApplicationContextInitializer 是 Spring 框架的类, 这个类的主要目的就是在 ConfigurableApplicationContext 调用refresh()方法之前,回调这个类的 initialize 方法。
通过 ConfigurableApplicationContext 的实例获取容器的环境 Environment,从而实现对配置文件的修改完善等工作。

setListeners((Collection)getSpringFactoriesInstances(ApplicationListener.class));

初始化 classpath 下 META-INF/spring.factories 中已配置的 ApplicationListener。
ApplicationListener 的加载过程和上面的 ApplicationContextInitializer 类的加载过程是一样的,
ApplicationListener 是 spring 的事件监听器,典型的观察者模式,通过 ApplicationEvent 类和 ApplicationListener 接口,可以实现对 spring 容器全生命周期的监听,当然也可以自定义监听事件

总结
关于 SpringApplication 类的构造过程,到这里我们就梳理完了。纵观 SpringApplication 类的实例化过程,我们可以看到,合理的利用该类,我们能在 spring 容器创建之前做一些预备工作,和定制化的需求。
比如,自定义 SpringBoot 的 Banner,比如自定义事件监听器,再比如在容器 refresh 之前通过自定义 ApplicationContextInitializer 修改一些配置或者获取指定的 bean 都是可以的。


run(args)

上一小节我们查看了 SpringApplication 类的实例化过程,这一小节总结 SpringBoot 启动流程最重要的部分:run 方法。

通过 run 方法梳理出 SpringBoot 启动的流程。经过深入分析后,大家会发现 SpringBoot 也就是给 Spring 包了一层皮,事先替我们准备好 Spring 所需要的环境及一些基础
run 方法的执行流程步骤如下:

/**
* Run the Spring application, creating and refreshing a new
* {@link ApplicationContext}.
*
* @param args the application arguments (usually passed from a Java mainmethod)
* @return a running {@link ApplicationContext}
*
* 运行spring应用,并刷新一个新的 ApplicationContext(Spring的上下文)
* ConfigurableApplicationContext 是 ApplicationContext 接口的子接口。在ApplicationContext
* 基础上增加了配置上下文的工具。 ConfigurableApplicationContext是容器的高级接口
*/
public ConfigurableApplicationContext run(String... args) {
    //StopWatch主要是用来统计每项任务执行时长,例如Spring Boot启动占用总时长。
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();

    // ConfigurableApplicationContext Spring 的上下文
    ConfigurableApplicationContext context = null;
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();

    configureHeadlessProperty();

    //【1】获取并启动监听器
    //通过加载META-INF/spring.factories 完成了SpringApplicationRunListener实例化工作
    SpringApplicationRunListeners listeners = getRunListeners(args);
    //实际上是调用了EventPublishingRunListener类的starting()方法
    listeners.starting();
    try {
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);

        //【2】构造容器环境
        //简而言之就是加载系统变量,环境变量,配置文件
        ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);

        //设置需要忽略的bean
        configureIgnoreBeanInfo(environment);

        //打印banner
        Banner printedBanner = printBanner(environment);

        //【3】创建容器
        context = createApplicationContext();

        //实例化SpringBootExceptionReporter.class,用来支持报告关于启动的错误
        exceptionReporters = getSpringFactoriesInstances(
            SpringBootExceptionReporter.class,
            new Class[]{ConfigurableApplicationContext.class}, context);

        //【54】准备容器
        //这一步主要是在容器刷新之前的准备动作。包含一个非常关键的操作:将启动类注入容器,为后续开启自动化配置奠定基础。
        prepareContext(context, environment, listeners, applicationArguments, printedBanner);

        //【5】刷新容器
        //springBoot相关的处理工作已经结束,接下的工作就交给了spring。内部会调用spring的refresh方法,
        // refresh方法在spring整个源码体系中举足轻重,是实现ioc和aop的关键。
        refreshContext(context);

        //【6】刷新容器后的扩展接口
        //设计模式中的模板方法,默认为空实现。如果有自定义需求,可以重写该方法。
        //比如打印一些启动结束log,或者一些其它后置处理。
        afterRefresh(context, applicationArguments);

        //时间记录停止
        stopWatch.stop();

        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
        }
        //发布容器启动完成事件
        listeners.started(context);

        //遍历所有注册的ApplicationRunner和CommandLineRunner,并执行其run()方法。
        //我们可以实现自己的ApplicationRunner或者CommandLineRunner,来对SpringBoot的启动过程进行扩展。
        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;
}

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

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

1、获取并启动监听器

构造应用上下文环境
初始化应用上下文
刷新应用上下文前的准备阶段
刷新应用上下文
刷新应用上下文后的扩展接口
事件机制在 Spring 是很重要的一部分内容,通过事件机制我们可以监听 Spring 容器中正在发生的一些事件,同样也可以自定义监听事件。Spring 的事件为 Bean 和 Bean 之间的消息传递提供支持。当一个对象处理完某种任务后,通知另外的对象进行某些处理,常用的场景有进行某些操作后发送通知、消息、邮件等情况。

private SpringApplicationRunListeners getRunListeners(String[] args) {
    Class<?>[] types = new Class<?>[]{SpringApplication.class, String[].class};

    return new SpringApplicationRunListeners(logger,
                                             getSpringFactoriesInstances(
                                                 SpringApplicationRunListener.class, types, this, args));
}

在这里面是不是看到一个熟悉的方法:getSpringFactoriesInstances(),可以看下面的注释,前面的小节我们已经详细介绍过该方法是怎么一步步的获取到 META-INF/spring.factories 中的指定的 key 的 value,获取到以后怎么实例化类的。

/**
 * 通过指定的classloader 从META-INF/spring.factories获取指定的Spring的工厂实例
 */
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
                                                      Class<?>[] parameterTypes,
                                                      Object... args) {
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();

    // Use names and ensure unique to protect against duplicates
    //通过指定的classLoader从 META-INF/spring.factories 的资源文件中,
    //读取 key 为 type.getName() 的 value
    Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));

    //创建Spring工厂实例
    List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
                                                       classLoader, args, names);
    //对Spring工厂实例排序(org.springframework.core.annotation.Order注解指定的顺序)
    AnnotationAwareOrderComparator.sort(instances);

    return instances;
}

回到 run 方法,debug 这个代码 SpringApplicationRunListeners listeners = getRunListeners(args); 看一下获取的是哪个监听器:
image.png
EventPublishingRunListener 监听器是 Spring 容器的启动监听器。
listeners.starting(); 开启了监听事件。
看一下 spring.factories 都放在哪里
image.png
这里开启的监听器所在的路径为:
spring-boot-2.2.9.RELEASE\spring-boot-project\spring-boot\src\main\resources\META-INF\spring.factories
image.png

image.png
image.png
image.png

2、构造应用上下文环境

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

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
                                                   ApplicationArguments applicationArguments) {
    // Create and configure the environment
    //创建并配置相应的环境
    ConfigurableEnvironment environment = getOrCreateEnvironment();

    //根据用户配置,配置 environment系统环境
    configureEnvironment(environment, applicationArguments.getSourceArgs());

    // 启动相应的监听器,其中一个重要的监听器 ConfigFileApplicationListener
    // 就是加载项目配置文件的监听器。
    listeners.environmentPrepared(environment);
    bindToSpringApplication(environment);

    if (this.webApplicationType == WebApplicationType.NONE) {
        environment = new EnvironmentConverter(getClassLoader())
            .convertToStandardEnvironmentIfNecessary(environment);
    }
    ConfigurationPropertySources.attach(environment);

    return environment;
}

看上面的注释,方法中主要完成的工作,首先是创建并按照相应的应用类型配置相应的环境,然后根据用户的配置,配置系统环境,然后启动监听器,并加载系统配置文件。

getOrCreateEnvironment()
通过代码可以看到根据不同的应用类型初始化不同的系统环境实例。前面咱们已经说过应用类型是怎么判断的了,这里就不在赘述了

private ConfigurableEnvironment getOrCreateEnvironment() {
    if (this.environment != null) {
        return this.environment;
    }

    //如果应用类型是 SERVLET 则实例化 StandardServletEnvironment
    if (this.webApplicationType == WebApplicationType.SERVLET) {
        return new StandardServletEnvironment();
    }

    return new StandardEnvironment();
}

image.png
从上面的继承关系可以看出,StandardServletEnvironment 是 StandardEnvironment 的子类。这两个对象也没什么好讲的,当是 web 项目的时候,环境上会多一些关于 web 环境的配置。

image.png
image.png
image.png
image.png

3、初始化应用上下文

补充:SPI 机制

image.png

  • 例 1:Java 连接数据库:

image.png
image.png

  • 例 2:springboot 自动装配

image.png
image.png

4、自定义 starter

5、内嵌 TomcatSpringBoot 源码剖析 · 语雀 (2022_4_1 22_24_13).html

Spring Boot 默认支持 Tomcat,Jetty,和 Undertow 作为底层容器。而 Spring Boot 默认使用 Tomcat,
一旦引入 spring-boot-starter-web 模块,就默认使用 Tomcat 容器。

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency>

Servlet 容器的使用

默认是 servlet 容器,我们看看 spring-boot-starter-web 这个 starter 中有什么
image.png
image.png
核心就是引入了 tomcat 和 SpringMvc

切换 servlet 容器

那如果我么想切换其他 Servlet 容器呢,只需如下两步:
(1)将 tomcat 依赖移除掉
(2)引入其他 Servlet 容器依赖
引入 jetty:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
  <exclusions>
		<!--移除spring-boot-starter-web中的tomcat-->
    <exclusion>
      <artifactId>spring-boot-starter-tomcat</artifactId>
      <groupId>org.springframework.boot</groupId>
    </exclusion>
  </exclusions>
</dependency>

<!--引入jetty-->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-jetty</artifactId>
</dependency>

内嵌 Tomcat 自动配置原理
在启动 springboot 的时候可谓是相当简单,只需要执行以下代码

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

那些看似简单的事物,其实并不简单。我们之所以觉得他简单,是因为复杂性都被隐藏了。
通过上述代码,大概率可以提出以下几个疑问
1、SpringBoot 是如何启动内置 tomcat 的
2、SpringBoot 为什么可以响应请求,他是如何配置的 SpringMvc

SpringBoot 启动内置 tomcat 流程

1、进入 SpringBoot 启动类,点进@SpringBootApplication 源码,如下图
image.png
2、继续点进@EnableAutoConfiguration,进入该注解,如下图
image.png
image.png
3、上图中使用@Import 注解对 AutoConfigurationImportSelector 类进行了引入,该类做了什么事情呢?
进入源码,首先调用 selectImport()方法,在该方法中调用了 getAutoConfigurationEntry()方法,在之中又调用了 getCandidateConfigurations()方法,getCandidateConfigurations()方法就去 META-INF/spring.factory 配置文件中加载相关配置类
image.png

protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
                                                           AnnotationMetadata annotationMetadata) {
    // 获取是否有配置spring.boot.enableautoconfiguration属性,默认返回true
    if (!isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    }
    //从启动类@SpringBootApplication 注解 获取属性值exclude 和 excludeName
    AnnotationAttributes attributes = getAttributes(annotationMetadata);

    // 【1】得到spring.factories文件配置的所有自动配置类
    List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);

    //利用LinkedHashSet移除重复的配置类
    configurations = removeDuplicates(configurations);

    //得到要排除的自动配置类,比如注解属性exclude的配置类
    // 比如:@SpringBootApplication(exclude = FreeMarkerAutoConfiguration.class)
    // 将会获取到exclude = FreeMarkerAutoConfiguration.class的注解数据
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);

    // 检查要被排除的配置类,因为有些不是自动配置类,故要抛出异常
    checkExcludedClasses(configurations, exclusions);

    // 【2】将要排除的配置类移除
    configurations.removeAll(exclusions);

    // 【3】因为从spring.factories文件获取的自动配置类太多,
    // 如果有些不必要的自动配置类都加载进内存,会造成内存浪费,因此这里需要进行过滤(根据一些条件如@ConditionalXxx)
    configurations = filter(configurations, autoConfigurationMetadata);

    // 【4】获取了符合条件的自动配置类后,此时触发AutoConfigurationImportEvent事件,
    // 目的是告诉ConditionEvaluationReport条件评估报告器对象来记录符合条件的自动配置类
    fireAutoConfigurationImportEvents(configurations, exclusions);

    // 【5】将符合条件和要排除的自动配置类封装进AutoConfigurationEntry对象,并返回
    return new AutoConfigurationEntry(configurations, exclusions);
}

这个 spring.factories 配置文件是加载的 spring-boot-autoconfigure 的配置文件
image.png
继续打开 spring.factories 配置文件,找到 tomcat 所在的类,tomcat 加载在
image.png
进入该类,里面也通过@Import 注解将 EmbeddedTomcat、EmbeddedJetty、EmbeddedUndertow 等嵌入式容器类加载进来了,springboot 默认是启动嵌入式 tomcat 容器,如果要改变启动 jetty 或者 undertow 容器,需在 pom 文件中去设置。如下图:
image.png
继续进入 EmbeddedTomcat 类中,见下图:

6、SpringMVC 自动配置原理

在上一小节介绍了 SpringBoot 是如何启动一个内置 tomcat 的。我们知道我们在 SpringBoot 项目里面是可以直接使用诸如@RequestMapping 这类的 SpringMVC 的注解,这是为什么?我明明没有配置 SpringMVC 为什么就可以使用呢?
其实仅仅引入spring-boot-starter-web是不够的,回忆一下,在一个普通的 WEB 项目中如何去使用 SpringMVC,我们首先就是要在 web.xml 中配置如下配置

<servlet>
  <description>spring mvc servlet</description>
  <servlet-name>springMvc</servlet-name>
  <servlet-class>
    org.springframework.web.servlet.DispatcherServlet
  </servlet-class>
  <load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
  <servlet-name>springMvc</servlet-name>
  <url-pattern>*.do</url-pattern>
</servlet-mapping>

但是在 SpringBoot 中,我们没有了 web.xml 文件,我们如何去配置一个 Dispatcherservlet 呢?
其实 Servlet3.0 规范中规定,要添加一个 Servlet,除了采用 xml 配置的方式,还有一种通过代码的方式,伪代码如下servletContext.addServlet(name, this.servlet);

那么也就是说,如果我们能动态往 web 容器中添加一个我们构造好的 DispatcherServlet 对象,是不是就实现自动装配 SpringMVC 了

自动配置 DispatcherServlet 和 DispatcherServletRegistry

springboot 的自动配置基于 SPI 机制,实现自动配置的核心要点就是添加一个自动配置的类,SpringBoot MVC 的自动配置自然也是相同原理。
所以,先找到 springmvc 对应的自动配置类。
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration

DispatcherServletAutoConfiguration 自动配置类

@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass(DispatcherServlet.class)
@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)
public class DispatcherServletAutoConfiguration {
    //...
}

1、首先注意到,@Configuration 表明这是一个配置类,将会被 spring 给解析。
2、@ConditionalOnWebApplication 意味着当时一个 web 项目,且是 Servlet 项目的时候才会被解析。
3、@ConditionalOnClass 指明 DispatcherServlet 这个核心类必须存在才解析该类。
4、@AutoConfigureAfter 指明在 ServletWebServerFactoryAutoConfiguration 这个类之后再解析,设定了一个顺序。
总的来说,这些注解表明了该自动配置类的会解析的前置条件需要满足。

其次,DispatcherServletAutoConfiguration 类主要包含了两个内部类,分别是
1、DispatcherServletConfiguration
2、DispatcherServletRegistrationConfiguration
顾名思义,前者是配置 DispatcherServlet,后者是配置 DispatcherServlet 的注册类。
什么是注册类?我们知道 Servlet 实例是要被添加(注册)到如 tomcat 这样的 ServletContext 里的,这样才能够提供请求服务。所以,DispatcherServletRegistrationConfiguration 将生成一个 Bean,负责将 DispatcherServlet 给注册到 ServletContext 中。

配置 DispatcherServletConfiguration
我们先看看 DispatcherServletConfiguration 这个配置类

@Configuration(proxyBeanMethods = false)
@Conditional(DefaultDispatcherServletCondition.class)
@ConditionalOnClass(ServletRegistration.class)
@EnableConfigurationProperties({ HttpProperties.class,
                                WebMvcProperties.class })
protected static class DispatcherServletConfiguration {
    //...
}

@Conditional 指明了一个前置条件判断,由 DefaultDispatcherServletCondition 实现。主要是判断了是否已经存在 DispatcherServlet,如果没有才会触发解析。
@ConditionalOnClass 指明了当 ServletRegistration 这个类存在的时候才会触发解析,生成的 DispatcherServlet 才能注册到 ServletContext 中。
最后,@EnableConfigrationProperties 将会从 application.properties 这样的配置文件中读取 spring.http 和 spring.mvc 前缀的属性生成配置对象 HttpProperties 和 WebMvcProperties。

再看 DispatcherServletConfiguration 这个内部类的内部代码

@Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServlet dispatcherServlet(HttpProperties httpProperties,
                                           WebMvcProperties webMvcProperties) {
    DispatcherServlet dispatcherServlet = new DispatcherServlet();
    dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
    dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());
    dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound());
    dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());
    dispatcherServlet.setEnableLoggingRequestDetails(httpProperties.isLogRequestDetails());
    return dispatcherServlet;
}

@Bean
@ConditionalOnBean(MultipartResolver.class)
@ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
public MultipartResolver multipartResolver(MultipartResolver resolver) {
    // Detect if the user has created a MultipartResolver but named itincorrectly
    return resolver;
}

这个两个方法我们比较熟悉了,就是生成了 Bean。
●dispatcherServlet 方法将生成一个 DispatcherServlet 的 Bean 对象。比较简单,就是获取一个实例,然后添加一些属性设置。
●multipartResolver 方法主要是把你配置的 MultipartResolver 的 Bean 给重命名一下,防止你不是用 multipartResolver 这个名字作为 Bean 的名字。

配置 DispatcherServletRegistrationConfiguration
再看注册类的 Bean 配置

@Configuration(proxyBeanMethods = false)
@Conditional(DispatcherServletRegistrationCondition.class)
@ConditionalOnClass(ServletRegistration.class)
@EnableConfigurationProperties(WebMvcProperties.class)
@Import(DispatcherServletConfiguration.class)
protected static class DispatcherServletRegistrationConfiguration {
    //...
}

同样的,@Conditional 有一个前置判断,DispatcherServletRegistrationCondition 主要判断了该注册类的 Bean 是否存在。
@ConditionOnClass 也判断了 ServletRegistration 是否存在
@EnableConfigurationProperties 生成了 WebMvcProperties 的属性对象
@Import 导入了 DispatcherServletConfiguration,也就是我们上面的配置对象。

再看 DispatcherServletRegistrationConfiguration 的内部实现

@Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
@ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet,
                                                                       WebMvcProperties webMvcProperties,
                                                                       ObjectProvider<MultipartConfigElement> multipartConfig) {
    DispatcherServletRegistrationBean registration =
        new DispatcherServletRegistrationBean(dispatcherServlet,
                                              webMvcProperties.getServlet().getPath());
    registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
    registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
    multipartConfig.ifAvailable(registration::setMultipartConfig);
    return registration;
}

内部只有一个方法,生成了 DispatcherServletRegistrationBean。核心逻辑就是实例化了一个 Bean,设置了一些参数,如 dispatcherServlet、loadOnStartup 等

总结
springboot mvc 的自动配置类是 DispatcherServletAutoConfigration,主要做了两件事:
1)配置 DispatcherServlet
2)配置 DispatcherServlet 的注册 Bean(DispatcherServletRegistrationBean)

注册 DispatcherServlet 到 ServletContext

在上一小节的源码翻阅中,我们看到了 DispatcherServlet 和 DispatcherServletRegistrationBean 这两个 Bean 的自动配置。DispatcherServlet 我们很熟悉,DispatcherServletRegistrationBean 负责将 DispatcherServlet 注册到 ServletContext 当中

DispatcherServletRegistrationBean 的类图
既然该类的职责是负责注册 DispatcherServlet,那么我们得知道什么时候触发注册操作。为此,我们先看看 DispatcherServletRegistrationBean 这个类的类图
image.png
注册 DispatcherServlet 流程
ServletContextInitializer
我们看到,最上面是一个 ServletContextInitializer 接口。我们可以知道,实现该接口意味着是用来初始化 ServletContext 的。我们看看该接口

public interface ServletContextInitializer {
    void onStartup(ServletContext servletContext) throws ServletException;
}

看看 RegistrationBean 是怎么实现 onStartup 方法的

@Override
public final void onStartup(ServletContext servletContext) throws
    ServletException {
    String description = getDescription();
    if (!isEnabled()) {
        logger.info(StringUtils.capitalize(description) + " was not registered (disabled)");
        return;
    }
    register(description, servletContext);
}

调用了内部 register 方法,这是一个抽象方法,再看 DynamicRegistrationBean 是怎么实现 register 方法的
再看 ServletRegistrationBean 是怎么实现 addRegistration 方法的

@Override
protected ServletRegistration.Dynamic addRegistration(String description,
                                                      ServletContext servletContext) {
    String name = getServletName();
    return servletContext.addServlet(name, this.servlet);
}

我们看到,这里直接将 DispatcherServlet 给 add 到了 servletContext 当中。

SpringBoot 启动流程中具体体现
getSelfInitializer().onStartup(servletContext)

这段代码其实就是去加载 SpringMVC,那么他是如何做到的呢? getSelfInitializer() 最终会去调用到 ServletWebServerApplicationContext 的 selfInitialize 方法,该方法代码如下
image.png

private void selfInitialize(ServletContext servletContext) throws ServletException {
    prepareWebApplicationContext(servletContext);
    ConfigurableListableBeanFactory beanFactory = getBeanFactory();
    ExistingWebApplicationScopes existingScopes = new ExistingWebApplicationScopes(beanFactory);
    WebApplicationContextUtils.registerWebApplicationScopes(beanFactory, getServletContext());
    existingScopes.restore();
    WebApplicationContextUtils.registerEnvironmentBeans(beanFactory, getServletContext());
    for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
        beans.onStartup(servletContext);
    }
}

我们通过调试,知道 getServletContextInitializerBeans() 返回的是一个 ServletContextInitializer 集合,集合中有以下几个对象
image.png
然后依次去调用对象的 onStartup 方法,那么对于上图标红的对象来说,就是会调用到 DispatcherServletRegistrationBean 的 onStartup 方法,这个类并没有这个方法,所以会调用父类 RegistrationBean 的 onStartup 方法,然后最终调用到 ServletRegistrationBean 的 addRegistration 方法
image.png

总结
SpringBoot 自动装配 SpringMvc 其实就是往 ServletContext 中加入了一个 Dispatcherservlet 。
Servlet3.0 规范中有这个说明,除了可以动态加 Servlet,还可以动态加 Listener,Filter
●addServlet
●addListener
●addFilter


文章作者: CoderXiong
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 CoderXiong !
  目录