Appearance
Spring Boot 源码构建
源码分析的笔记基于 spring-boot-2.2.2.RELEASE 版本
源码下载地址:https://github.com/spring-projects/spring-boot/releases
方式1:maven 命令编译项目
- 进入到下载的源码目录执行如下命令:
bash
mvn -Dmaven.test.skip=true clean install
-Dmaven.test.skip=true在打包过程中会忽略testcase,不然编译过程漫长,而且可能会发生一些异常,初次编译的目的削减掉这一步,如果正常编译通过,后面可以对那测试案例做测试。
- 如果出现报错找不到spring-javaformat插件 执行
mvn spring-javaformat:apply命令即可。 - 如果再出现错误,执行
mvn -Dmaven.test.failure.ignore=true -Dmaven.test.skip=true clean install
使用 mvnwrapper 编译项目
- 进入到下载的源码目录执行命令:
mvnw clean install -DskipTests -Pfast

- 如果出现报错找不到 spring-javaformat 插件 执行
mvn spring-javaformat:apply命令即可。命令执行成功后,再次执行指令:mvnw clean install -DskipTests -Pfast
Spring Boot 自动配置原理分析
Spring Boot 框架是一个将整合框架的整合代码都写好了的框架。所以要知道它的工作原理才能够找到各种整合框架可以配置的属性,以及属性对应的属性名。
准备阶段
Spring Boot 是一个可快速整合各种技术的框架,Spring Boot 会大量收录行业内相关技术的技术相关配置、技术初始化等信息,将其收集整理成一个依赖、配置、技术初始化的技术列表集合
spring-boot-dependencies 父工程依赖管理原理
创建 Spring Boot 项目,继承了 Spring Boot 的父工程 spring-boot-starter-parent 后,查看工程的依赖关系,父工程又依赖了 spring-boot-dependencies 模块,而此 spring-boot-denpendencies 模块中的 pom 文件,管理所有公共 Starter 与相关技术的依赖,并且通过 <dependencyManagement> 标签实现 jar 版本管理
因为继承父工程 spring-boot-starter-parent 后,可以根据需要,直接引用相应的 starter 即可,不需要配置版本号

spring-boot-starters 工程
starters 的原理
starters 是依赖关系的整理和封装,是一套依赖坐标的整合。只要导入相关的 starter 即可该功能及其相关必需的依赖

举例:进行 JPA or Web 开发,只需要导入 spring-boot-starter-data-jpa 或 spring-boot-starter-web 即可
每个 starter 包含了当前功能下的许多必备依赖坐标,这些依赖坐标是项目开发,上线和运行必须的。同时这些依赖也支持依赖传递。例如:spring-boot-starter-web 包含了所有 web 开发必须的依赖坐标

自定义 starter 的命名规范
- 官方的 starter 命名:
spring-boot-starter-* - 非官方的 starter 命名:
thirdpartyproject-spring-boot-starter
官方提供的 starter 详见官方文档:https://docs.spring.io/spring-boot/docs/2.3.3.RELEASE/reference/html/using-spring-boot.html#using-boot-starter
自动配置信息位置说明
每个 starter 都有相应自动配置处理在 spring-boot-autoconfigure 模块中,在此模块的 src/main/resources/META-INF 中定义了所有内置支持的技术框架及其相关的约定的默认配置:
additional-spring-configuration-metadata.json:默认配置,Spring Boot 采用约定大于配置设计思想。spring.factories:定义了自动配置相关的处理类的映射关系。在项目启动的时候会将相关映射的处理类加载到 Spring 容器中


- 在 spring-boot-autoconfigure 模块中,所有支持的框架根据功能类型来划分包,每个包都有一个
XxxAutoConfiguration配置类,都是一个基于纯注解的配置类,是各种框架整合的代码。如图所示:

- 如果配置的框架有默认的配置参数,都放在一个命名为
XxxProperties的属性类,通过@ConfigurationProperties注解将配置文件的属性值绑定到对应的 bean 上。如图所示:

- 通过项目的 resources 下的
application.properties或application.yml文件可以修改每个整合框架的默认属性,从而实现了快速整合的目的。

