参考资料

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+

  1. C:\Users\xiong>java -version
  2. java version "14.0.1" 2020-04-14
  3. Java(TM) SE Runtime Environment (build 14.0.1+7)
  4. Java HotSpot(TM) 64-Bit Server VM (build 14.0.1+7, mixed mode, sharing)
  5. C:\Users\xiong>mvn -version
  6. Apache Maven 3.5.2 (138edd61fd100ec658bfa2d307c43b76940a5d7d; 2017-10-18T15:58:13+08:00)
  7. Maven home: D:\softwares\apache-maven-3.5.2\bin\..
  8. Java version: 14.0.1, vendor: Oracle Corporation
  9. Java home: C:\Program Files\Java\jdk-14.0.1
  10. Default locale: zh_CN, platform encoding: GBK
  11. OS name: "windows 10", version: "10.0", arch: "amd64", family: "windows"

编译源码

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

  1. //跳过测试用例 会下载大量jar包
  2. 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

  1. <!-- Spring Boot父项目依赖管理 -->
  2. <parent>
  3. <groupId>org.springframework.boot</groupId>
  4. <artifactId>spring-boot-starter-parent</artifactId>
  5. <version>2.2.9.RELEASE</version>
  6. <relativePath/> <!-- lookup parent from repository -->
  7. </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 节点

  1. <parent>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-dependencies</artifactId>
  4. <version>${revision}</version>
  5. <relativePath>../../spring-boot-dependencies</relativePath>
  6. </parent>
  7. <artifactId>spring-boot-starter-parent</artifactId>
  8. <packaging>pom</packaging>
  9. <name>Spring Boot Starter Parent</name>
  10. <description>Parent pom providing dependency and plugin management for applications
  11. built with Maven</description>
  12. <properties>
  13. <main.basedir>${basedir}/../../..</main.basedir>
  14. <java.version>1.8</java.version>
  15. <resource.delimiter>@</resource.delimiter> <!-- delimiter that doesn't clash with Spring ${} placeholders -->
  16. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  17. <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
  18. <maven.compiler.source>${java.version}</maven.compiler.source>
  19. <maven.compiler.target>${java.version}</maven.compiler.target>
  20. </properties>

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

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

  1. <resources>
  2. <resource>
  3. <filtering>true</filtering>
  4. <directory>${basedir}/src/main/resources</directory>
  5. <includes>
  6. <include>**/application*.yml</include>
  7. <include>**/application*.yaml</include>
  8. <include>**/application*.properties</include>
  9. </includes>
  10. </resource>
  11. <resource>
  12. <directory>${basedir}/src/main/resources</directory>
  13. <excludes>
  14. <exclude>**/application*.yml</exclude>
  15. <exclude>**/application*.yaml</exclude>
  16. <exclude>**/application*.properties</exclude>
  17. </excludes>
  18. </resource>
  19. </resources>

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

  1. <properties>
  2. <main.basedir>${basedir}/../..</main.basedir>
  3. <!-- Dependency versions -->
  4. <activemq.version>5.15.13</activemq.version>
  5. <antlr2.version>2.7.7</antlr2.version>
  6. <appengine-sdk.version>1.9.81</appengine-sdk.version>
  7. <artemis.version>2.10.1</artemis.version>
  8. <aspectj.version>1.9.6</aspectj.version>
  9. <assertj.version>3.13.2</assertj.version>
  10. <atomikos.version>4.0.6</atomikos.version>
  11. <awaitility.version>4.0.3</awaitility.version>
  12. <bitronix.version>2.1.4</bitronix.version>
  13. <byte-buddy.version>1.10.13</byte-buddy.version>
  14. <caffeine.version>2.8.5</caffeine.version>
  15. <cassandra-driver.version>3.7.2</cassandra-driver.version>
  16. <classmate.version>1.5.1</classmate.version>
  17. <commons-codec.version>1.13</commons-codec.version>
  18. <commons-dbcp2.version>2.7.0</commons-dbcp2.version>
  19. <commons-lang3.version>3.9</commons-lang3.version>
  20. ...
  21. </properties>

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

  1. <dependencyManagement>
  2. <dependencies>
  3. <!-- Spring Boot -->
  4. <dependency>
  5. <groupId>org.springframework.boot</groupId>
  6. <artifactId>spring-boot</artifactId>
  7. <version>${revision}</version>
  8. </dependency>
  9. <dependency>
  10. <groupId>org.springframework.boot</groupId>
  11. <artifactId>spring-boot-test</artifactId>
  12. <version>${revision}</version>
  13. </dependency>
  14. <dependency>
  15. <groupId>org.springframework.boot</groupId>
  16. <artifactId>spring-boot-test-autoconfigure</artifactId>
  17. <version>${revision}</version>
  18. </dependency>
  19. <dependency>
  20. <groupId>org.springframework.boot</groupId>
  21. <artifactId>spring-boot-actuator</artifactId>
  22. <version>${revision}</version>
  23. </dependency>
  24. ...
  25. </dependencies>
  26. </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依赖文件源码,核心代码具体如下

  1. <dependencies>
  2. <dependency>
  3. <groupId>org.springframework.boot</groupId>
  4. <artifactId>spring-boot-starter</artifactId>
  5. </dependency>
  6. <dependency>
  7. <groupId>org.springframework.boot</groupId>
  8. <artifactId>spring-boot-starter-json</artifactId> #json
  9. </dependency>
  10. <dependency>
  11. <groupId>org.springframework.boot</groupId>
  12. <artifactId>spring-boot-starter-tomcat</artifactId> #tomcat
  13. </dependency>
  14. <dependency>
  15. <groupId>org.springframework.boot</groupId>
  16. <artifactId>spring-boot-starter-validation</artifactId>
  17. <exclusions>
  18. <exclusion>
  19. <groupId>org.apache.tomcat.embed</groupId>
  20. <artifactId>tomcat-embed-el</artifactId>
  21. </exclusion>
  22. </exclusions>
  23. </dependency>
  24. <dependency>
  25. <groupId>org.springframework</groupId>
  26. <artifactId>spring-web</artifactId>
  27. </dependency>
  28. <dependency>
  29. <groupId>org.springframework</groupId>
  30. <artifactId>spring-webmvc</artifactId>
  31. </dependency>
  32. </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. //1、
  2. @SpringBootApplication
  3. public class SpringbootMytestApplication {
  4. public static void main(String[] args) {
  5. SpringApplication.run(SpringbootMytestApplication.class, args);
  6. }
  7. }
  8. //2、@SpringBootApplication是一个组合注解
  9. @Target({ElementType.TYPE}) //注解的适用范围,Type表示注解可以描述在类、接口、注解或枚举中
  10. @Retention(RetentionPolicy.RUNTIME) //表示注解的生命周期,Runtime运行时
  11. @Documented //表示注解可以记录在javadoc中
  12. @Inherited //表示可以被子类继承该注解
  13. @SpringBootConfiguration // 标明该类为配置类
  14. @EnableAutoConfiguration // 启动自动配置功能
  15. @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
  16. @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
  17. public @interface SpringBootApplication {
  18. // 根据class来排除特定的类,使其不能加入spring容器,传入参数value类型是class类型。
  19. @AliasFor(annotation = EnableAutoConfiguration.class)
  20. Class<?>[] exclude() default {};
  21. // 根据classname 来排除特定的类,使其不能加入spring容器,传入参数value类型是class的全类名字符串数组。
  22. @AliasFor(annotation = EnableAutoConfiguration.class)
  23. String[] excludeName() default {};
  24. // 指定扫描包,参数是包名的字符串数组。
  25. @AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
  26. String[] scanBasePackages() default {};
  27. // 扫描特定的包,参数类似是Class类型数组。
  28. @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
  29. Class<?>[] scanBasePackageClasses() default {};
  30. }

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

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


