首页 > 编程语言 >【Spring Boot源码剖析之Spring Boot源码剖析】

【Spring Boot源码剖析之Spring Boot源码剖析】

时间:2023-03-04 14:32:10浏览次数:33  
标签:return Spring 配置 Boot 源码 context spring new class

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 Boot源码剖析之Spring Boot源码剖析】_spring



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定义。

用图来描述一下整个过程:


【Spring Boot源码剖析之Spring Boot源码剖析】_spring_02


@Import({AutoConfigurationImportSelector.class})

@Import({AutoConfigurationImportSelector.class}) :将
AutoConfigurationImportSelector 这个类导入到 Spring 容器中,
AutoConfigurationImportSelector 可以帮助 Springboot 应用将所有符合条件的 @Configuration 配置都加载到当前 SpringBoot 创建并使用的 IOC 容器( ApplicationContext )中。


【Spring Boot源码剖析之Spring Boot源码剖析】_maven_03


可以看到 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源码剖析之Spring Boot源码剖析】_java_04



而在spring-boot-autoconfigure包的META-INF下的spring.factories中保存着springboot的默认提供的自动配置类。

【Spring Boot源码剖析之Spring Boot源码剖析】_spring_05


我们下面总结下 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自动配置的原理,主要做了以下事情:

  1. 从spring.factories配置文件中加载自动配置类;
  2. 加载的自动配置类中排除掉 @EnableAutoConfiguration 注解的 exclude 属性指定的自动配置类;
  3. 然后再用 AutoConfigurationImportFilter 接口去过滤自动配置类是否符合其标注注解(若有标注的话) @ConditionalOnClass , @ConditionalOnBean 和 @ConditionalOnWebApplication 的条件,若都符合的话则返回匹配结果;
  4. 然后触发 AutoConfigurationImportEvent 事件,告诉 ConditionEvaluationReport 条件评估报告器对象来分别记录符合条件和 exclude 的自动配置类。
  5. 最后spring再将最后筛选后的自动配置类导入IOC容器中

画图梳理一下这个流程


【Spring Boot源码剖析之Spring Boot源码剖析】_maven_06


总结:

  • 在当前Spring Boot工程中,各个组件(jar包),都会存在一个名称为META-INF的目录,目录中有一个sping.factories文件。
  • 在这个文件中会配置工厂类的全路径如MyBatis中配置了org.mybatis.spring.boot.autoconfigure.MyBatisAutoConfiguration。
  • Spring Boot 通过@EnableAutoConfiguration注解收集配置工厂类
  • 由Spring创建bean实例存到IoC容器中。

【Spring Boot源码剖析之Spring Boot源码剖析】_maven_07


条件注解

@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类型的继承关系:


【Spring Boot源码剖析之Spring Boot源码剖析】_maven_08



/**
* 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的类继承关系:


【Spring Boot源码剖析之Spring Boot源码剖析】_spring_09


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容器在其父类的构造方法中被实例化。

【Spring Boot源码剖析之Spring Boot源码剖析】_maven_10


刷新应用上下文前的准备阶段

刷新应用上下文前的准备阶段,就是想上下文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方法


【Spring Boot源码剖析之Spring Boot源码剖析】_java_11


primarySources 集合中只有一个SpringBootDemoApplication类对象


【Spring Boot源码剖析之Spring Boot源码剖析】_maven_12


这个类对象正好是我们的启动类


【Spring Boot源码剖析之Spring Boot源码剖析】_maven_13



断点打到getAllResource


【Spring Boot源码剖析之Spring Boot源码剖析】_spring_14



allSources中只有我们的启动类对象,sources集合为空,验证了我们的结论。

【Spring Boot源码剖析之Spring Boot源码剖析】_maven_15


接着看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。


【Spring Boot源码剖析之Spring Boot源码剖析】_spring_16


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,或者一些其它后置处理。


标签:return,Spring,配置,Boot,源码,context,spring,new,class
From: https://blog.51cto.com/u_15874356/6100038

相关文章

  • 【代理】【五】代理源码解析-Cglib代理-Enhancer代理对象创建过程
    1 前言上节我们看了EnhancerKey是通过KeyFactory里的 Generator来创建代理对象,这节我们就来看下Enhancer是如何创建代理对象的。2 源码分析上节我们看过了,Abst......
  • springcloud-gateway配置跨域
    跨域有两种解决方式:一是将前端和后端通过nginx部署在同一域名下,后端通过location/admin这种方式转发,避免浏览器因为协议,主机和端口号不同造成的跨域二是非简单请求,浏览......
  • springcloud-引入gateway
    gateway其实和MVC框架路由逻辑相似,mvc路由是将请求url交给对应的控制器方法处理,gateway是将请求转发给对应的服务来处理。1,gateway需要引入nacos注册和配置中心2,gateway......
  • springcloud-alibaba接入nacos配置中心
    未采用动态配置前,如果集群的话,某个服务集群中的每台服务器配置都不一样,需要单个打包部署,工作量大,不易维护。1,引入依赖,和注册中心一样,因为子模块都有使用,放到公用模块commm......
  • 21_Spring_日志框架和测试支持
    ​ spring5框架自带了通用的日志封装,也可以整合自己的日志 1)spring移除了LOG4jConfigListener,官方建议使用log4j2 2)spring5整合log4j2导入log4j2依赖 <......
  • springmvc整合thymeleaf之helloword
    版本说明:代码地址:https://gitee.com/joy521125/ssm-senior.git  thymeleaf分支;基于https://gitee.com/joy521125/ssm-senior.gitmaster分支修改而来;1.加入jar包:1......
  • 21_Spring_日志框架和测试支持
     spring5框架自带了通用的日志封装,也可以整合自己的日志 1)spring移除了LOG4jConfigListener,官方建议使用log4j2 2)spring5整合log4j2导入log4j2依赖 <!--log4j2......
  • 21_Spring_日志框架和测试支持
     spring5框架自带了通用的日志封装,也可以整合自己的日志 1)spring移除了LOG4jConfigListener,官方建议使用log4j2 2)spring5整合log4j2导入log4j2依赖 <!--log4j2......
  • java——spring boot集成RabbitMQ——高级特效——死信代码示例
    首先,消息成为死信的条件:       首先看消息生产者,生产者和之前的一样,没什么变化(注意:后面统一把nomal改为normal了):          消费......
  • 21_Spring_日志框架和测试支持
    ​ spring5框架自带了通用的日志封装,也可以整合自己的日志 1)spring移除了LOG4jConfigListener,官方建议使用log4j2 2)spring5整合log4j2导入log4j2依赖 <......