自动配置流程分析
查看启动类注解 @SpringBootApplication,可以跟踪自动配置加载的实现步骤
自定义配置的注解
Spring Boot 启动注解 @SpringBootApplication 包含若干个注解,其中 @SpringBootConfiguration 与 @ComponentScan 均为 Spring 基础的注解,而实现自动配置的关键是 @EnableAutoConfiguration 注解
java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
// ...
}查看 @EnableAutoConfiguration 注解源码,分别包含了 @AutoConfigurationPackage 与 @Import(AutoConfigurationImportSelector.class)
java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
// ...
}@AutoConfigurationPackage 注解
在 @AutoConfigurationPackage 注解中,通过 @Import 注解导入 AutoConfigurationPackages.Registrar 的内部类
java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
// ...
}AutoConfigurationPackages.Registrar 内部类,实现了 ImportBeanDefinitionRegistrar 接口,Spring 容器启动时会调用 registerBeanDefinitions 方法
java
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
}该方法会分别将当前 @AutoConfigurationPackages 所有位置的包及其子包、与其 basePackages、basePackageClasses 属性配置的都包路径,都注册到 Spring 的 BeanDefinitionRegistry 中进行包扫描


AutoConfigurationImportSelector 类
在 @EnableAutoConfiguration 注解中,通过 @Import 注解引入自动配置处理类 AutoConfigurationImportSelector,该类实现了 DeferredImportSelector 接口,而 DeferredImportSelector 接口又继承了 ImportSelector 接口。Spring 容器初始化时会调用 selectImports 方法
java
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
// 此方法是主要处理逻辑,获取需要加载的bean全限定名集合
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}在 getAutoConfigurationEntry 方法中通过 getCandidateConfigurations 方法获取所有需要加载的 bean 全限定名集合
java
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 通过 getCandidateConfigurations 方法获取所有需要加载的 bean 全限定名集合
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
// 去重处理
configurations = removeDuplicates(configurations);
// 获取不需要加载的 bean,这里我们可以通过 spring.autoconfigure.exclude 人为配置
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = filter(configurations, autoConfigurationMetadata);
// 发送事件,通知所有的 AutoConfigurationImportListener 进行监听
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}跟踪源码可知,最终会读取资源目录中的 META-INF/spring.factories 文件,即前面所说的定义了自动配置相关的处理类的映射关系

读取 spring.factories 文件,获取所有自动处理类全限定名,然而 Spring Boot 不会将所有处理类都实例化,每个配置类上都会有 @ConditionalOn* 的条件注解来控制 bean 的加载,下面以 RedisAutoConfiguration 为例:
java
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
...
}当导入了 spring-boot-starter-data-redis 的依赖后,项目存在 RedisOperations 字节码文件后,此时才会实例化 RedisAutoConfiguration 类

又比如内置 web 容器的处理类 EmbeddedWebServerFactoryCustomizerAutoConfiguration,类上引入 @EnableConfigurationProperties({ServerProperties.class}) 注解,用于加载默认配置类的参数。然后内部类中通过 @ConditionalOnClass 注解来控制加载哪种类型的 web 容器;又比如 mybatis-spring-boot-starter,则会自动配置 SqlSessionFactory、SqlSessionTemplate、DataSource等 MyBatis 所需的组件

小结

自动配置类本质上就是一个配置类而已,只是用 META-INF/spring.factories 管理,与应用配置类解耦。Spring Boot 启动时先加载 spring.factories 文件中的 org.springframework.boot.autoconfigure.EnableAutoConfiguration 配置项,循环配置中每个自动配置类,然后根据定义在类上的 @ConditionalOn* 条件注解来决定哪些 bean 需要加载。
以 @Enable** 开头的注解本质是利用了 @Import 配合 DeferredImportSelector 实现导入,在 selectImports 方法的返回值即为要导入的配置类名。值得注意的是,DeferredImportSelector 接口的导入会在最后执行,其目的是为了让其它配置优先解析
对于正常加载成 bean 的类,通常会通过 @EnableConfigurationProperties 注解初始化对应的配置属性类并加载对应的配置。而配置属性类上通常会通过 @ConfigurationProperties 加载指定前缀的配置,并且这些配置通常都有默认值。
Spring Boot 实现自动配置原理图解(网络资源)