@SpringBootConfiguration

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

  1. //3、对@Configuration注解的封装
  2. @Target(ElementType.TYPE)
  3. @Retention(RetentionPolicy.RUNTIME)
  4. @Documented
  5. @Configuration
  6. public @interface SpringBootConfiguration {
  7. }

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


@EnableAutoConfiguration(重点)

  1. package org.springframework.boot.autoconfigure;
  2. @Target(ElementType.TYPE)
  3. @Retention(RetentionPolicy.RUNTIME)
  4. @Documented
  5. @Inherited
  6. @AutoConfigurationPackage //自动配置包
  7. @Import(AutoConfigurationImportSelector.class) //spring的底层注解@Import,将组件导入到IOC容器中
  8. // 告诉SpringBoot开启自动配置功能,这样自动配置才能生效。
  9. public @interface EnableAutoConfiguration {
  10. String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
  11. // 返回不会被导入到 Spring 容器中的类
  12. Class<?>[] exclude() default {};
  13. // 返回不会被导入到 Spring 容器中的类名
  14. String[] excludeName() default {};
  15. }

@AutoConfigurationPackage

  1. @Target(ElementType.TYPE)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. @Inherited
  5. // Spring的底层注解@Import,给容器中导入一个组件
  6. // 导入的组件是AutoConfigurationPackages.Registrar.class
  7. @Import(AutoConfigurationPackages.Registrar.class)
  8. public @interface AutoConfigurationPackage {
  9. }

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

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

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

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

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 方法代码:

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

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

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

