Spring Boot 源码剖析
Spring Boot依赖管理
问题:(1)为什么导入dependency时不需要指定版本?
Spring Boot项目的父项目依赖spring-boot-starter-parent
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.1</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
spring-boot-starter-parent中定义了jdk版本,源文件编码方式,Maven打包编译版本等。
<properties>
<java.version>1.8</java.version>
<resource.delimiter>@</resource.delimiter>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>
并且在build标签中定义了resource资源和pluginManagement
<resources>
<resource>
<directory>${basedir}/src/main/resources</directory>
<filtering>true</filtering>
<includes>
<include>**/application*.yml</include>
<include>**/application*.yaml</include>
<include>**/application*.properties</include>
</includes>
</resource>
<resource>
<directory>${basedir}/src/main/resources</directory>
<excludes>
<exclude>**/application*.yml</exclude>
<exclude>**/application*.yaml</exclude>
<exclude>**/application*.properties</exclude>
</excludes>
</resource>
</resources>
里面定义了资源过滤,针对 application 的 yml 、 properties 格式进行了过滤,可以支持不同环境的配置,比如 application-dev.yml 、 application-test.yml 、 application-dev.properties 、 application-dev.properties 等等。
spring-boot-starter-parent项目的父项目是spring-boot-dependencies
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.5.1</version>
</parent>
在这个项目中定义了大量的依赖项目的版本
<properties>
<activemq.version>5.16.2</activemq.version>
<antlr2.version>2.7.7</antlr2.version>
<appengine-sdk.version>1.9.89</appengine-sdk.version>
<artemis.version>2.17.0</artemis.version>
<aspectj.version>1.9.6</aspectj.version>
<assertj.version>3.19.0</assertj.version>
<atomikos.version>4.0.6</atomikos.version>
<awaitility.version>4.0.3</awaitility.version>
<build-helper-maven-plugin.version>3.2.0</build-helper-maven-plugin.version>
<byte-buddy.version>1.10.22</byte-buddy.version>
......
</properties>
spring-boot-dependencies的dependencyManagement节点
在这里,dependencies定义了SpringBoot版本的依赖的组件以及相应版本。
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-amqp</artifactId>
<version>${activemq.version}</version>
</dependency>
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-blueprint</artifactId>
<version>${activemq.version}</version>
</dependency>
.......
</dependencyManagement>
因为存在这个依赖关系所以在我们创建的Spring Boot项目中部分依赖不需要写版本号。
(2)问题2: spring-boot-starter-parent父依赖启动器的主要作用是进行版本统一管理,那么项目
运行依赖的JAR包是从何而来的?
spring-boot-starter-web
查看spring-boot-starter-web依赖文件源码,核心代码具体如下
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.5.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
<version>2.5.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<version>2.5.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.3.8</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.8</version>
<scope>compile</scope>
</dependency>
可见在spring-boot-starter-web依赖启动器中打包了web开发所需要的底层所有依赖。
因此在引入spring-boot-starter-web依赖启动器时,就可以实现web场景开发,而不需要额外导入tomcat服务器及其他web依赖文件。
Spring Boot除了提供Web依赖启动器之外,还提供了其他很多依赖启动器。具体可以到官网查找。
自动配置
自动配置:根据我们添加的jar包依赖,Spring Boot会将一些配置类的bean注册进ioc容器,我们可以在需要的地方直接使用。
Spring Boot是如何进行自动配置的,都把哪些组件进行了自动配置?
Spring Boot的启动入口是一个被@SpringBootApplication注解的类的main方法。
@SpringBootApplication
查看@SpringBootApplication源码:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.boot.autoconfigure;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.context.TypeExcludeFilter;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.core.annotation.AliasFor;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration // 标明该类为配置类
@EnableAutoConfiguration // 启动自动配置功能
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
// 根据class来排除特定的类,使其不能加入spring容器,传入参数value类型是class类型。
@AliasFor(
annotation = EnableAutoConfiguration.class
)
Class<?>[] exclude() default {};
// 根据classname 来排除特定的类,使其不能加入spring容器,传入参数value类型是class的全 类名字符串数组。
@AliasFor(
annotation = EnableAutoConfiguration.class
)
String[] excludeName() default {};
// 指定扫描包,参数是包名的字符串数组。
@AliasFor(
annotation = ComponentScan.class,
attribute = "basePackages"
)
String[] scanBasePackages() default {};
// 扫描特定的包,参数类似是Class类型数组。
@AliasFor(
annotation = ComponentScan.class,
attribute = "basePackageClasses"
)
Class<?>[] scanBasePackageClasses() default {};
@AliasFor(
annotation = ComponentScan.class,
attribute = "nameGenerator"
)
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
@AliasFor(
annotation = Configuration.class
)
boolean proxyBeanMethods() default true;
}
从源码可以看出,@SpringBootApplication是一个组合注解,前面4个是注解的元数据信息,后面三个注解是核心:@SpringBootConfiguration、@EnableAutoConfiguration、
@ComponentScan
@SpringBootConfiguration
查看源码:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@Indexed
public @interface SpringBootConfiguration {
@AliasFor(
annotation = Configuration.class
)
boolean proxyBeanMethods() default true;
}
从源码看到,它是一个被@Configuration标记的注解,没有其他的代码。说明它其实就是一个@Configuration的包装,重新命名,功能相同。
被@SpringBootConfiguration 标记的类就是一个配置类。
@EnableAutoConfiguration
查看源码:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.boot.autoconfigure;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Import;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
// 自动配置包
@AutoConfigurationPackage
// Spring的底层注解@Import,给容器中导入一个组件;
// 导入的组件是AutoConfigurationPackages.Registrar.class
@Import({AutoConfigurationImportSelector.class})
// 告诉SpringBoot开启自动配置功能,这样自动配置才能生效。
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
// 返回不会被导入到 Spring 容器中的类
Class<?>[] exclude() default {};
// 返回不会被导入到 Spring 容器中的类名
String[] excludeName() default {};
}
@EnableAutoConfiguration是一个组合注解:是@AutoConfigurationPackage 和@Import的组合。
Spring中很多以Enable开头的注解,其作用就是借助@Import来收集并注册特定场景相关的Bean,并加载到IOC容器。
@EnableAutoConfiguration就是借助@Import来收集所有符合自动配置条件的bean定义,并加载到IoC容器。
它引入了AutoConfigurationImportSelector类。
下面我们继续深入到@AutoConfigurationPackage 和@Import这两个注解中去看看它们做了什么工作。
@AutoConfigurationPackage
查看源码:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.boot.autoconfigure;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.boot.autoconfigure.AutoConfigurationPackages.Registrar;
import org.springframework.context.annotation.Import;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({Registrar.class})// 导入Registrar中注册的组件
public @interface AutoConfigurationPackage {
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
}
@AutoConfigurationPackage 注解引入Registrar类
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
Registrar() {
}
/**
* 注册BeanDefinition
*/
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// 根据注解元信息,拿到要扫描的包,扫描并注册到BeanDefinitionRegistry中
AutoConfigurationPackages.register(registry, (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0]));
}
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new AutoConfigurationPackages.PackageImports(metadata));
}
}
register方法:
public static void register(BeanDefinitionRegistry registry, String... packageNames) {
// 第一次进到这个方法的时候private static final String BEAN = AutoConfigurationPackages.class.getName(); 并没有在register中注册。
if (registry.containsBeanDefinition(BEAN)) {
AutoConfigurationPackages.BasePackagesBeanDefinition beanDefinition = (AutoConfigurationPackages.BasePackagesBeanDefinition)registry.getBeanDefinition(BEAN);
beanDefinition.addBasePackages(packageNames);
} else {
// 注册BeanDefinition
registry.registerBeanDefinition(BEAN, new AutoConfigurationPackages.BasePackagesBeanDefinition(packageNames));
}
}
在register中注册的是AutoConfigurationPackages的内部类BasePackagesBeanDefinition。
BasePackagesBeanDefinition源码:
static final class BasePackagesBeanDefinition extends GenericBeanDefinition {
private final Set<String> basePackages = new LinkedHashSet();
BasePackagesBeanDefinition(String... basePackages) {
// Class对象是BasePackages
this.setBeanClass(AutoConfigurationPackages.BasePackages.class);
this.setRole(2);
// 保存基础包
this.addBasePackages(basePackages);
}
public Supplier<?> getInstanceSupplier() {
return () -> {
return new AutoConfigurationPackages.BasePackages(StringUtils.toStringArray(this.basePackages));
};
}
private void addBasePackages(String[] additionalBasePackages) {
this.basePackages.addAll(Arrays.asList(additionalBasePackages));
}
}
static final class BasePackages {
private final List<String> packages;
private boolean loggedBasePackageInfo;
BasePackages(String... names) {
List<String> packages = new ArrayList();
String[] var3 = names;
int var4 = names.length;
for(int var5 = 0; var5 < var4; ++var5) {
String name = var3[var5];
if (StringUtils.hasText(name)) {
packages.add(name);
}
}
this.packages = packages;
}
List<String> get() {
if (!this.loggedBasePackageInfo) {
if (this.packages.isEmpty()) {
if (AutoConfigurationPackages.logger.isWarnEnabled()) {
AutoConfigurationPackages.logger.warn("@EnableAutoConfiguration was declared on a class in the default package. Automatic @Repository and @Entity scanning is not enabled.");
}
} else if (AutoConfigurationPackages.logger.isDebugEnabled()) {
String packageNames = StringUtils.collectionToCommaDelimitedString(this.packages);
AutoConfigurationPackages.logger.debug("@EnableAutoConfiguration was declared on a class in the package '" + packageNames + "'. Automatic @Repository and @Entity scanning is enabled.");
}
this.loggedBasePackageInfo = true;
}
return this.packages;
}
}
BasePackagesBeanDefinition 继承了GenericBeanDefinition,说明它就是一个Spring的BeanDefinition。只是在这个BeanDefinition中保存的Class并不是AutoConfigurationPackages,而是
BasePackages。
因此@EnableAutoConfiguration中的@AutoConfigurationPackage使用@Import({Registrar.class})注解,向Ioc容器中注册了一个BasePackages的Bean定义。
用图来描述一下整个过程:
@Import({AutoConfigurationImportSelector.class})
@Import({AutoConfigurationImportSelector.class}) :将
AutoConfigurationImportSelector 这个类导入到 Spring 容器中,
AutoConfigurationImportSelector 可以帮助 Springboot 应用将所有符合条件的 @Configuration 配置都加载到当前 SpringBoot 创建并使用的 IOC 容器( ApplicationContext )中。
可以看到 AutoConfigurationImportSelector 重点是实现了 DeferredImportSelector 接口和各种 Aware 接口,然后 DeferredImportSelector 接口又继承了 ImportSelector 接口。
其不光实现了 ImportSelector 接口,还实现了很多其它的 Aware 接口,分别表示在某个时机会被回调。
自动配置实现逻辑的入口方法:
AutoConfigurationImportSelector将会被放到DeferredImportSelectorGrouping中的deferredImports集合中。并且在DeferredImportSelectorGrouping的getImports()方法中进行统一处理。
跟自动配置逻辑相关的入口方法在 DeferredImportSelectorGrouping 类的 getImports 方法处.
public Iterable<Entry> getImports() {
Iterator var1 = this.deferredImports.iterator();
// 遍历DeferredImportSelectorGrouping的deferredImports集合。AutoConfigurationImportSelector也在这个集合中。
while(var1.hasNext()) {
ConfigurationClassParser.DeferredImportSelectorHolder deferredImport = (ConfigurationClassParser.DeferredImportSelectorHolder)var1.next();
// (1)利用AutoConfigurationGroup的process方法来处理自动配置的相关逻辑,决定导入哪些配置类
this.group.process(deferredImport.getConfigurationClass().getMetadata(), deferredImport.getImportSelector());
}
// (2)经过上面的处理后,然后再进行选择导入哪些配置类
return this.group.selectImports();
}
其中重要的两个步骤:
- 利用AutoConfigurationGroup的process方法来处理自动配置的相关逻辑,决定导入哪些配置类
- 经过上面的处理后,然后再进行选择导入哪些配置类
分析自动配置的主要逻辑
AutoConfigurationImportSelector的process方法:
// 这里用来处理自动配置类,比如过滤掉不符合匹配条件的自动配置类
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector, () -> {
return String.format("Only %s implementations are supported, got %s", AutoConfigurationImportSelector.class.getSimpleName(), deferredImportSelector.getClass().getName());
});
// (1)调用getAutoConfigurationEntry方法得到自动配置类放入 autoConfigurationEntry对象中
AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector)deferredImportSelector).getAutoConfigurationEntry(annotationMetadata);
// (2)又将封装了自动配置类的autoConfigurationEntry对象装进autoConfigurationEntries集合
this.autoConfigurationEntries.add(autoConfigurationEntry);
Iterator var4 = autoConfigurationEntry.getConfigurations().iterator();
// (3)遍历刚获取的自动配置类,放入entries
while(var4.hasNext()) {
String importClassName = (String)var4.next();
// 这里符合条件的自动配置类作为key,annotationMetadata作为值放进entries集合
this.entries.putIfAbsent(importClassName, annotationMetadata);
}
}
其中重要的三个步骤:
- 调用getAutoConfigurationEntry方法得到自动配置类放入 autoConfigurationEntry对象中
- 又将封装了自动配置类的autoConfigurationEntry对象装进autoConfigurationEntries集合
- 遍历刚获取的自动配置类,放入entries
获取自动配置类方法getAutoConfigurationEntry:
protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
} else {
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
// (1)得到spring.factories文件配置的所有自动配置类
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
// 利用LinkedHashSet移除重复的配置类
configurations = this.removeDuplicates(configurations);
// 得到要排除的自动配置类,比如注解属性exclude的配置类
// 比如:@SpringBootApplication(exclude = FreeMarkerAutoConfiguration.class)
// 将会获取到exclude = FreeMarkerAutoConfiguration.class的注解数据
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
// 检查要被排除的配置类,因为有些不是自动配置类,故要抛出异常
this.checkExcludedClasses(configurations, exclusions);
// (2)将要排除的配置类移除
configurations.removeAll(exclusions);
// (3)对加载到的自动配置类按需要进行过滤
configurations = this.getConfigurationClassFilter().filter(configurations);
// (4)获取了符合条件的自动配置类后,此时触发AutoConfigurationImportEvent事件
// 目的是告诉ConditionEvaluationReport条件评估报告器对象来记录符合条件的自动配置类
// 该事件什么时候会被触发?--> 在刷新容器时调用invokeBeanFactoryPostProcessors后置处理器时触发
this.fireAutoConfigurationImportEvents(configurations, exclusions);
// (5)将符合条件和要排除的自动配置类封装进AutoConfigurationEntry对象,并返回
return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
}
}
其中重要的五个步骤:
- 得到spring.factories文件配置的所有自动配置类
- 将要排除的配置类移除
- 对加载到的自动配置类按需要进行过滤
- 告诉ConditionEvaluationReport条件评估报告器对象来记录符合条件的自动配置类
- 将符合条件和要排除的自动配置类封装进AutoConfigurationEntry对象,并返回
getCandidateConfigurations获取配置文件中的自动配置类过程分析
这个方法中重要的方法是loadFactoryNames,这个方法的作用是加载一些组件的名字。
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
// 加载组件名字
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
return configurations;
}
进入loadFactoryNames方法,它只是做了简单验证,就委托另外一个方法loadSpringFactories 去加载组件名。
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
ClassLoader classLoaderToUse = classLoader;
if (classLoader == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
String factoryTypeName = factoryType.getName();
// 真正去加载组件名的方法
return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}
继续进入loadSpringFactories方法:
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
Map<String, List<String>> result = (Map)cache.get(classLoader);
if (result != null) {
return result;
} else {
HashMap result = new HashMap();
try {
// 加载类路径下spring.factories文件,将其中设置的配置类的全路径信息封装 为Enumeration类对象
Enumeration urls = classLoader.getResources("META-INF/spring.factories");
//循环Enumeration类对象,根据相应的节点信息生成Properties对象,通过传入 的键获取值,在将值切割为一个个小的字符串转化为Array,方法result集合中
while(urls.hasMoreElements()) {
URL url = (URL)urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
Iterator var6 = properties.entrySet().iterator();
while(var6.hasNext()) {
Entry<?, ?> entry = (Entry)var6.next();
String factoryTypeName = ((String)entry.getKey()).trim();
String[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
String[] var10 = factoryImplementationNames;
int var11 = factoryImplementationNames.length;
for(int var12 = 0; var12 < var11; ++var12) {
String factoryImplementationName = var10[var12];
((List)result.computeIfAbsent(factoryTypeName, (key) -> {
return new ArrayList();
})).add(factoryImplementationName.trim());
}
}
}
result.replaceAll((factoryType, implementations) -> {
return (List)implementations.stream().distinct().collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
});
cache.put(classLoader, result);
return result;
} catch (IOException var14) {
throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var14);
}
}
}
这个方法的主要作用是遍历整个ClassLoader下所有的jar包下的Spring.factories文件。
而在spring-boot-autoconfigure包的META-INF下的spring.factories中保存着springboot的默认提供的自动配置类。
我们下面总结下 getAutoConfigurationEntry 方法主要做的事情:
【1】从 spring.factories 配置文件中加载 EnableAutoConfiguration 自动配置类),获取的自动配
置类如图所示。
【2】若 @EnableAutoConfiguration 等注解标有要 exclude 的自动配置类,那么再将这个自动配置类排除掉;
【3】排除掉要 exclude 的自动配置类后,然后再调用 filter 方法进行进一步的过滤,再次排除一些
不符合条件的自动配置类;
【4】经过重重过滤后,此时再触发 AutoConfigurationImportEvent 事件,告诉
ConditionEvaluationReport 条件评估报告器对象来记录符合条件的自动配置类;
【5】 最后再将符合条件的自动配置类返回。
总结了 getAutoConfigurationEntry 方法主要的逻辑后,我们再来细看一下
AutoConfigurationImportSelector 的 filter 方法:
List<String> filter(List<String> configurations) {
long startTime = System.nanoTime();
// 将从spring.factories中获取的自动配置类转出字符串数组
String[] candidates = StringUtils.toStringArray(configurations);
boolean skipped = false;
Iterator var6 = this.filters.iterator();
int i;
while(var6.hasNext()) {
AutoConfigurationImportFilter filter = (AutoConfigurationImportFilter)var6.next();
// 判断各种filter来判断每个candidate(这里实质要通过candidate(自动配置类)拿到其标注的
// @ConditionalOnClass,@ConditionalOnBean和@ConditionalOnWebApplication里面的注解值)是否匹配,
boolean[] match = filter.match(candidates, this.autoConfigurationMetadata);
// 遍历match数组,注意match顺序跟candidates的自动配置类一一对应
for(i = 0; i < match.length; ++i) {
// 若有不匹配的话
if (!match[i]) {
candidates[i] = null;
// 标注skipped为true
skipped = true;
}
}
}
// 这里表示若所有自动配置类经过OnBeanCondition,OnClassCondition和 OnWebApplicationCondition过滤后,全部都匹配的话,则全部原样返回
if (!skipped) {
return configurations;
} else {
// 建立result集合来装匹配的自动配置类
List<String> result = new ArrayList(candidates.length);
String[] var12 = candidates;
int var14 = candidates.length;
for(i = 0; i < var14; ++i) {
String candidate = var12[i];
// 符合条件的自动配置类,此时添加到result集合中
if (candidate != null) {
result.add(candidate);
}
}
if (AutoConfigurationImportSelector.logger.isTraceEnabled()) {
int numberFiltered = configurations.size() - result.size();
AutoConfigurationImportSelector.logger.trace("Filtered " + numberFiltered + " auto configuration class in " + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) + " ms");
}
// 最后返回符合条件的自动配置类
return result;
}
}
AutoConfigurationImportSelector 的 filter 方法主要做的事情就是调用AutoConfigurationImportFilter 接口的match 方法来判断每一个自动配置类上的条件注解(若有的话) @ConditionalOnClass ,@ConditionalOnBean 或 @ConditionalOnWebApplication 是否满足条件,若满足,则返回true,说明匹配,若不满足,则返回false说明不匹配。
我们现在知道 AutoConfigurationImportSelector 的 filter 方法主要做了什么事情就行了,现在先不用研究的过深
有选择的导入自动配置类
this.group.selectImports 方法是如何进一步有选择的导入自动配置类的。
public Iterable<Entry> selectImports() {
if (this.autoConfigurationEntries.isEmpty()) {
return Collections.emptyList();
} else {
// 这里得到所有要排除的自动配置类的set集合
Set<String> allExclusions = (Set)this.autoConfigurationEntries.stream().map(AutoConfigurationImportSelector.AutoConfigurationEntry::getExclusions).flatMap(Collection::stream).collect(Collectors.toSet());
// 这里得到经过滤后所有符合条件的自动配置类的set集合
Set<String> processedConfigurations = (Set)this.autoConfigurationEntries.stream().map(AutoConfigurationImportSelector.AutoConfigurationEntry::getConfigurations).flatMap(Collection::stream).collect(Collectors.toCollection(LinkedHashSet::new));
// 移除掉要排除的自动配置类
processedConfigurations.removeAll(allExclusions);
// 对标注有@Order注解的自动配置类进行排序
return (Iterable)this.sortAutoConfigurations(processedConfigurations, this.getAutoConfigurationMetadata()).stream().map((importClassName) -> {
return new Entry((AnnotationMetadata)this.entries.get(importClassName), importClassName);
}).collect(Collectors.toList());
}
}
selectImports 方法的自动配置类再进一步排除 exclude 的自动配置类,然后再排序
最后,我们再总结下SpringBoot自动配置的原理,主要做了以下事情:
- 从spring.factories配置文件中加载自动配置类;
- 加载的自动配置类中排除掉 @EnableAutoConfiguration 注解的 exclude 属性指定的自动配置类;
- 然后再用 AutoConfigurationImportFilter 接口去过滤自动配置类是否符合其标注注解(若有标注的话) @ConditionalOnClass , @ConditionalOnBean 和 @ConditionalOnWebApplication 的条件,若都符合的话则返回匹配结果;
- 然后触发 AutoConfigurationImportEvent 事件,告诉 ConditionEvaluationReport 条件评估报告器对象来分别记录符合条件和 exclude 的自动配置类。
- 最后spring再将最后筛选后的自动配置类导入IOC容器中
画图梳理一下这个流程
总结:
- 在当前Spring Boot工程中,各个组件(jar包),都会存在一个名称为META-INF的目录,目录中有一个sping.factories文件。
- 在这个文件中会配置工厂类的全路径如MyBatis中配置了org.mybatis.spring.boot.autoconfigure.MyBatisAutoConfiguration。
- Spring Boot 通过@EnableAutoConfiguration注解收集配置工厂类
- 由Spring创建bean实例存到IoC容器中。
条件注解
@Conditional是Spring4新提供的注解,它的作用是按照一定的条件进行判断,满足条件给容器注册bean。
- @ConditionalOnBean:仅仅在当前上下文中存在某个对象时,才会实例化一个Bean。
- @ConditionalOnClass:某个class位于类路径上,才会实例化一个Bean。
- @ConditionalOnExpression:当表达式为true的时候,才会实例化一个Bean。基于SpEL表达式 的条件判断。
- @ConditionalOnMissingBean:仅仅在当前上下文中不存在某个对象时,才会实例化一个Bean。
- @ConditionalOnMissingClass:某个class类路径上不存在的时候,才会实例化一个Bean。
- @ConditionalOnNotWebApplication:不是web应用,才会实例化一个Bean。
- @ConditionalOnWebApplication:当项目是一个Web项目时进行实例化。
- @ConditionalOnNotWebApplication:当项目不是一个Web项目时进行实例化。
- @ConditionalOnProperty:当指定的属性有指定的值时进行实例化。
- @ConditionalOnJava:当JVM版本为指定的版本范围时触发实例化。
- @ConditionalOnResource:当类路径下有指定的资源时触发实例化。
- @ConditionalOnJndi:在JNDI存在的条件下触发实例化。
- @ConditionalOnSingleCandidate:当指定的Bean在容器中只有一个,或者有多个但是指定了首 选的Bean时触发实例化。
@ComponentScan
- @ConponentScan用于配置扫描Spring的扫包基础路径的注解。默认扫描被这个注解标记的类的目录及子目录。
- @SpringBootApplication中包含了@ConponentScan,因此它具备@ConponentScan相同的功能,因此被@SpringBootApplication注解标记了的类的类路径及其子路径上的bean都能被扫描到。这也是为什么非这个目录及其子目录中的bean不能被扫描的原因。
SpringApplication初始化过程
@SpringBootApplication中的@ComponentScan是在什么时候被扫描到的?
SpringBoot项目的main方法:
@SpringBootApplication
@EnableConfigurationProperties
public class SpringBootDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootDemoApplication.class, args);
}
}
从run方法开始追踪:
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class[]{primarySource}, args);
}
调用了另外一个run方法:
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return (new SpringApplication(primarySources)).run(args);
}
这个run方法构建了一个SpringApplication类的实例,我们继续来看下SpringApplication类的构造函数
SpringApplication构造函数
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.sources = new LinkedHashSet();
this.bannerMode = Mode.CONSOLE;
this.logStartupInfo = true;
this.addCommandLineProperties = true;
this.addConversionService = true;
this.headless = true;
this.registerShutdownHook = true;
this.additionalProfiles = Collections.emptySet();
this.isCustomEnvironment = false;
this.lazyInitialization = false;
this.applicationContextFactory = ApplicationContextFactory.DEFAULT;
this.applicationStartup = ApplicationStartup.DEFAULT;
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
// 1、推断应用类型:servlet、reactive、none
this.webApplicationType = WebApplicationType.deduceFromClasspath();
this.bootstrapRegistryInitializers = this.getBootstrapRegistryInitializersFromSpringFactories();
// 2、初始化META-INF/spring.factories文件中配置的ApplicationContextInitializer
this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 3、初始化classpath下的所有已配置的ApplicationListener
this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
// 4、推断出main方法的类名。
this.mainApplicationClass = this.deduceMainApplicationClass();
}
判断应用类型
private static final String[] SERVLET_INDICATOR_CLASSES = new String[]{"javax.servlet.Servlet", "org.springframework.web.context.ConfigurableWebApplicationContext"};
static WebApplicationType deduceFromClasspath() {
if (ClassUtils.isPresent("org.springframework.web.reactive.DispatcherHandler", (ClassLoader)null) && !ClassUtils.isPresent("org.springframework.web.servlet.DispatcherServlet", (ClassLoader)null) && !ClassUtils.isPresent("org.glassfish.jersey.servlet.ServletContainer", (ClassLoader)null)) {
return REACTIVE;
} else {
String[] var0 = SERVLET_INDICATOR_CLASSES;
int var1 = var0.length;
for(int var2 = 0; var2 < var1; ++var2) {
String className = var0[var2];
if (!ClassUtils.isPresent(className, (ClassLoader)null)) {
return NONE;
}
}
return SERVLET;
}
}
判断应用类型主要分三个步骤:
- 1、判断类路径中有没有DispatcherHandler,DispatcherServlet,ServletContainer:如果有DispatcherHandler,没有DispatcherServlet,ServletContainer则是REACTIVE(响应式编程)类型的应用。
-
- 2、如果不满足条件1,如果类路径不包含Servlet类或不包含ConfigurableWebApplicationContext类,则是NONE(不是一个web应用)类型应用。
-
- 如果条件1、2都不满足,那它就是一个Servlet(web应用,需要启动内置servlet容器)应用。
初始化ApplicationContextInitializer
this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = this.getClassLoader();
// 从MATA-INF/spring.factories文件中读取所有ApplicationContextInitializer类全路径为key的所有类全路径
Set<String> names = new LinkedHashSet(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
// 反射实例化这些ApplicationContextInitializer类
List<T> instances = this.createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
主要完成了两步操作:
- 从MATA-INF/spring.factories文件中读取所有ApplicationContextInitializer类全路径为key的所有类全路径。
- 反射实例化上一步获取到的类,并放入到一个集合中。
初始化classpath下的所有ApplicationListener
this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
``
加载过程跟初始化器一样
主要完成了两步操作:
- 从MATA-INF/spring.factories文件中读取所有ApplicationListener类全路径为key的所有类全路径。
- 反射实例化上一步获取到的类,并放入到一个集合中。
#### 根据调用栈,推断出main方法的类
```java
this.mainApplicationClass = this.deduceMainApplicationClass();
private Class<?> deduceMainApplicationClass() {
try {
StackTraceElement[] stackTrace = (new RuntimeException()).getStackTrace();
StackTraceElement[] var2 = stackTrace;
int var3 = stackTrace.length;
for(int var4 = 0; var4 < var3; ++var4) {
StackTraceElement stackTraceElement = var2[var4];
if ("main".equals(stackTraceElement.getMethodName())) {
return Class.forName(stackTraceElement.getClassName());
}
}
} catch (ClassNotFoundException var6) {
}
return null;
}
这一步的主要功能是:
- 根据调用栈,找到main方法所在类的class对象,存储到成员变量。
SpringApplication.run方法流程介绍
SpringApplication对象构建完后调用run方法,run方法代码如下:
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
DefaultBootstrapContext bootstrapContext = this.createBootstrapContext();
ConfigurableApplicationContext context = null;
this.configureHeadlessProperty();
//从META-INF/spring.factories中获取监听器
//1、获取并启动监听器
SpringApplicationRunListeners listeners = this.getRunListeners(args);
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//2、构造应用上下文环境
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments);
this.configureIgnoreBeanInfo(environment);
Banner printedBanner = this.printBanner(environment);
//3、初始化应用上下文
context = this.createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
//4、刷新应用上下文前的准备阶段
this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
//5、刷新应用上下文
this.refreshContext(context);
//6、刷新应用上下文后的扩展接口
this.afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
(new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
}
listeners.started(context);
this.callRunners(context, applicationArguments);
} catch (Throwable var10) {
this.handleRunFailure(context, var10, listeners);
throw new IllegalStateException(var10);
}
try {
listeners.running(context);
return context;
} catch (Throwable var9) {
this.handleRunFailure(context, var9, (SpringApplicationRunListeners)null);
throw new IllegalStateException(var9);
}
}
在以上的代码中,启动过程中的重要步骤共分为六步 :
- 1、获取并启动监听器
- 2、构造应用上下文环境
- 3、初始化应用上下文
- 4、刷新应用上下文前的准备阶段
- 5、刷新应用上下文
- 6、刷新应用上下文后的扩展接口
获取并启动监听器
事件机制在Spring是很重要的一部分内容,通过事件机制我们可以监听Spring容器中正在发生的一些事件,同样也可以自定义监听事件。Spring的事件为Bean和Bean之间的消息传递提供支持。当一个对象处理完某种任务后,通知另外的对象进行某些处理,常用的场景有进行某些操作后发送通知,消息、邮件等情况。
获取监听器的入口代码:
SpringApplicationRunListeners listeners = this.getRunListeners(args);
listeners.starting(bootstrapContext, this.mainApplicationClass);
进入到方法中:
private SpringApplicationRunListeners getRunListeners(String[] args) {
Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
return new SpringApplicationRunListeners(logger,
// 从META-INF/spring.factories文件中获取监听器
getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args),
this.applicationStartup);
}
从代码中看到,在构建SpringApplicationRunListeners实例之前调用了一个我们非常熟悉的方法:getSpringFactoriesInstances,该方法的主要作用是从spring.factories文件中加载SpringApplicationRunListener类全路径为key的所有类,并实例化。
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = getClassLoader();
// Use names and ensure unique to protect against duplicates
// 根据传入的type类型,使用type的类名全路径作为key,到META-INF/spring.factories文件中去查找值,并放到names中
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
// 将查找到的类通过反射实例化
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
// 按order注解顺序排序
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
获取到监听器后,启动监听器:
listeners.starting(bootstrapContext, this.mainApplicationClass);
构造应用上下文环境
用上下文环境包括什么呢?包括计算机的环境,Java环境,Spring的运行环境,Spring项目的配置(在SpringBoot中就是那个熟悉的application.properties/yml)等等。
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
//创建并配置相应的环境
ConfigurableEnvironment environment = this.getOrCreateEnvironment();
//根据用户配置,配置 environment系统环境
this.configureEnvironment((ConfigurableEnvironment)environment, applicationArguments.getSourceArgs());
// 启动相应的监听器,其中一个重要的监听器 ConfigFileApplicationListener 就是加载项目配置文件的监听器。
ConfigurationPropertySources.attach((Environment)environment);
listeners.environmentPrepared(bootstrapContext, (ConfigurableEnvironment)environment);
DefaultPropertiesPropertySource.moveToEnd((ConfigurableEnvironment)environment);
Assert.state(!((ConfigurableEnvironment)environment).containsProperty("spring.main.environment-prefix"), "Environment prefix cannot be set via properties.");
this.bindToSpringApplication((ConfigurableEnvironment)environment);
if (!this.isCustomEnvironment) {
environment = (new EnvironmentConverter(this.getClassLoader())).convertEnvironmentIfNecessary((ConfigurableEnvironment)environment, this.deduceEnvironmentClass());
}
ConfigurationPropertySources.attach((Environment)environment);
return (ConfigurableEnvironment)environment;
}
getOrCreateEnvironment方法做了什么?
private ConfigurableEnvironment getOrCreateEnvironment() {
if (this.environment != null) {
return this.environment;
}
switch (this.webApplicationType) {
case SERVLET:
// 如果应用类型是SERVLET,则创建Servlet类型的环境
return new ApplicationServletEnvironment();
case REACTIVE:
// 如果应用类型是响应式编程的,则创建Reactive类型的环境
return new ApplicationReactiveWebEnvironment();
default:
return new ApplicationEnvironment();
}
}
ApplicationServletEnvironment类型的继承关系:
/**
* Template method delegating to
* {@link #configurePropertySources(ConfigurableEnvironment, String[])} and
* {@link #configureProfiles(ConfigurableEnvironment, String[])} in that order.
* Override this method for complete control over Environment customization, or one of
* the above for fine-grained control over property sources or profiles, respectively.
* @param environment this application's environment
* @param args arguments passed to the {@code run} method
* @see #configureProfiles(ConfigurableEnvironment, String[])
* @see #configurePropertySources(ConfigurableEnvironment, String[])
*/
protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
if (this.addConversionService) {
environment.setConversionService(new ApplicationConversionService());
}
// 将main方法的参数分组到SimpleCommandLinePropertySource中
configurePropertySources(environment, args);
// 激活相关的配置文件
configureProfiles(environment, args);
}
初始化应用上下文
/**
*用于创建 {@link ApplicationContext} 的策略方法
* Strategy method used to create the {@link ApplicationContext}. By default this
* method will respect any explicitly set application context class or factory before
* falling back to a suitable default.
* @return the application context (not yet refreshed)
* @see #setApplicationContextClass(Class)
* @see #setApplicationContextFactory(ApplicationContextFactory)
*/
protected ConfigurableApplicationContext createApplicationContext() {
// 根据应用类型创建对应的 ApplicationContext实例
return this.applicationContextFactory.create(this.webApplicationType);
}
调试进入:
org.springframework.boot.ApplicationContextFactory
ApplicationContextFactory DEFAULT = (webApplicationType) -> {
try {
switch (webApplicationType) {
case SERVLET:
// 应用类型是Servlet时,new一个AnnotationConfigServletWebServerApplicationContext上下文实例对象
return new AnnotationConfigServletWebServerApplicationContext();
case REACTIVE:
return new AnnotationConfigReactiveWebServerApplicationContext();
default:
return new AnnotationConfigApplicationContext();
}
}
catch (Exception ex) {
throw new IllegalStateException("Unable create a default ApplicationContext instance, "
+ "you may need a custom ApplicationContextFactory", ex);
}
};
AnnotationConfigServletWebServerApplicationContext的类继承关系:
GenericApplicationContext是AnnotationConfigServletWebServerApplicationContext的父类。
创建对象的时候,创建了一个reader和一个scanner:
/**
* Create a new {@link AnnotationConfigServletWebServerApplicationContext} that needs
* to be populated through {@link #register} calls and then manually
* {@linkplain #refresh refreshed}.
*/
public AnnotationConfigServletWebServerApplicationContext() {
this.reader = new AnnotatedBeanDefinitionReader(this);
this.scanner = new ClassPathBeanDefinitionScanner(this);
}
另外在调用AnnotationConfigServletWebServerApplicationContext构造函数之前,要先调用父类的构造函数:
public GenericApplicationContext() {
this.customClassLoader = false;
this.refreshed = new AtomicBoolean();
this.beanFactory = new DefaultListableBeanFactory();
}
AnnotationConfigServletWebServerApplicationContext中的IoC容器在其父类的构造方法中被实例化。
刷新应用上下文前的准备阶段
刷新应用上下文前的准备阶段,就是想上下文context中设置一些属性,完成bean对象的创建。
private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
// 设置容器环境
context.setEnvironment(environment);
// 执行容器后置处理器,向context中设置值
postProcessApplicationContext(context);
// 遍历初始化器,执行初始化
applyInitializers(context);
// 向各个监听器去发送容器准备好的事件
listeners.contextPrepared(context);
bootstrapContext.close(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// Add boot specific singleton beans
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
if (beanFactory instanceof DefaultListableBeanFactory) {
((DefaultListableBeanFactory) beanFactory)
.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
if (this.lazyInitialization) {
context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
}
// Load the sources
// 获取所有启动类
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
load(context, sources.toArray(new Object[0]));
listeners.contextLoaded(context);
}
Set<Object> sources = getAllSources();拿到启动类,我们来看下它是怎么拿到的?
public Set<Object> getAllSources() {
Set<Object> allSources = new LinkedHashSet<>();
// primarySources在SpringApplication的run方法中传入的
if (!CollectionUtils.isEmpty(this.primarySources)) {
allSources.addAll(this.primarySources);
}
if (!CollectionUtils.isEmpty(this.sources)) {
allSources.addAll(this.sources);
}
return Collections.unmodifiableSet(allSources);
}
从源码中看到:它是将primarySources和sources两个集合合并后返回。因此primarySources和sources中存放的就是启动类。
下面看看它们在哪里被赋值的:
在SpringApplication的构造方法中,将构造方法中的参数直接添加到了primarySources集合中。
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
this.webApplicationType = WebApplicationType.deduceFromClasspath();
this.bootstrapRegistryInitializers = getBootstrapRegistryInitializersFromSpringFactories();
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
另外有一个方法也涉及到往这个集合中添加值:
public void addPrimarySources(Collection<Class<?>> additionalPrimarySources) {
this.primarySources.addAll(additionalPrimarySources);
}
但是通过findUseage发现,在启动流程中,这个方法还没有被调用过。因此,当前状态下primarySources集合中只有SpringApplication.run(SpringBootDemoApplication.class, args);
参数中传入的启动类。
另外一个集合sources的只在一个方法中赋值:
public void setSources(Set<String> sources) {
Assert.notNull(sources, "Sources must not be null");
this.sources = new LinkedHashSet<>(sources);
}
这个方法只被SampleSpringXmlApplication类中的main方法调用,而Spring Boot的启动类是通过SpringApplication的run方法启动的。因此sources集合是空的。
Set<Object> sources = getAllSources();在Spring Boot工程启动过程中,拿到的是SpringBoot启动类,就是传入到SpringApplication.run()方法的第一个参数。
验证:
断点打在run方法
primarySources 集合中只有一个SpringBootDemoApplication类对象
这个类对象正好是我们的启动类
断点打到getAllResource
allSources中只有我们的启动类对象,sources集合为空,验证了我们的结论。
接着看load()方法
/**
* Load beans into the application context.
* @param context the context to load beans into
* @param sources the sources to load
*/
protected void load(ApplicationContext context, Object[] sources) {
if (logger.isDebugEnabled()) {
logger.debug("Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
}
// 创建BeanDefinitionLoader
BeanDefinitionLoader loader = createBeanDefinitionLoader(getBeanDefinitionRegistry(context), sources);
if (this.beanNameGenerator != null) {
loader.setBeanNameGenerator(this.beanNameGenerator);
}
if (this.resourceLoader != null) {
loader.setResourceLoader(this.resourceLoader);
}
if (this.environment != null) {
loader.setEnvironment(this.environment);
}
loader.load();
}
在创建BeanDefinitionLoader之前,先获取BeanDefinitionRegistery:
private BeanDefinitionRegistry getBeanDefinitionRegistry(ApplicationContext context) {
if (context instanceof BeanDefinitionRegistry) {
return (BeanDefinitionRegistry) context;
}
if (context instanceof AbstractApplicationContext) {
return (BeanDefinitionRegistry) ((AbstractApplicationContext) context).getBeanFactory();
}
throw new IllegalStateException("Could not locate BeanDefinitionRegistry");
}
其实就是将context中的IOC容器直接拿出来,并且转成BeanDefinitionRegistry。在前面看AnnotationConfigServletWebApplicationContext类继承关系的时候知道,它实现了BeanDefinitionRegistry接口,因此可以强转。并且可以验证这里就是AnnotationConfigServletWebApplicationContext。
BeanDefinitionRegistry定义了很重要的方法registerBeanDefinition(),该方法将BeanDefinition 注册进DefaultListableBeanFactory容器的beanDefinitionMap中。
现在再来看创建BeanDefinitionLoader的过程:
protected BeanDefinitionLoader createBeanDefinitionLoader(BeanDefinitionRegistry registry, Object[] sources) {
return new BeanDefinitionLoader(registry, sources);
}
构造方法:
/**
* Create a new {@link BeanDefinitionLoader} that will load beans into the specified
* {@link BeanDefinitionRegistry}.
* @param registry the bean definition registry that will contain the loaded beans
* @param sources the bean sources
*/
BeanDefinitionLoader(BeanDefinitionRegistry registry, Object... sources) {
Assert.notNull(registry, "Registry must not be null");
Assert.notEmpty(sources, "Sources must not be empty");
this.sources = sources;
//注解形式的Bean定义读取器 比如:@Configuration @Bean @Component @Controller @Service等等
this.annotatedReader = new AnnotatedBeanDefinitionReader(registry);
//XML形式的Bean定义读取器
this.xmlReader = (XML_ENABLED ? new XmlBeanDefinitionReader(registry) : null);
this.groovyReader = (isGroovyPresent() ? new GroovyBeanDefinitionReader(registry) : null);
//类路径扫描器
this.scanner = new ClassPathBeanDefinitionScanner(registry);
//扫描器添加排除过滤器
this.scanner.addExcludeFilter(new ClassExcludeFilter(sources));
}
在该方法中创建了reader和scanner,这两个很熟悉的东西,后面肯定会使用reader去读取配置,scaner进行包扫描。
接着看loader.load();方法:
/**
* Load the sources into the reader.
*/
void load() {
// 遍历启动类,分别进行load
for (Object source : this.sources) {
load(source);
}
}
private void load(Object source) {
Assert.notNull(source, "Source must not be null");
// 从calss加载
if (source instanceof Class<?>) {
load((Class<?>) source);
return;
}
// 从resource加载
if (source instanceof Resource) {
load((Resource) source);
return;
}
// 从package加载
if (source instanceof Package) {
load((Package) source);
return;
}
// 从CharSequence加载
if (source instanceof CharSequence) {
load((CharSequence) source);
return;
}
throw new IllegalArgumentException("Invalid source type " + source.getClass());
}
Spring Boot应用启动时会从class加载。
private void load(Class<?> source) {
if (isGroovyPresent() && GroovyBeanDefinitionSource.class.isAssignableFrom(source)) {
// Any GroovyLoaders added in beans{} DSL can contribute beans here
GroovyBeanDefinitionSource loader = BeanUtils.instantiateClass(source, GroovyBeanDefinitionSource.class);
((GroovyBeanDefinitionReader) this.groovyReader).beans(loader.getBeans());
}
// 判断启动类是否符合注册条件
if (isEligible(source)) {
this.annotatedReader.register(source);
}
}
什么是符合注册条件的:
private boolean isEligible(Class<?> type) {
// 不是匿名的,就是没有名字的类。不是groovy闭包。不是没有构造方法的
return !(type.isAnonymousClass() || isGroovyClosure(type) || hasNoConstructors(type));
}
这里,启动类是符合注册条件的。
所以启动类会被注册到IoC容器中。
this.annotatedReader.register(source);
而annotatedReader是BeanDefinitionLoader构造函数中被实例化的,并且它是Spring Framework中的类,所以注册过程是由Spring Framework完成的
BeanDefinitionLoader(BeanDefinitionRegistry registry, Object... sources) {
Assert.notNull(registry, "Registry must not be null");
Assert.notEmpty(sources, "Sources must not be empty");
this.sources = sources;
// AnnotatedBeanDefinitionReader是Spring Framework中的组件
this.annotatedReader = new AnnotatedBeanDefinitionReader(registry);
this.xmlReader = (XML_ENABLED ? new XmlBeanDefinitionReader(registry) : null);
this.groovyReader = (isGroovyPresent() ? new GroovyBeanDefinitionReader(registry) : null);
this.scanner = new ClassPathBeanDefinitionScanner(registry);
this.scanner.addExcludeFilter(new ClassExcludeFilter(sources));
}
刷新应用上下文
刷新上下文代码:
private void refreshContext(ConfigurableApplicationContext context) {
if (this.registerShutdownHook) {
shutdownHook.registerApplicationContext(context);
}
// 核心方法
refresh(context);
}
从源码可以看到,刷新上下文的核心方法是refresh方法
/**
* Refresh the underlying {@link ApplicationContext}.
* @param applicationContext the application context to refresh
*/
protected void refresh(ConfigurableApplicationContext applicationContext) {
applicationContext.refresh();
}
直接直接调用了application的refresh方法
public void refresh() throws BeansException, IllegalStateException {
synchronized(this.startupShutdownMonitor) {
StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
// 第⼀步:刷新前的预处理
this.prepareRefresh();
/*
第⼆步:
获取BeanFactory;默认实现是DefaultListableBeanFactory
加载BeanDefition 并注册到 BeanDefitionRegistry
*/
ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
// 第三步:BeanFactory的预准备⼯作(BeanFactory进⾏⼀些设置,⽐如context的类加载器等)
this.prepareBeanFactory(beanFactory);
try {
// 第四步:BeanFactory准备⼯作完成后进⾏的后置处理⼯作
this.postProcessBeanFactory(beanFactory);
StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
// 第五步:实例化并调⽤实现了BeanFactoryPostProcessor接⼝的Bean
this.invokeBeanFactoryPostProcessors(beanFactory);
// 第六步:注册BeanPostProcessor(Bean的后置处理器),在创建bean的前后等执⾏
this.registerBeanPostProcessors(beanFactory);
beanPostProcess.end();
// 第七步:初始化MessageSource组件(做国际化功能;消息绑定,消息解析);
this.initMessageSource();
// 第⼋步:初始化事件派发器
this.initApplicationEventMulticaster();
// 第九步:⼦类重写这个⽅法,在容器刷新的时候可以⾃定义逻辑
this.onRefresh();
// 第⼗步:注册应⽤的监听器。就是注册实现了ApplicationListener接⼝的监听器bean
this.registerListeners();
/*
第⼗⼀步:
初始化所有剩下的⾮懒加载的单例bean
初始化创建⾮懒加载⽅式的单例Bean实例(未设置属性)
填充属性
初始化⽅法调⽤(⽐如调⽤afterPropertiesSet⽅法、init-method⽅法)
调⽤BeanPostProcessor(后置处理器)对实例bean进⾏后置处
*/
this.finishBeanFactoryInitialization(beanFactory);
/*
第⼗⼆步:
完成context的刷新。主要是调⽤LifecycleProcessor的onRefresh()⽅法,并且发布事
件 (ContextRefreshedEvent)
*/
this.finishRefresh();
} catch (BeansException var10) {
if (this.logger.isWarnEnabled()) {
this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var10);
}
this.destroyBeans();
this.cancelRefresh(var10);
throw var10;
} finally {
this.resetCommonCaches();
contextRefresh.end();
}
}
}
invokeBeanFactoryPostProcessors(beanFactory);(重点)
IoC容器的初始化过程包括三个步骤,在invokeBeanFactoryPostProcessors()方法中完成了IoC容
器初始化过程的三个步骤:
1,第一步:Resource定位
在SpringBoot中,我们都知道他的包扫描是从主类所在的包开始扫描的,prepareContext()
方法中,会先将主类解析成BeanDefinition,然后在refresh()方法的
invokeBeanFactoryPostProcessors()方法中解析主类的BeanDefinition获取basePackage的路
径。这样就完成了定位的过程。其次SpringBoot的各种starter是通过SPI扩展机制实现的自动装
配,SpringBoot的自动装配同样也是在invokeBeanFactoryPostProcessors()方法中实现的。还有
一种情况,在SpringBoot中有很多的@EnableXXX注解,细心点进去看的应该就知道其底层是
@Import注解,在invokeBeanFactoryPostProcessors()方法中也实现了对该注解指定的配置类的
定位加载。
常规的在SpringBoot中有三种实现定位,第一个是主类所在包的,第二个是SPI扩展机制实现
的自动装配(比如各种starter),第三种就是@Import注解指定的类。(对于非常规的不说了)
2,第二步:BeanDefinition的载入
在第一步中说了三种Resource的定位情况,定位后紧接着就是BeanDefinition的分别载入。
所谓的载入就是通过上面的定位得到的basePackage,SpringBoot会将该路径拼接成:
classpath:com/lagou/**/.class这样的形式,然后一个叫做
xPathMatchingResourcePatternResolver的类会将该路径下所有的.class文件都加载进来,然后
遍历判断是不是有@Component注解,如果有的话,就是我们要装载的BeanDefinition。大致过
程就是这样的了。
3、第三个过程:注册BeanDefinition
这个过程通过调用上文提到的BeanDefinitionRegister接口的实现来完成。这个注册过程把载入
过程中解析得到的BeanDefinition向IoC容器进行注册。通过上文的分析,我们可以看到,在IoC容
器中将BeanDefinition注入到一个ConcurrentHashMap中,IoC容器就是通过这个HashMap来持
有这些BeanDefinition数据的。比如DefaultListableBeanFactory 中的beanDefinitionMap属性。
OK,总结完了,接下来我们通过代码看看具体是怎么实现的。
刷新应用上下文后的扩展接口
/**
* Called after the context has been refreshed.
* @param context the application context
* @param args the application arguments
*/
protected void afterRefresh(ConfigurableApplicationContext context, ApplicationArguments args) {
}
扩展接口,设计模式中的模板方法,默认为空实现。如果有自定义需求,可以重写该方法。比如打印一些启动结束log,或者一些其它后置处理。