Spring Boot 常见的自动配置实现
以下常见的自动配置类选自 spring-boot-autoconfigure-x.x.x.jar!\META-INF\spring.factories 文件中
AopAutoConfiguration
Spring Boot 是利用了自动配置类来简化了 aop 相关配置。AOP 自动配置类为 org.springframework.boot.autoconfigure.aop.AopAutoConfiguration。但可以通过 spring.aop.auto=false 配置,禁用 aop 自动配置
java
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Advice.class)
static class AspectJAutoProxyingConfiguration {
@Configuration(proxyBeanMethods = false)
@EnableAspectJAutoProxy(proxyTargetClass = false)
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false")
static class JdkDynamicAutoProxyConfiguration {
}
@Configuration(proxyBeanMethods = false)
@EnableAspectJAutoProxy(proxyTargetClass = true)
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",
matchIfMissing = true)
static class CglibAutoProxyConfiguration {
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingClass("org.aspectj.weaver.Advice")
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",
matchIfMissing = true)
static class ClassProxyingConfiguration {
@Bean
static BeanFactoryPostProcessor forceAutoProxyCreatorToUseClassProxying() {
return (beanFactory) -> {
if (beanFactory instanceof BeanDefinitionRegistry) {
BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);
AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
}
};
}
}
}AOP 自动配置的本质是通过 @EnableAspectJAutoProxy 来开启了自动代理,如果在引导类上添加了 @EnableAspectJAutoProxy 注解,则使用者添加的为准。
java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {
// ...
}该注解的本质是向容器中添加了 AnnotationAwareAspectJAutoProxyCreator 这个 bean 后置处理器,它能够找到容器中所有切面,并为匹配切点的目标类创建代理,创建代理的工作一般是在 bean 的初始化阶段完成的
java
class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
/**
* Register, escalate, and configure the AspectJ auto proxy creator based on the value
* of the @{@link EnableAspectJAutoProxy#proxyTargetClass()} attribute on the importing
* {@code @Configuration} class.
*/
@Override
public void registerBeanDefinitions(
AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
AnnotationAttributes enableAspectJAutoProxy =
AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
if (enableAspectJAutoProxy != null) {
if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
}
if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
}
}
}
}java
public abstract class AopConfigUtils {
// ...
@Nullable
public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry) {
return registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry, null);
}
@Nullable
public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(
BeanDefinitionRegistry registry, @Nullable Object source) {
return registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source);
}
// ...
}DataSourceAutoConfiguration
对应的自动配置类为:org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration。它内部采用了条件装配,通过检查容器的 bean,以及类路径下的 class,来决定该 @Bean 是否生效。简单说明一下,Spring Boot 支持两大类数据源:
EmbeddedDatabase:内嵌数据库连接池PooledDataSource:非内嵌数据库连接池
而 PooledDataSource 又支持如下数据源
- hikari 提供的
HikariDataSource - tomcat-jdbc 提供的
DataSource - dbcp2 提供的
BasicDataSource - oracle 提供的
PoolDataSourceImpl
如果知道数据源的实现类类型,即指定了 spring.datasource.type,理论上可以支持所有数据源,但这样做的一个最大问题是无法订制每种数据源的详细配置(如最大、最小连接数等)
java
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
@ConditionalOnMissingBean(type = "io.r2dbc.spi.ConnectionFactory")
@EnableConfigurationProperties(DataSourceProperties.class)
@Import({ DataSourcePoolMetadataProvidersConfiguration.class,
DataSourceInitializationConfiguration.InitializationSpecificCredentialsDataSourceInitializationConfiguration.class,
DataSourceInitializationConfiguration.SharedCredentialsDataSourceInitializationConfiguration.class })
public class DataSourceAutoConfiguration {
@Configuration(proxyBeanMethods = false)
@Conditional(EmbeddedDatabaseCondition.class)
@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
@Import(EmbeddedDataSourceConfiguration.class)
protected static class EmbeddedDatabaseConfiguration {
}
@Configuration(proxyBeanMethods = false)
@Conditional(PooledDataSourceCondition.class)
@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
@Import({ DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class,
DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.OracleUcp.class,
DataSourceConfiguration.Generic.class, DataSourceJmxConfiguration.class })
protected static class PooledDataSourceConfiguration {
}
// ...
}其中 @EnableConfigurationProperties(DataSourceProperties.class) 用于封装 spring.datasource 数据源相关的配置
MybatisAutoConfiguration
MyBatis 自动配置类为 org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
java
@Configuration
@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class }) // 项目中存在此类文件才加载
@ConditionalOnSingleCandidate(DataSource.class) // 当项目只存在一个 DataSource 实例时加载
@EnableConfigurationProperties(MybatisProperties.class) // 封装 mybatis 前缀的配置
@AutoConfigureAfter({ DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class }) // 在
public class MybatisAutoConfiguration implements InitializingBean {
// ...
}它主要配置了两个 bean
SqlSessionFactory:MyBatis 核心对象,用来创建 SqlSession
SqlSessionTemplate:SqlSession 的实现,此实现会与当前线程绑定
用
ImportBeanDefinitionRegistrar的方式扫描所有标注了@Mapper注解的接口用
AutoConfigurationPackages来确定扫描的包MybatisProperties,它会读取配置文件中带mybatis.前缀的配置项进行定制配置
@MapperScan 注解的作用与 MybatisAutoConfiguration 类似,会注册 MapperScannerConfigurer 有如下区别
@MapperScan扫描具体包(当然也可以配置关注哪个注解)@MapperScan如果不指定扫描具体包,则会把引导类范围内,所有接口当做 Mapper 接口MybatisAutoConfiguration关注的是所有标注@Mapper注解的接口,会忽略掉非@Mapper标注的接口
MyBatis 其实并非将接口交给 Spring 管理,而是每个接口会对应一个 MapperFactoryBean,是后者被 Spring 所管理,接口只是作为 MapperFactoryBean 的一个属性来配置
TransactionAutoConfiguration
事务自动配置类有两个:
org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfigurationorg.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration
DataSourceTransactionManagerAutoConfiguration 配置了 DataSourceTransactionManager,用来执行事务的提交、回滚操作
TransactionAutoConfiguration 功能上对标 @EnableTransactionManagement,包含以下三个 bean
BeanFactoryTransactionAttributeSourceAdvisor事务切面类,包含通知和切点TransactionInterceptor事务通知类,由它在目标方法调用前后加入事务操作AnnotationTransactionAttributeSource会解析@Transactional及事务属性,也包含了切点功能
注:如果使用者配置了
DataSourceTransactionManager或是在引导类加了@EnableTransactionManagement,则以自定义的配置为准
ServletWebServerFactoryAutoConfiguration
用于提供 ServletWebServerFactory
DispatcherServletAutoConfiguration
用于提供 DispatcherServlet、DispatcherServletRegistrationBean
WebMvcAutoConfiguration
用于配置 DispatcherServlet 的各项组件,如:多项 HandlerMapping、多项 HandlerAdapter、HandlerExceptionResolver
ErrorMvcAutoConfiguration
用于提供 BasicErrorController
MultipartAutoConfiguration
提供了 org.springframework.web.multipart.support.StandardServletMultipartResolver,用来解析 multipart/form-data 格式的数据
HttpEncodingAutoConfiguration
Spring Boot 已经提供了 org.springframework.boot.web.servlet.filter.OrderedCharacterEncodingFilter,对应配置项为 server.servlet.encoding.charset=UTF-8,默认就是 UTF-8,只影响非 json 格式的数据。当 POST 请求参数如果有中文,无需特殊设置
条件装配实现原理
条件装配的底层是本质上是 @Conditional 注解与 Condition 接口配合应用。
以下是实现原理的小示例,如果项目中引入 druid 依赖则加载指定的 bean,否则不加载。
- 编写条件判断类,实现
Condition接口,在matches方法中指定条件判断逻辑
java
public class MyCondition implements Condition {
// 如果存在 Druid 依赖,条件成立
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return ClassUtils.isPresent("com.alibaba.druid.pool.DruidDataSource", null);
}
}- 在要导入的自动配置类上添加
@Conditional(MyCondition.class),将来此类被导入时就会做条件检查
java
@Configuration // 第三方的配置类
@Conditional(MyCondition.class) // 加入条件
static class AutoConfiguration {
@Bean
public Bean1 bean1() {
return new Bean1();
}
}- 测试,项目加入 druid 依赖后,bean1 存在于容器;移除依赖后,bean2 不存在。
xml
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>x.x.x</version>
</dependency>SpringBoot 项目启动流程
SpringBoot 应用启动流程图