分析自动配置的主要逻辑

  1. // AutoConfigurationImportSelector$AutoConfigurationGroup.java
  2. // 这里用来处理自动配置类,比如过滤掉不符合匹配条件的自动配置类
  3. public void process(AnnotationMetadata annotationMetadata,
  4. DeferredImportSelector deferredImportSelector) {
  5. Assert.state(
  6. deferredImportSelector instanceof AutoConfigurationImportSelector,
  7. () -> String.format("Only %s implementations are supported, got %s",
  8. AutoConfigurationImportSelector.class.getSimpleName(),
  9. deferredImportSelector.getClass().getName()));
  10. // 【1】调用getAutoConfigurationEntry方法得到自动配置类放入autoConfigurationEntry对象中
  11. AutoConfigurationEntry autoConfigurationEntry =
  12. ((AutoConfigurationImportSelector) deferredImportSelector)
  13. .getAutoConfigurationEntry(getAutoConfigurationMetadata(),
  14. annotationMetadata);
  15. // 【2】又将封装了自动配置类的autoConfigurationEntry对象装进autoConfigurationEntries集合
  16. this.autoConfigurationEntries.add(autoConfigurationEntry);
  17. // 【3】遍历刚获取的自动配置类
  18. for (String importClassName : autoConfigurationEntry.getConfigurations()) {
  19. // 这里符合条件的自动配置类作为key,annotationMetadata作为值放进entries集合
  20. this.entries.putIfAbsent(importClassName, annotationMetadata);
  21. }
  22. }

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

  1. // AutoConfigurationImportSelector.java
  2. // 获取符合条件的自动配置类,避免加载不必要的自动配置类从而造成内存浪费
  3. protected AutoConfigurationEntry getAutoConfigurationEntry(
  4. AutoConfigurationMetadata autoConfigurationMetadata,
  5. AnnotationMetadata annotationMetadata) {
  6. // 获取是否有配置spring.boot.enableautoconfiguration属性,默认返回true
  7. if (!isEnabled(annotationMetadata)) {
  8. return EMPTY_ENTRY;
  9. }
  10. // 获得@Congiguration标注的Configuration类即被审视introspectedClass的注解数据,
  11. // 比如:@SpringBootApplication(exclude = FreeMarkerAutoConfiguration.class)
  12. // 将会获取到exclude = FreeMarkerAutoConfiguration.class和excludeName=""的注解数据
  13. AnnotationAttributes attributes = getAttributes(annotationMetadata);
  14. // 【1】得到spring.factories文件配置的所有自动配置类
  15. List<String> configurations = getCandidateConfigurations(annotationMetadata,
  16. attributes);
  17. // 利用LinkedHashSet移除重复的配置类
  18. configurations = removeDuplicates(configurations);
  19. // 得到要排除的自动配置类,比如注解属性exclude的配置类
  20. // 比如:@SpringBootApplication(exclude = FreeMarkerAutoConfiguration.class)
  21. // 将会获取到exclude = FreeMarkerAutoConfiguration.class的注解数据
  22. Set<String> exclusions = getExclusions(annotationMetadata, attributes);
  23. // 检查要被排除的配置类,因为有些不是自动配置类,故要抛出异常
  24. checkExcludedClasses(configurations, exclusions);
  25. // 【2】将要排除的配置类移除
  26. configurations.removeAll(exclusions);
  27. // 【3】因为从spring.factories文件获取的自动配置类太多,如果有些不必要的自动配置类都加载进内存,会造成内存浪费,因此这里需要进行过滤
  28. // 注意这里会调用AutoConfigurationImportFilter的match方法来判断是否符合
  29. // @ConditionalOnBean,@ConditionalOnClass或@ConditionalOnWebApplication,后面会重点分析一下
  30. configurations = filter(configurations, autoConfigurationMetadata);
  31. // 【4】获取了符合条件的自动配置类后,此时触发AutoConfigurationImportEvent事件,
  32. // 目的是告诉ConditionEvaluationReport条件评估报告器对象来记录符合条件的自动配置类
  33. // 该事件什么时候会被触发?--> 在刷新容器时调用invokeBeanFactoryPostProcessors后置处理器时触发
  34. fireAutoConfigurationImportEvents(configurations, exclusions);
  35. // 【5】将符合条件和要排除的自动配置类封装进AutoConfigurationEntry对象,并返回
  36. return new AutoConfigurationEntry(configurations, exclusions);
  37. }

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

  1. //1、
  2. protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
  3. AnnotationAttributes attributes) {
  4. // 这个方法需要传入两个参数getSpringFactoriesLoaderFactoryClass()和getBeanClassLoader()
  5. // getSpringFactoriesLoaderFactoryClass()这个方法返回的是EnableAutoConfiguration.class
  6. // getBeanClassLoader()这个方法返回的是beanClassLoader(类加载器)
  7. List<String> configurations =
  8. SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
  9. getBeanClassLoader());
  10. Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
  11. + "are using a custom packaging, make sure that file is correct.");
  12. return configurations;
  13. }

继续点开loadFactory 方法

  1. //2、
  2. public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
  3. String factoryTypeName = factoryType.getName();
  4. return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
  5. }
  6. //3、
  7. private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
  8. MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
  9. if (result != null) {
  10. return result;
  11. } else {
  12. try {
  13. Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
  14. LinkedMultiValueMap result = new LinkedMultiValueMap();
  15. while(urls.hasMoreElements()) {
  16. URL url = (URL)urls.nextElement();
  17. UrlResource resource = new UrlResource(url);
  18. Properties properties = PropertiesLoaderUtils.loadProperties(resource);
  19. Iterator var6 = properties.entrySet().iterator();
  20. while(var6.hasNext()) {
  21. Entry<?, ?> entry = (Entry)var6.next();
  22. String factoryTypeName = ((String)entry.getKey()).trim();
  23. String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
  24. int var10 = var9.length;
  25. for(int var11 = 0; var11 < var10; ++var11) {
  26. String factoryImplementationName = var9[var11];
  27. result.add(factoryTypeName, factoryImplementationName.trim());
  28. }
  29. }
  30. }
  31. cache.put(classLoader, result);
  32. return result;
  33. } catch (IOException var13) {
  34. throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
  35. }
  36. }
  37. }

