参考资料
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
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,会出现下面问题
3、新建一个Module
注意:要将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官方文档查找
列出了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 {};
}
可以添加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() 进行检索,看看其结果是什么?
再看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);
}
}
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 )中。
可以看到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
是一个非常重要的类(需要重点关注!!!)
再进入到AutoConfigurationImportSelector$AutoConfigurationGroup的process方法:
通过图中我们可以看到,跟自动配置逻辑相关的入口方法在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的默认提供的自动配置类。
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);
}
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容器中
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 {
...
}
精髓
- SpringBoot 启动会加载大量的自动配置类
- 我们看我们需要实现的功能有没有SpringBoot 默认写好的自动配置类
- 我们再来看这个自动配置类中到底配置了哪些组件(只要我们有我们要用的组件,我们就不需要再来配置了)
- 给容器中自动配置类添加组件的时候,会从properties 类中获取某些属性,我们就可以在配置文件中指定这些属性的值。
- xxxAutoConfiguration :自动配置类,用于给容器中添加组件从而代替之前我们手动完成大量繁琐的配置。
- 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 有三个枚举,具体的判断逻辑如下:
- WebApplicationType.REACTIVE:classpath下存在 org.springframework.web.reactive.DispatcherHandler
- WebApplicationType.SERVLET:classpath下存在 javax.servlet.Servlet 或者org.springframework.web.context.ConfigurableWebApplicationContext
- 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看看都获取到了哪些
上面说了,是从classpath下 META-INF/spring.factories中获取,我们验证一下:
发现在上图所示的两个工程中找到了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、获取并启动监听器
构造应用上下文环境
初始化应用上下文
刷新应用上下文前的准备阶段
刷新应用上下文
刷新应用上下文后的扩展接口
事件机制在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); 看一下获取的是哪个监听器:
EventPublishingRunListener监听器是Spring容器的启动监听器。
listeners.starting(); 开启了监听事件。
看一下spring.factories都放在哪里
这里开启的监听器所在的路径为:
spring-boot-2.2.9.RELEASE\spring-boot-project\spring-boot\src\main\resources\META-INF\spring.factories
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();
}
从上面的继承关系可以看出,StandardServletEnvironment是StandardEnvironment的子类。这两个对象也没什么好讲的,当是web项目的时候,环境上会多一些关于web环境的配置。
3、初始化应用上下文
补充:SPI机制
- 例1:Java连接数据库:
- 例2:springboot自动装配
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中有什么
核心就是引入了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源码,如下图
2、继续点进@EnableAutoConfiguration,进入该注解,如下图
3、上图中使用@Import注解对AutoConfigurationImportSelector 类进行了引入,该类做了什么事情呢?
进入源码,首先调用selectImport()方法,在该方法中调用了getAutoConfigurationEntry()方法,在之中又调用了getCandidateConfigurations()方法,getCandidateConfigurations()方法就去META-INF/spring.factory配置文件中加载相关配置类
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的配置文件
继续打开spring.factories配置文件,找到tomcat所在的类,tomcat加载在
进入该类,里面也通过@Import注解将EmbeddedTomcat、EmbeddedJetty、EmbeddedUndertow等嵌入式容器类加载进来了,springboot默认是启动嵌入式tomcat容器,如果要改变启动jetty或者undertow容器,需在pom文件中去设置。如下图:
继续进入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这个类的类图
注册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 方法,该方法代码如下
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 集合,集合中有以下几个对象
然后依次去调用对象的onStartup 方法,那么对于上图标红的对象来说,就是会调用到 DispatcherServletRegistrationBean 的onStartup 方法,这个类并没有这个方法,所以会调用父类RegistrationBean 的onStartup 方法,然后最终调用到ServletRegistrationBean的addRegistration方法
总结
SpringBoot自动装配SpringMvc其实就是往ServletContext中加入了一个Dispatcherservlet 。
Servlet3.0规范中有这个说明,除了可以动态加Servlet,还可以动态加Listener,Filter
●addServlet
●addListener
●addFilter