Spring Boot 的启动流程,本质就是运行一个 Spring 容器的环境。总体来说,就是先做容器的初始化,并将对应的 bean 实例初始化后放入容器。Spring Boot 初始化的参数根据参数的提供方,划分成如下3个大类,每个大类的参数又被封装了各种各样的对象,具体如下:
- 环境属性(Environment)
- 系统配置(spring.factories)
- 参数(Arguments、application.properties)
阶段一:SpringApplication 构造
SpringApplication 构造方法中会完成以下的操作:
- 记录
BeanDefinition源 - 推断应用类型
- 记录
ApplicationContext初始化器 - 记录监听器
- 推断主启动类
阶段二:执行 run 方法
- 得到
SpringApplicationRunListeners事件发布器,发布 application starting 事件 - 封装启动 args 参数
- 准备
Environment添加命令行参数,通过PropertySource<T>抽象类的实现SimpleCommandLinePropertySource进行解析 ConfigurationPropertySources处理,发布 application environment 已准备事件- 通过
EnvironmentPostProcessorApplicationListener进行 env 后处理,如读取 application.properties(由StandardConfigDataLocationResolver解析),spring.application.json 等文件ConfigurationPropertySources.attach(Environment environment)方法用于规范统一解析各种环境键名称格式EnvironmentPostProcessor后处理增强- 绑定 spring.main 文件前缀的 key-value 至
SpringApplication对象
- 打印 banner,由
SpringApplicationBannerPrinter实现 - 创建容器
- 准备容器,发布 application context 已初始化事件
- 加载 bean 定义,发布 application prepared 事件
- refresh 容器,发布 application started 事件
- 执行 runner,发布 application ready 事件,若发生异常,则发布 application failed 事件
ConfigurableApplicationContext
添加 Spring Boot 最基础的依赖与编写最基础的入口,启动后用于源码的断点跟踪。
xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-parent</artifactId>
<version>2.2.2.RELEASE</version>
<relativePath>../spring-boot-project/spring-boot-parent</relativePath>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
</dependencies>java
@SpringBootApplication
public class TestApplication {
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
}启动 Spring Boot 项目,实现是执行 SpringApplication.run 的静态方法并返回 ConfigurableApplicationContext 实例
java
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}ConfigurableApplicationContext 接口类关系图