从代码中我们可以知道,在这个方法中会遍历整个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()过滤方法

  1. // AutoConfigurationImportSelector.java
  2. 
  3. private List<String> filter(List<String> configurations,
  4. AutoConfigurationMetadata autoConfigurationMetadata) {
  5. long startTime = System.nanoTime();
  6. // 将从spring.factories中获取的自动配置类转出字符串数组
  7. String[] candidates = StringUtils.toStringArray(configurations);
  8. // 定义skip数组,是否需要跳过。注意skip数组与candidates数组顺序一一对应
  9. boolean[] skip = new boolean[candidates.length];
  10. boolean skipped = false;
  11. // getAutoConfigurationImportFilters方法:
  12. // 拿到OnBeanCondition,OnClassCondition和OnWebApplicationCondition
  13. // 然后遍历这三个条件类去过滤从spring.factories加载的大量配置类
  14. for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) {
  15. // 调用各种aware方法,将beanClassLoader,beanFactory等注入到filter对象中,
  16. // 这里的filter对象即OnBeanCondition,OnClassCondition或OnWebApplicationCondition
  17. invokeAwareMethods(filter);
  18. // 各种filter来判断每个candidate(这里实质要通过candidate(自动配置类)拿到其标注的
  19. // @ConditionalOnClass,@ConditionalOnBean和@ConditionalOnWebApplication里面的注解值)是否匹配,
  20. // 注意candidates数组与match数组一一对应
  21. /**********************【主线,重点关注】********************************/
  22. boolean[] match = filter.match(candidates, autoConfigurationMetadata);
  23. // 遍历match数组,注意match顺序跟candidates的自动配置类一一对应
  24. for (int i = 0; i < match.length; i++) {
  25. // 若有不匹配的话
  26. if (!match[i]) {
  27. // 不匹配的将记录在skip数组,标志skip[i]为true,也与candidates数组一一对应
  28. skip[i] = true;
  29. // 因为不匹配,将相应的自动配置类置空
  30. candidates[i] = null;
  31. // 标注skipped为true
  32. skipped = true;
  33. }
  34. }
  35. }
  36. //这里表示若所有自动配置类经过OnBeanCondition,OnClassCondition和OnWebApplicationCondition过滤后
  37. //全部都匹配的话,则全部原样返回
  38. if (!skipped) {
  39. return configurations;
  40. }
  41. // 建立result集合来装匹配的自动配置类
  42. List<String> result = new ArrayList<>(candidates.length);
  43. for (int i = 0; i < candidates.length; i++) {
  44. // 若skip[i]为false,则说明是符合条件的自动配置类,此时添加到result集合中
  45. if (!skip[i]) {
  46. result.add(candidates[i]);
  47. }
  48. }
  49. // 打印日志
  50. if (logger.isTraceEnabled()) {
  51. int numberFiltered = configurations.size() - result.size();
  52. logger.trace("Filtered " + numberFiltered + " auto configuration class in "
  53. + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime)
  54. + " ms");
  55. }
  56. // 最后返回符合条件的自动配置类
  57. return new ArrayList<>(result);
  58. }

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 方法是如何进一步有选择的导入自动配置类的。直接看代码:

  1. // AutoConfigurationImportSelector$AutoConfigurationGroup.java
  2. public Iterable<Entry> selectImports() {
  3. if (this.autoConfigurationEntries.isEmpty()) {
  4. return Collections.emptyList();
  5. }
  6. // 这里得到所有要排除的自动配置类的set集合
  7. Set<String> allExclusions = this.autoConfigurationEntries.stream()
  8. .map(AutoConfigurationEntry::getExclusions)
  9. .flatMap(Collection::stream).collect(Collectors.toSet());
  10. // 这里得到经过滤后所有符合条件的自动配置类的set集合
  11. Set<String> processedConfigurations = this.autoConfigurationEntries.stream()
  12. .map(AutoConfigurationEntry::getConfigurations)
  13. .flatMap(Collection::stream)
  14. .collect(Collectors.toCollection(LinkedHashSet::new));
  15. // 移除掉要排除的自动配置类
  16. processedConfigurations.removeAll(allExclusions);
  17. // 对标注有@Order注解的自动配置类进行排序,
  18. return sortAutoConfigurations(processedConfigurations,
  19. getAutoConfigurationMetadata())
  20. .stream()
  21. .map((importClassName) -> new Entry(
  22. this.entries.get(importClassName), importClassName))
  23. .collect(Collectors.toList());
  24. }