SpringApplication
Spring Boot 启动流程的首先是通过 SpringApplication 类的构造函数,创建其对象,并加载各种配置信息,初始化各种配置对象;
java
public SpringApplication(Class<?>... primarySources) {
this(null, primarySources);
}
@SuppressWarnings({ "unchecked", "rawtypes" })
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
// 初始化资源加载器
this.resourceLoader = resourceLoader;
// 断言主类不为空
Assert.notNull(primarySources, "PrimarySources must not be null");
// 初始化配置类的类名信息(由原来数组转换成 Set 集合)
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// 确认当前容器加载的类型
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// getSpringFactoriesInstances 方法,用于读取资源目录 META-INF/spring.factories 文件
// 获取 ApplicationContextInitializer 类型的实例
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 初始化监听器,对初始化过程及运行过程进行干预
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 初始化了引导类类名信息,备用
this.mainApplicationClass = deduceMainApplicationClass();
}WebApplicationType 加载容器的类型
WebApplicationType 枚举类,定义 Spring Boot 容器的类型
java
public enum WebApplicationType {
/**
* The application should not run as a web application and should not start an
* embedded web server.
*/
NONE,
/**
* The application should run as a servlet-based web application and should start an
* embedded servlet web server.
*/
SERVLET,
/**
* The application should run as a reactive web application and should start an
* embedded reactive web server.
*/
REACTIVE;
...
}其中 WebApplicationType.deduceFromClasspath() 静态方法用于判断当前容器加载的类型

导入不同的 starter,判断不同的类型

getSpringFactoriesInstances 方法
getSpringFactoriesInstances 是 SpringApplication 类的方法,在加载配置的过程中多次被调用
java
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
return getSpringFactoriesInstances(type, new Class<?>[] {});
}
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
// 获取类加载器
ClassLoader classLoader = getClassLoader();
// Use names and ensure unique to protect against duplicates
// 根据类型,获取 spring.factories 文件中配置的相关实现类全限定名称集合
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
// 根据全限定名,实例化
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
// 将实例化的对象排序
AnnotationAwareOrderComparator.sort(instances);
return instances;
}SpringFactoriesLoader.loadFactoryNames(type, classLoader) 方法,读取资源目录中的 META-INF/spring.factories 文件。(前面自动配置原理分析章节中已经看过)

以传入的类型为 key,获取配置中相应的 value,即该类型的所有实现类全限定名称。

自定义监听器
从上面的源码可以看到,在创建 SpringApplication 对象中的 setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); 这里会进行初始化监听器,同样是读取 spring.factories 文件中配置的实现,因此使用者可以自定义监听器的实现,并将监听器类全限名称配置到 spring.factories 文件中即可
- 创建自定义监听器,需要实现
org.springframework.context.ApplicationListener接口
java
public class CustomListener implements ApplicationListener<ApplicationStartingEvent> {
/**
* Handle an application event.
*
* @param event the event to respond to
*/
@Override
public void onApplicationEvent(ApplicationStartingEvent event) {
System.out.println("自定义事件处理逻辑, source: " + event.getSource());
}
}- spring.factories 文件中配置监听器映射
properties
org.springframework.context.ApplicationListener=com.moon.springboot.listener.CustomListener加载到容器中