可以看到, 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 编码自动配置)为例解释自动配置原理

  1. package org.springframework.boot.autoconfigure.web.servlet;
  2. import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
  3. import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
  4. import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
  5. import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
  6. import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
  7. import org.springframework.boot.autoconfigure.http.HttpProperties;
  8. import org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type;
  9. import org.springframework.boot.context.properties.EnableConfigurationProperties;
  10. import org.springframework.boot.web.server.WebServerFactoryCustomizer;
  11. import org.springframework.boot.web.servlet.filter.OrderedCharacterEncodingFilter;
  12. import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
  13. import org.springframework.context.annotation.Bean;
  14. import org.springframework.context.annotation.Configuration;
  15. import org.springframework.core.Ordered;
  16. import org.springframework.web.filter.CharacterEncodingFilter;
  17. // 表示这是一个配置类,和以前编写的配置文件一样,也可以给容器中添加组件
  18. @Configuration(proxyBeanMethods = false)
  19. // 启动指定类的ConfigurationProperties功能;将配置文件中对应的值和HttpEncodingProperties绑定起来;
  20. @EnableConfigurationProperties(HttpProperties.class)
  21. // Spring底层@Conditional注解,根据不同的条件,如果满足指定的条件,整个配置类里面的配置就会生效。
  22. // 判断当前应用是否是web应用,如果是,当前配置类生效
  23. @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
  24. // 判断当前项目有没有这个CharacterEncodingFilter : SpringMVC中进行乱码解决的过滤器
  25. @ConditionalOnClass(CharacterEncodingFilter.class)
  26. // 判断配置文件中是否存在某个配置 spring.http.encoding.enabled 如果不存在,判断也是成立的
  27. // matchIfMissing = true 表示即使我们配置文件中不配置spring.http.encoding.enabled=true,也是默认生效的
  28. @ConditionalOnProperty(prefix = "spring.http.encoding", value = "enabled", matchIfMissing = true)
  29. public class HttpEncodingAutoConfiguration {
  30. // 它已经和SpringBoot配置文件中的值进行映射了
  31. private final HttpProperties.Encoding properties;
  32. // 只有一个有参构造器的情况下,参数的值就会从容器中拿
  33. public HttpEncodingAutoConfiguration(HttpProperties properties) {
  34. this.properties = properties.getEncoding();
  35. }
  36. @Bean //给容器中添加一个组件,这个组件中的某些值需要从properties中获取
  37. @ConditionalOnMissingBean //判断容器中没有这个组件
  38. public CharacterEncodingFilter characterEncodingFilter() {
  39. CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
  40. filter.setEncoding(this.properties.getCharset().name());
  41. filter.setForceRequestEncoding(this.properties.shouldForce(Type.REQUEST));
  42. filter.setForceResponseEncoding(this.properties.shouldForce(Type.RESPONSE));
  43. return filter;
  44. }
  45. @Bean
  46. public LocaleCharsetMappingsCustomizer localeCharsetMappingsCustomizer() {
  47. return new LocaleCharsetMappingsCustomizer(this.properties);
  48. }
  49. private static class LocaleCharsetMappingsCustomizer
  50. implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory>, Ordered {
  51. private final HttpProperties.Encoding properties;
  52. LocaleCharsetMappingsCustomizer(HttpProperties.Encoding properties) {
  53. this.properties = properties;
  54. }
  55. @Override
  56. public void customize(ConfigurableServletWebServerFactory factory) {
  57. if (this.properties.getMapping() != null) {
  58. factory.setLocaleCharsetMappings(this.properties.getMapping());
  59. }
  60. }
  61. @Override
  62. public int getOrder() {
  63. return 0;
  64. }
  65. }
  66. }

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

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

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

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

精髓

  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的配置如下:

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

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

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

3、run方法执行流程

  1. @SpringBootApplication //来标注一个主程序类,说明这是一个Spring Boot应用
  2. public class MyTestMVCApplication {
  3. public static void main(String[] args) {
  4. SpringApplication.run(MyTestMVCApplication.class, args);
  5. }
  6. }
  7. public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
  8. // 调用重载方法
  9. return run(new Class<?>[] { primarySource }, args);
  10. }
  11. public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
  12. // 两件事:1.初始化SpringApplication
  13. // 2.执行run方法
  14. return new SpringApplication(primarySources).run(args);
  15. }

SpringApplication() 构造方法

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

  1. public SpringApplication(Class<?>... primarySources) {
  2. this(null, primarySources);
  3. }
  4. @SuppressWarnings({"unchecked", "rawtypes"})
  5. public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
  6. //设置资源加载器为null
  7. this.resourceLoader = resourceLoader;
  8. //断言加载资源类不能为null
  9. Assert.notNull(primarySources, "PrimarySources must not be null");
  10. //将primarySources数组转换为List,最后放到LinkedHashSet集合中
  11. this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
  12. //【1.1 推断应用类型,后面会根据类型初始化对应的环境。常用的一般都是servlet环境 】
  13. this.webApplicationType = WebApplicationType.deduceFromClasspath();
  14. //【1.2 初始化classpath下 META-INF/spring.factories中已配置的 ApplicationContextInitializer 】
  15. setInitializers((Collection)getSpringFactoriesInstances(ApplicationContextInitializer.class));
  16. //【1.3 初始化classpath下所有已配置的 ApplicationListener 】
  17. setListeners((Collection)getSpringFactoriesInstances(ApplicationListener.class));
  18. //【1.4 根据调用栈,推断出 main 方法的类名 】
  19. this.mainApplicationClass = deduceMainApplicationClass();
  20. }