打印 banner
Spring Boot 在控制台输出 banner 具体由 SpringApplicationBannerPrinter 实现
模拟实现示例
banner 的数据是从环境对象中读取,然后 SpringApplicationBannerPrinter 的 print 方法进行输出打印
java
@Test
public void test() {
ApplicationEnvironment env = new ApplicationEnvironment();
SpringApplicationBannerPrinter printer = new SpringApplicationBannerPrinter(
new DefaultResourceLoader(),
new SpringBootBanner()
);
// 测试文字 banner
// env.getPropertySources().addLast(new MapPropertySource("custom", Map.of("spring.banner.location","banner1.txt")));
// 测试图片 banner
env.getPropertySources().addLast(new MapPropertySource("custom", Map.of("spring.banner.image.location","banner2.png")));
// 版本号的获取
System.out.println(SpringBootVersion.getVersion());
printer.print(env, Step7.class, System.out);
}初始化 Spring 容器
在 SpringApplication 对象创建并加载配置信息、初始化各种配置对象后,然后调用对象的 run(String... args) 方法,用于初始化容器,并得到 ConfigurableApplicationContext 对象,这也是核心部分
java
public ConfigurableApplicationContext run(String... args) {
// 设置计时器
StopWatch stopWatch = new StopWatch();
// 开始计时
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
// 模拟输入输出信号,避免出现因缺少外设导致的信号传输失败,进而引发错误(模拟显示器,键盘,鼠标...)
// 设置 java.awt.headless=true
configureHeadlessProperty();
// 获取当前注册的所有监听器
SpringApplicationRunListeners listeners = getRunListeners(args);
// 执行容器启动中事件的监听器,即调用 ApplicationListener 接口的 onApplicationEvent 方法
listeners.starting();
try {
// 获取启动应用的参数
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 将前期读取的数据加载成了一个环境对象 ConfigurableEnvironment,用来描述信息
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
// 配置忽略的信息,暂不知道什么作用
configureIgnoreBeanInfo(environment);
// 初始化项目启动时的 logo
Banner printedBanner = printBanner(environment);
// 创建容器对象,根据前期配置的容器类型进行判定并创建
context = createApplicationContext();
// 读取 spring.factories 文件,获取 SpringBootExceptionReporter 类型的实例
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
// 对容器进行设置,参数来源于前期的设定
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
// 刷新容器环境
refreshContext(context);
// 刷新完毕后做后置处理
afterRefresh(context, applicationArguments);
// 计时结束
stopWatch.stop();
// 判定是否记录启动时间的日志
if (this.logStartupInfo) {
// 创建日志对应的对象,输出日志信息,包含启动时间
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
// 监听器执行了对应的操作步骤
listeners.started(context);
// 调用运行器
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;
}Spring Boot 监听机制
Spring Boot 启动过程由于存在着不同的处理过程阶段,如果设计接口就要设计十余个标准接口,这样对开发者不友好,同时整体过程管理分散,十余个过程在不同地方调用,管理难度大,过程过于松散。然后 Spring Boot 采用了监听器设计模式来解决此问题
内置监听器
Spring Boot 将自身的启动过程当成一个大的事件,该事件是由若干个小的事件组成的。例如:
org.springframework.boot.context.event.ApplicationStartingEvent- 应用启动事件,在应用运行但未进行任何处理时,将发送 ApplicationStartingEvent
org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent- 环境准备事件,当 Environment 被使用,且上下文创建之前,将发送 ApplicationEnvironmentPreparedEvent
org.springframework.boot.context.event.ApplicationContextInitializedEvent- 上下文初始化事件
org.springframework.boot.context.event.ApplicationPreparedEvent- 应用准备事件,在开始刷新之前,bean 定义被加载之后发送 ApplicationPreparedEvent
org.springframework.context.event.ContextRefreshedEvent- 上下文刷新事件
org.springframework.boot.context.event.ApplicationStartedEvent- 应用启动完成事件,在上下文刷新之后且所有的应用和命令行运行器被调用之前发送 ApplicationStartedEvent
org.springframework.boot.context.event.ApplicationReadyEvent- 应用准备就绪事件,在应用程序和命令行运行器被调用之后,将发出 ApplicationReadyEvent,用于通知应用已经准备处理请求
org.springframework.context.event.ContextClosedEvent- 上下文关闭事件,对应容器关闭
上述列出的仅仅是部分事件,当应用启动后走到某一个过程点时,监听器监听到某个事件触发,就会执行对应的事件。除了系统内置的事件处理,用户还可以根据需要自定义开发当前事件触发时要做的其他动作。
Spring Boot 内嵌 Tomcat 容器
Tomcat 基本结构
Server
└───Service
├───Connector (协议,端口)
└───Engine
└───Host(虚拟主机 localhost)
├───Context1 (应用1,可以设置虚拟路径,“/” 即 url 起始路径;项目磁盘路径,即 docBase )
│ │ index.html
│ └───WEB-INF
│ │ web.xml (servlet, filter, listener) 3.0
│ ├───classes (servlet, controller, service ...)
│ ├───jsp
│ └───lib (第三方 jar 包)
└───Context2 (应用2)
│ index.html
└───WEB-INF
web.xml模拟内嵌 Tomcat 容器实现示例
创建基础 tomcat
- 引入相关依赖
xml
<!-- tomcat 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</dependency>
<!-- servlet 支持 (在 spring boot 中依赖管理) -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
</dependency>- 创建测试使用的 Servlet
java
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html;charset=utf-8");
resp.getWriter().print("<h3>hello, servlet</h3>");
}
}- 创建内嵌 tomcat 核心代码
java
public static void main(String[] args) throws LifecycleException, IOException {
// 1.创建 Tomcat 对象
Tomcat tomcat = new Tomcat();
// 设置 tomcat 基础目录,用于保存一些必须的配置与临时文件
tomcat.setBaseDir("tomcat");
// 2.创建项目文件夹, 即 docBase 文件夹
File docBase = Files.createTempDirectory("boot.").toFile();
docBase.deleteOnExit(); // 在容器关闭时,删除文件夹
/*
* 3.创建 Tomcat 项目, 在 Tomcat 中称为 Context。
* public Context addContext(String contextPath, String docBase)
* contextPath 参数:上下文映射路径,空字符串("")代表根上下文
* docBase 参数:上下文的基础目录,用于静态文件
*/
Context context = tomcat.addContext("", docBase.getAbsolutePath());
// 4.编程式添加 Servlet
context.addServletContainerInitializer(new ServletContainerInitializer() {
@Override
public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException {
ctx.addServlet("helloServlet", new HelloServlet()).addMapping("/hello");
}
}, Collections.emptySet());
// 5.启动 Tomcat
tomcat.start();
// 6.创建连接器,使用 ProtocolHandler 的实现类 Http11Nio2Protocol
Connector connector = new Connector(new Http11Nio2Protocol());
// 设置监听端口
connector.setPort(8080);
tomcat.setConnector(connector);
}集成 Spring 容器
- 添加 Spring MVC 相关依赖
xml
<!-- Spring MVC 核心依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
</dependency>
<!-- spring boot 自动配置依赖,示例用于引入 DispatcherServletRegistrationBean 来注册 DispatcherServlet -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<!-- json 对象转换需要的 jackson 的依赖(参考 spring-boot-starter-web 的依赖)-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>- 创建测试的控制器
java
@RestController
public class DemoController {
@GetMapping("hello2")
public Map<String, Object> hello() {
Map<String, Object> map = new HashMap<>();
map.put("hello2", "hello, spring!");
return map;
}
}- 创建配置类
java
@Configuration
@ComponentScan("com.moon.springboot.tomcat")
public class SpringConfig {
@Bean
public DispatcherServletRegistrationBean registrationBean(DispatcherServlet dispatcherServlet) {
return new DispatcherServletRegistrationBean(dispatcherServlet, "/");
}
// 此例子中必须为 DispatcherServlet 提供 AnnotationConfigWebApplicationContext, 否则会选择 XmlWebApplicationContext 实现
@Bean
public DispatcherServlet dispatcherServlet(WebApplicationContext applicationContext) {
return new DispatcherServlet(applicationContext);
}
@Bean
public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
RequestMappingHandlerAdapter handlerAdapter = new RequestMappingHandlerAdapter();
handlerAdapter.setMessageConverters(Arrays.asList(new MappingJackson2HttpMessageConverter()));
return handlerAdapter;
}
}- 创建 tomcat 关键代码改造
java
public static void main(String[] args) throws LifecycleException, IOException {
// ...
// 创建基于注解的 Spring 容器 AnnotationConfigWebApplicationContext
// 弃用 AnnotationConfigServletWebServerApplicationContext 是因为此容器自带内嵌的 web 容器
AnnotationConfigWebApplicationContext springContext = new AnnotationConfigWebApplicationContext();
springContext.register(SpringConfig.class);
springContext.refresh(); // 手动刷新容器
// 4.编程式添加 Servlet
context.addServletContainerInitializer(new ServletContainerInitializer() {
@Override
public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException {
ctx.addServlet("helloServlet", new HelloServlet()).addMapping("/hello");
// 可以从 Spring 容器中获取到被管理的 DispatcherServlet,并注册
// DispatcherServlet dispatcherServlet = springContext.getBean(DispatcherServlet.class);
// ctx.addServlet("dispatcherServlet", dispatcherServlet).addMapping("/");
// 上面是手动注册一个 DispatcherServlet,为了日后的方便扩展,可以从 Spring 容器中获取所有 ServletRegistrationBean 类型的实例
// 调用其对象的 onStartup 方法,完成所有 DispatcherServlet 的注册
for (ServletRegistrationBean registrationBean : springContext.getBeansOfType(ServletRegistrationBean.class).values()) {
registrationBean.onStartup(ctx);
}
}
}, Collections.emptySet());
// ...
}