deduceFromClasspath()

  1. private static final String[] SERVLET_INDICATOR_CLASSES =
  2. { "javax.servlet.Servlet", "org.springframework.web.context.ConfigurableWebApplicationContext" };
  3. private static final String WEBMVC_INDICATOR_CLASS =
  4. "org.springframework.web.servlet.DispatcherServlet";
  5. private static final String WEBFLUX_INDICATOR_CLASS =
  6. "org.springframework.web.reactive.DispatcherHandler";
  7. private static final String JERSEY_INDICATOR_CLASS =
  8. "org.glassfish.jersey.servlet.ServletContainer";
  9. private static final String SERVLET_APPLICATION_CONTEXT_CLASS =
  10. "org.springframework.web.context.WebApplicationContext";
  11. private static final String REACTIVE_APPLICATION_CONTEXT_CLASS =
  12. "org.springframework.boot.web.reactive.context.ReactiveWebApplicationContext";
  13. /**
  14. * 判断 应用的类型
  15. * NONE: 应用程序不是web应用,也不应该用web服务器去启动
  16. * SERVLET: 应用程序应作为基于 servlet 的web应用程序运行,并应启动嵌入式 servlet web(tomcat)服务器。
  17. * REACTIVE: 应用程序应作为 reactive 的web应用程序运行,并应启动嵌入式 reactive web服务器。
  18. * @return
  19. */
  20. static WebApplicationType deduceFromClasspath() {
  21. //classpath下必须存在org.springframework.web.reactive.DispatcherHandler
  22. if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
  23. && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
  24. return WebApplicationType.REACTIVE;
  25. }
  26. for (String className : SERVLET_INDICATOR_CLASSES) {
  27. //classpath环境下不存在javax.servlet.Servlet或者org.springframework.web.context.ConfigurableWebApplicationContext
  28. if (!ClassUtils.isPresent(className, null)) {
  29. return WebApplicationType.NONE;
  30. }
  31. }
  32. return WebApplicationType.SERVLET;
  33. }

返回类型是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

  1. private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
  2. return getSpringFactoriesInstances(type, new Class<?>[]{});
  3. }
  4. /**
  5. * 通过指定的classloader 从META-INF/spring.factories获取指定的Spring的工厂实例
  6. */
  7. private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
  8. Class<?>[] parameterTypes,
  9. Object... args) {
  10. ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
  11. // Use names and ensure unique to protect against duplicates
  12. //通过指定的classLoader从 META-INF/spring.factories 的资源文件中,
  13. //读取 key 为 type.getName() 的 value
  14. Set<String> names = new LinkedHashSet<> (SpringFactoriesLoader.loadFactoryNames(type, classLoader));
  15. //创建Spring工厂实例
  16. List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
  17. //对Spring工厂实例排序(org.springframework.core.annotation.Order注解指定的顺序)
  18. AnnotationAwareOrderComparator.sort(instances);
  19. return instances;
  20. }

看看 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方法的执行流程步骤如下:

  1. /**
  2. * Run the Spring application, creating and refreshing a new
  3. * {@link ApplicationContext}.
  4. *
  5. * @param args the application arguments (usually passed from a Java mainmethod)
  6. * @return a running {@link ApplicationContext}
  7. *
  8. * 运行spring应用,并刷新一个新的 ApplicationContext(Spring的上下文)
  9. * ConfigurableApplicationContext 是 ApplicationContext 接口的子接口。在ApplicationContext
  10. * 基础上增加了配置上下文的工具。 ConfigurableApplicationContext是容器的高级接口
  11. */
  12. public ConfigurableApplicationContext run(String... args) {
  13. //StopWatch主要是用来统计每项任务执行时长,例如Spring Boot启动占用总时长。
  14. StopWatch stopWatch = new StopWatch();
  15. stopWatch.start();
  16. // ConfigurableApplicationContext Spring 的上下文
  17. ConfigurableApplicationContext context = null;
  18. Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
  19. configureHeadlessProperty();
  20. //【1】获取并启动监听器
  21. //通过加载META-INF/spring.factories 完成了SpringApplicationRunListener实例化工作
  22. SpringApplicationRunListeners listeners = getRunListeners(args);
  23. //实际上是调用了EventPublishingRunListener类的starting()方法
  24. listeners.starting();
  25. try {
  26. ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
  27. //【2】构造容器环境
  28. //简而言之就是加载系统变量,环境变量,配置文件
  29. ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
  30. //设置需要忽略的bean
  31. configureIgnoreBeanInfo(environment);
  32. //打印banner
  33. Banner printedBanner = printBanner(environment);
  34. //【3】创建容器
  35. context = createApplicationContext();
  36. //实例化SpringBootExceptionReporter.class,用来支持报告关于启动的错误
  37. exceptionReporters = getSpringFactoriesInstances(
  38. SpringBootExceptionReporter.class,
  39. new Class[]{ConfigurableApplicationContext.class}, context);
  40. //【54】准备容器
  41. //这一步主要是在容器刷新之前的准备动作。包含一个非常关键的操作:将启动类注入容器,为后续开启自动化配置奠定基础。
  42. prepareContext(context, environment, listeners, applicationArguments, printedBanner);
  43. //【5】刷新容器
  44. //springBoot相关的处理工作已经结束,接下的工作就交给了spring。内部会调用spring的refresh方法,
  45. // refresh方法在spring整个源码体系中举足轻重,是实现ioc和aop的关键。
  46. refreshContext(context);
  47. //【6】刷新容器后的扩展接口
  48. //设计模式中的模板方法,默认为空实现。如果有自定义需求,可以重写该方法。
  49. //比如打印一些启动结束log,或者一些其它后置处理。
  50. afterRefresh(context, applicationArguments);
  51. //时间记录停止
  52. stopWatch.stop();
  53. if (this.logStartupInfo) {
  54. new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
  55. }
  56. //发布容器启动完成事件
  57. listeners.started(context);
  58. //遍历所有注册的ApplicationRunner和CommandLineRunner,并执行其run()方法。
  59. //我们可以实现自己的ApplicationRunner或者CommandLineRunner,来对SpringBoot的启动过程进行扩展。
  60. callRunners(context, applicationArguments);
  61. } catch (Throwable ex) {
  62. handleRunFailure(context, ex, exceptionReporters, listeners);
  63. throw new IllegalStateException(ex);
  64. }
  65. try {
  66. //应用已经启动完成的监听事件
  67. listeners.running(context);
  68. } catch (Throwable ex) {
  69. handleRunFailure(context, ex, exceptionReporters, null);
  70. throw new IllegalStateException(ex);
  71. }
  72. return context;
  73. }

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

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

    1、获取并启动监听器

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

    1. private SpringApplicationRunListeners getRunListeners(String[] args) {
    2. Class<?>[] types = new Class<?>[]{SpringApplication.class, String[].class};
    3. return new SpringApplicationRunListeners(logger,
    4. getSpringFactoriesInstances(
    5. SpringApplicationRunListener.class, types, this, args));
    6. }

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

    1. /**
    2. * 通过指定的classloader 从META-INF/spring.factories获取指定的Spring的工厂实例
    3. */
    4. private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
    5. Class<?>[] parameterTypes,
    6. Object... args) {
    7. ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    8. // Use names and ensure unique to protect against duplicates
    9. //通过指定的classLoader从 META-INF/spring.factories 的资源文件中,
    10. //读取 key 为 type.getName() 的 value
    11. Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    12. //创建Spring工厂实例
    13. List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
    14. classLoader, args, names);
    15. //对Spring工厂实例排序(org.springframework.core.annotation.Order注解指定的顺序)
    16. AnnotationAwareOrderComparator.sort(instances);
    17. return instances;
    18. }

    回到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()方法。

  1. private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
  2. ApplicationArguments applicationArguments) {
  3. // Create and configure the environment
  4. //创建并配置相应的环境
  5. ConfigurableEnvironment environment = getOrCreateEnvironment();
  6. //根据用户配置,配置 environment系统环境
  7. configureEnvironment(environment, applicationArguments.getSourceArgs());
  8. // 启动相应的监听器,其中一个重要的监听器 ConfigFileApplicationListener
  9. // 就是加载项目配置文件的监听器。
  10. listeners.environmentPrepared(environment);
  11. bindToSpringApplication(environment);
  12. if (this.webApplicationType == WebApplicationType.NONE) {
  13. environment = new EnvironmentConverter(getClassLoader())
  14. .convertToStandardEnvironmentIfNecessary(environment);
  15. }
  16. ConfigurationPropertySources.attach(environment);
  17. return environment;
  18. }

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

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

  1. private ConfigurableEnvironment getOrCreateEnvironment() {
  2. if (this.environment != null) {
  3. return this.environment;
  4. }
  5. //如果应用类型是 SERVLET 则实例化 StandardServletEnvironment
  6. if (this.webApplicationType == WebApplicationType.SERVLET) {
  7. return new StandardServletEnvironment();
  8. }
  9. return new StandardEnvironment();
  10. }

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容器。

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-web</artifactId>
  4. </dependency>

Servlet容器的使用

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

切换servlet容器

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

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-web</artifactId>
  4. <exclusions>
  5. <!--移除spring-boot-starter-web中的tomcat-->
  6. <exclusion>
  7. <artifactId>spring-boot-starter-tomcat</artifactId>
  8. <groupId>org.springframework.boot</groupId>
  9. </exclusion>
  10. </exclusions>
  11. </dependency>
  12. <!--引入jetty-->
  13. <dependency>
  14. <groupId>org.springframework.boot</groupId>
  15. <artifactId>spring-boot-starter-jetty</artifactId>
  16. </dependency>

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

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

那些看似简单的事物,其实并不简单。我们之所以觉得他简单,是因为复杂性都被隐藏了。
通过上述代码,大概率可以提出以下几个疑问
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

  1. protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
  2. AnnotationMetadata annotationMetadata) {
  3. // 获取是否有配置spring.boot.enableautoconfiguration属性,默认返回true
  4. if (!isEnabled(annotationMetadata)) {
  5. return EMPTY_ENTRY;
  6. }
  7. //从启动类@SpringBootApplication 注解 获取属性值exclude 和 excludeName
  8. AnnotationAttributes attributes = getAttributes(annotationMetadata);
  9. // 【1】得到spring.factories文件配置的所有自动配置类
  10. List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
  11. //利用LinkedHashSet移除重复的配置类
  12. configurations = removeDuplicates(configurations);
  13. //得到要排除的自动配置类,比如注解属性exclude的配置类
  14. // 比如:@SpringBootApplication(exclude = FreeMarkerAutoConfiguration.class)
  15. // 将会获取到exclude = FreeMarkerAutoConfiguration.class的注解数据
  16. Set<String> exclusions = getExclusions(annotationMetadata, attributes);
  17. // 检查要被排除的配置类,因为有些不是自动配置类,故要抛出异常
  18. checkExcludedClasses(configurations, exclusions);
  19. // 【2】将要排除的配置类移除
  20. configurations.removeAll(exclusions);
  21. // 【3】因为从spring.factories文件获取的自动配置类太多,
  22. // 如果有些不必要的自动配置类都加载进内存,会造成内存浪费,因此这里需要进行过滤(根据一些条件如@ConditionalXxx)
  23. configurations = filter(configurations, autoConfigurationMetadata);
  24. // 【4】获取了符合条件的自动配置类后,此时触发AutoConfigurationImportEvent事件,
  25. // 目的是告诉ConditionEvaluationReport条件评估报告器对象来记录符合条件的自动配置类
  26. fireAutoConfigurationImportEvents(configurations, exclusions);
  27. // 【5】将符合条件和要排除的自动配置类封装进AutoConfigurationEntry对象,并返回
  28. return new AutoConfigurationEntry(configurations, exclusions);
  29. }

这个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中配置如下配置

  1. <servlet>
  2. <description>spring mvc servlet</description>
  3. <servlet-name>springMvc</servlet-name>
  4. <servlet-class>
  5. org.springframework.web.servlet.DispatcherServlet
  6. </servlet-class>
  7. <load-on-startup>1</load-on-startup>
  8. </servlet>
  9. <servlet-mapping>
  10. <servlet-name>springMvc</servlet-name>
  11. <url-pattern>*.do</url-pattern>
  12. </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自动配置类

  1. @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
  2. @Configuration(proxyBeanMethods = false)
  3. @ConditionalOnWebApplication(type = Type.SERVLET)
  4. @ConditionalOnClass(DispatcherServlet.class)
  5. @AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)
  6. public class DispatcherServletAutoConfiguration {
  7. //...
  8. }

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这个配置类

  1. @Configuration(proxyBeanMethods = false)
  2. @Conditional(DefaultDispatcherServletCondition.class)
  3. @ConditionalOnClass(ServletRegistration.class)
  4. @EnableConfigurationProperties({ HttpProperties.class,
  5. WebMvcProperties.class })
  6. protected static class DispatcherServletConfiguration {
  7. //...
  8. }

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

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

  1. @Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
  2. public DispatcherServlet dispatcherServlet(HttpProperties httpProperties,
  3. WebMvcProperties webMvcProperties) {
  4. DispatcherServlet dispatcherServlet = new DispatcherServlet();
  5. dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
  6. dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());
  7. dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound());
  8. dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());
  9. dispatcherServlet.setEnableLoggingRequestDetails(httpProperties.isLogRequestDetails());
  10. return dispatcherServlet;
  11. }
  12. @Bean
  13. @ConditionalOnBean(MultipartResolver.class)
  14. @ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
  15. public MultipartResolver multipartResolver(MultipartResolver resolver) {
  16. // Detect if the user has created a MultipartResolver but named itincorrectly
  17. return resolver;
  18. }

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

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

  1. @Configuration(proxyBeanMethods = false)
  2. @Conditional(DispatcherServletRegistrationCondition.class)
  3. @ConditionalOnClass(ServletRegistration.class)
  4. @EnableConfigurationProperties(WebMvcProperties.class)
  5. @Import(DispatcherServletConfiguration.class)
  6. protected static class DispatcherServletRegistrationConfiguration {
  7. //...
  8. }

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

再看DispatcherServletRegistrationConfiguration的内部实现

  1. @Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
  2. @ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
  3. public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet,
  4. WebMvcProperties webMvcProperties,
  5. ObjectProvider<MultipartConfigElement> multipartConfig) {
  6. DispatcherServletRegistrationBean registration =
  7. new DispatcherServletRegistrationBean(dispatcherServlet,
  8. webMvcProperties.getServlet().getPath());
  9. registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
  10. registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
  11. multipartConfig.ifAvailable(registration::setMultipartConfig);
  12. return registration;
  13. }

内部只有一个方法,生成了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的。我们看看该接口

  1. public interface ServletContextInitializer {
  2. void onStartup(ServletContext servletContext) throws ServletException;
  3. }

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

  1. @Override
  2. public final void onStartup(ServletContext servletContext) throws
  3. ServletException {
  4. String description = getDescription();
  5. if (!isEnabled()) {
  6. logger.info(StringUtils.capitalize(description) + " was not registered (disabled)");
  7. return;
  8. }
  9. register(description, servletContext);
  10. }

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

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

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

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

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

  1. private void selfInitialize(ServletContext servletContext) throws ServletException {
  2. prepareWebApplicationContext(servletContext);
  3. ConfigurableListableBeanFactory beanFactory = getBeanFactory();
  4. ExistingWebApplicationScopes existingScopes = new ExistingWebApplicationScopes(beanFactory);
  5. WebApplicationContextUtils.registerWebApplicationScopes(beanFactory, getServletContext());
  6. existingScopes.restore();
  7. WebApplicationContextUtils.registerEnvironmentBeans(beanFactory, getServletContext());
  8. for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
  9. beans.onStartup(servletContext);
  10. }
  11. }

我们通过调试,知道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