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 格式進行了過濾,可以支持不同環(huán)境的配置舌缤,比如 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節(jié)點
在這里玻墅,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>
因為存在這個依賴關(guān)系所以在我們創(chuàng)建的Spring Boot項目中部分依賴不需要寫版本號。
(2)問題2: spring-boot-starter-parent父依賴啟動器的主要作用是進行版本統(tǒng)一管理澳厢,那么項目
運行依賴的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開發(fā)所需要的底層所有依賴剩拢。
因此在引入spring-boot-starter-web依賴啟動器時线得,就可以實現(xiàn)web場景開發(fā),而不需要額外導入tomcat服務(wù)器及其他web依賴文件徐伐。
Spring Boot除了提供Web依賴啟動器之外贯钩,還提供了其他很多依賴啟動器。具體可以到官網(wǎng)查找办素。
自動配置
自動配置:根據(jù)我們添加的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 {
// 根據(jù)class來排除特定的類檩咱,使其不能加入spring容器揭措,傳入?yún)?shù)value類型是class類型胯舷。
@AliasFor(
annotation = EnableAutoConfiguration.class
)
Class<?>[] exclude() default {};
// 根據(jù)classname 來排除特定的類刻蚯,使其不能加入spring容器,傳入?yún)?shù)value類型是class的全 類名字符串數(shù)組桑嘶。
@AliasFor(
annotation = EnableAutoConfiguration.class
)
String[] excludeName() default {};
// 指定掃描包炊汹,參數(shù)是包名的字符串數(shù)組。
@AliasFor(
annotation = ComponentScan.class,
attribute = "basePackages"
)
String[] scanBasePackages() default {};
// 掃描特定的包逃顶,參數(shù)類似是Class類型數(shù)組讨便。
@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個是注解的元數(shù)據(jù)信息霸褒,后面三個注解是核心:@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來收集并注冊特定場景相關(guān)的Bean,并加載到IOC容器劲适。
@EnableAutoConfiguration就是借助@Import來收集所有符合自動配置條件的bean定義楷掉,并加載到IoC容器。
它引入了AutoConfigurationImportSelector類霞势。
下面我們繼續(xù)深入到@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) {
// 根據(jù)注解元信息,拿到要掃描的包愕贡,掃描并注冊到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的內(nèi)部類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);
// 保存基礎(chǔ)包
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 創(chuàng)建并使用的 IOC 容器( ApplicationContext )中吨娜。
可以看到 AutoConfigurationImportSelector 重點是實現(xiàn)了 DeferredImportSelector 接口和各種 Aware 接口巷折,然后 DeferredImportSelector 接口又繼承了 ImportSelector 接口。
其不光實現(xiàn)了 ImportSelector 接口惧所,還實現(xiàn)了很多其它的 Aware 接口骤坐,分別表示在某個時機會被回調(diào)。
自動配置實現(xiàn)邏輯的入口方法:
AutoConfigurationImportSelector將會被放到DeferredImportSelectorGrouping中的deferredImports集合中下愈。并且在DeferredImportSelectorGrouping的getImports()方法中進行統(tǒng)一處理纽绍。
跟自動配置邏輯相關(guān)的入口方法在 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方法來處理自動配置的相關(guān)邏輯拌夏,決定導入哪些配置類
this.group.process(deferredImport.getConfigurationClass().getMetadata(), deferredImport.getImportSelector());
}
// (2)經(jīng)過上面的處理后,然后再進行選擇導入哪些配置類
return this.group.selectImports();
}
其中重要的兩個步驟:
- 利用AutoConfigurationGroup的process方法來處理自動配置的相關(guān)邏輯叫编,決定導入哪些配置類
- 經(jīng)過上面的處理后辖佣,然后再進行選擇導入哪些配置類
分析自動配置的主要邏輯
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)調(diào)用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);
}
}
其中重要的三個步驟:
- 調(diào)用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的注解數(shù)據(jù)
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
// 檢查要被排除的配置類世蔗,因為有些不是自動配置類,故要拋出異常
this.checkExcludedClasses(configurations, exclusions);
// (2)將要排除的配置類移除
configurations.removeAll(exclusions);
// (3)對加載到的自動配置類按需要進行過濾
configurations = this.getConfigurationClassFilter().filter(configurations);
// (4)獲取了符合條件的自動配置類后朗兵,此時觸發(fā)AutoConfigurationImportEvent事件
// 目的是告訴ConditionEvaluationReport條件評估報告器對象來記錄符合條件的自動配置類
// 該事件什么時候會被觸發(fā)污淋?--> 在刷新容器時調(diào)用invokeBeanFactoryPostProcessors后置處理器時觸發(fā)
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());
}
繼續(xù)進入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文件,將其中設(shè)置的配置類的全路徑信息封裝 為Enumeration類對象
Enumeration urls = classLoader.getResources("META-INF/spring.factories");
//循環(huán)Enumeration類對象冗美,根據(jù)相應的節(jié)點信息生成Properties對象魔种,通過傳入 的鍵獲取值,在將值切割為一個個小的字符串轉(zhuǎn)化為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的默認提供的自動配置類叶摄。
我們下面總結(jié)下 getAutoConfigurationEntry 方法主要做的事情:
【1】從 spring.factories 配置文件中加載 EnableAutoConfiguration 自動配置類),獲取的自動配
置類如圖所示。
【2】若 @EnableAutoConfiguration 等注解標有要 exclude 的自動配置類安拟,那么再將這個自動配置類排除掉蛤吓;
【3】排除掉要 exclude 的自動配置類后,然后再調(diào)用 filter 方法進行進一步的過濾去扣,再次排除一些
不符合條件的自動配置類柱衔;
【4】經(jīng)過重重過濾后樊破,此時再觸發(fā) AutoConfigurationImportEvent 事件愉棱,告訴
ConditionEvaluationReport 條件評估報告器對象來記錄符合條件的自動配置類;
【5】 最后再將符合條件的自動配置類返回哲戚。
總結(jié)了 getAutoConfigurationEntry 方法主要的邏輯后奔滑,我們再來細看一下
AutoConfigurationImportSelector 的 filter 方法:
List<String> filter(List<String> configurations) {
long startTime = System.nanoTime();
// 將從spring.factories中獲取的自動配置類轉(zhuǎn)出字符串數(shù)組
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(這里實質(zhì)要通過candidate(自動配置類)拿到其標注的
// @ConditionalOnClass,@ConditionalOnBean和@ConditionalOnWebApplication里面的注解值)是否匹配,
boolean[] match = filter.match(candidates, this.autoConfigurationMetadata);
// 遍歷match數(shù)組顺少,注意match順序跟candidates的自動配置類一一對應
for(i = 0; i < match.length; ++i) {
// 若有不匹配的話
if (!match[i]) {
candidates[i] = null;
// 標注skipped為true
skipped = true;
}
}
}
// 這里表示若所有自動配置類經(jīng)過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 方法主要做的事情就是調(diào)用AutoConfigurationImportFilter 接口的match 方法來判斷每一個自動配置類上的條件注解(若有的話) @ConditionalOnClass ,@ConditionalOnBean 或 @ConditionalOnWebApplication 是否滿足條件,若滿足秒裕,則返回true袱蚓,說明匹配,若不滿足,則返回false說明不匹配几蜻。
我們現(xiàn)在知道 AutoConfigurationImportSelector 的 filter 方法主要做了什么事情就行了喇潘,現(xiàn)在先不用研究的過深
有選擇的導入自動配置類
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());
// 這里得到經(jīng)過濾后所有符合條件的自動配置類的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 的自動配置類梭稚,然后再排序
最后颖低,我們再總結(jié)下SpringBoot自動配置的原理,主要做了以下事情:
- 從spring.factories配置文件中加載自動配置類弧烤;
- 加載的自動配置類中排除掉 @EnableAutoConfiguration 注解的 exclude 屬性指定的自動配置類忱屑;
- 然后再用 AutoConfigurationImportFilter 接口去過濾自動配置類是否符合其標注注解(若有標注的話) @ConditionalOnClass , @ConditionalOnBean 和 @ConditionalOnWebApplication 的條件,若都符合的話則返回匹配結(jié)果暇昂;
- 然后觸發(fā) AutoConfigurationImportEvent 事件莺戒,告訴 ConditionEvaluationReport 條件評估報告器對象來分別記錄符合條件和 exclude 的自動配置類。
- 最后spring再將最后篩選后的自動配置類導入IOC容器中
畫圖梳理一下這個流程
總結(jié):
- 在當前Spring Boot工程中话浇,各個組件(jar包)脏毯,都會存在一個名稱為META-INF的目錄,目錄中有一個sping.factories文件幔崖。
- 在這個文件中會配置工廠類的全路徑如MyBatis中配置了org.mybatis.spring.boot.autoconfigure.MyBatisAutoConfiguration食店。
- Spring Boot 通過@EnableAutoConfiguration注解收集配置工廠類
- 由Spring創(chuàng)建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版本為指定的版本范圍時觸發(fā)實例化绵载。
- @ConditionalOnResource:當類路徑下有指定的資源時觸發(fā)實例化。
- @ConditionalOnJndi:在JNDI存在的條件下觸發(fā)實例化蓬蝶。
- @ConditionalOnSingleCandidate:當指定的Bean在容器中只有一個尘分,或者有多個但是指定了首 選的Bean時觸發(fā)實例化。
@ComponentScan
- @ConponentScan用于配置掃描Spring的掃包基礎(chǔ)路徑的注解丸氛。默認掃描被這個注解標記的類的目錄及子目錄培愁。
- @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);
}
調(diào)用了另外一個run方法:
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return (new SpringApplication(primarySources)).run(args);
}
這個run方法構(gòu)建了一個SpringApplication類的實例私股,我們繼續(xù)來看下SpringApplication類的構(gòu)造函數(shù)
SpringApplication構(gòu)造函數(shù)
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應用,需要啟動內(nèi)置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的所有類全路徑。
- 反射實例化上一步獲取到的類喧笔,并放入到一個集合中帽驯。
#### 根據(jù)調(diào)用棧,推斷出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;
}
這一步的主要功能是:
- 根據(jù)調(diào)用棧书闸,找到main方法所在類的class對象尼变,存儲到成員變量。
SpringApplication.run方法流程介紹
SpringApplication對象構(gòu)建完后調(diào)用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中獲取監(jiān)聽器
//1嫌术、獲取并啟動監(jiān)聽器
SpringApplicationRunListeners listeners = this.getRunListeners(args);
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//2、構(gòu)造應用上下文環(huán)境
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、獲取并啟動監(jiān)聽器
- 2现柠、構(gòu)造應用上下文環(huán)境
- 3院领、初始化應用上下文
- 4、刷新應用上下文前的準備階段
- 5够吩、刷新應用上下文
- 6比然、刷新應用上下文后的擴展接口
獲取并啟動監(jiān)聽器
事件機制在Spring是很重要的一部分內(nèi)容,通過事件機制我們可以監(jiān)聽Spring容器中正在發(fā)生的一些事件周循,同樣也可以自定義監(jiān)聽事件强法。Spring的事件為Bean和Bean之間的消息傳遞提供支持扒寄。當一個對象處理完某種任務(wù)后,通知另外的對象進行某些處理拟烫,常用的場景有進行某些操作后發(fā)送通知该编,消息、郵件等情況硕淑。
獲取監(jiān)聽器的入口代碼:
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文件中獲取監(jiān)聽器
getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args),
this.applicationStartup);
}
從代碼中看到课竣,在構(gòu)建SpringApplicationRunListeners實例之前調(diào)用了一個我們非常熟悉的方法: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
// 根據(jù)傳入的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;
}
獲取到監(jiān)聽器后迂曲,啟動監(jiān)聽器:
listeners.starting(bootstrapContext, this.mainApplicationClass);
構(gòu)造應用上下文環(huán)境
用上下文環(huán)境包括什么呢?包括計算機的環(huán)境寥袭,Java環(huán)境路捧,Spring的運行環(huán)境,Spring項目的配置(在SpringBoot中就是那個熟悉的application.properties/yml)等等传黄。
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
//創(chuàng)建并配置相應的環(huán)境
ConfigurableEnvironment environment = this.getOrCreateEnvironment();
//根據(jù)用戶配置杰扫,配置 environment系統(tǒng)環(huán)境
this.configureEnvironment((ConfigurableEnvironment)environment, applicationArguments.getSourceArgs());
// 啟動相應的監(jiān)聽器,其中一個重要的監(jiān)聽器 ConfigFileApplicationListener 就是加載項目配置文件的監(jiān)聽器膘掰。
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,則創(chuàng)建Servlet類型的環(huán)境
return new ApplicationServletEnvironment();
case REACTIVE:
// 如果應用類型是響應式編程的,則創(chuàng)建Reactive類型的環(huán)境
return new ApplicationReactiveWebEnvironment();
default:
return new ApplicationEnvironment();
}
}
ApplicationServletEnvironment類型的繼承關(guān)系:
/**
* 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方法的參數(shù)分組到SimpleCommandLinePropertySource中
configurePropertySources(environment, args);
// 激活相關(guān)的配置文件
configureProfiles(environment, args);
}
初始化應用上下文
/**
*用于創(chuàng)建 {@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() {
// 根據(jù)應用類型創(chuàng)建對應的 ApplicationContext實例
return this.applicationContextFactory.create(this.webApplicationType);
}
調(diào)試進入:
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的類繼承關(guān)系:
GenericApplicationContext是AnnotationConfigServletWebServerApplicationContext的父類凡伊。
創(chuàng)建對象的時候,創(chuàng)建了一個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);
}
另外在調(diào)用AnnotationConfigServletWebServerApplicationContext構(gòu)造函數(shù)之前,要先調(diào)用父類的構(gòu)造函數(shù):
public GenericApplicationContext() {
this.customClassLoader = false;
this.refreshed = new AtomicBoolean();
this.beanFactory = new DefaultListableBeanFactory();
}
AnnotationConfigServletWebServerApplicationContext中的IoC容器在其父類的構(gòu)造方法中被實例化窒舟。
刷新應用上下文前的準備階段
刷新應用上下文前的準備階段系忙,就是想上下文context中設(shè)置一些屬性,完成bean對象的創(chuàng)建辜纲。
private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
// 設(shè)置容器環(huán)境
context.setEnvironment(environment);
// 執(zhí)行容器后置處理器笨觅,向context中設(shè)置值
postProcessApplicationContext(context);
// 遍歷初始化器,執(zhí)行初始化
applyInitializers(context);
// 向各個監(jiān)聽器去發(fā)送容器準備好的事件
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的構(gòu)造方法中,將構(gòu)造方法中的參數(shù)直接添加到了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發(fā)現(xiàn),在啟動流程中,這個方法還沒有被調(diào)用過羹呵。因此骂际,當前狀態(tài)下primarySources集合中只有SpringApplication.run(SpringBootDemoApplication.class, args);
參數(shù)中傳入的啟動類。
另外一個集合sources的只在一個方法中賦值:
public void setSources(Set<String> sources) {
Assert.notNull(sources, "Sources must not be null");
this.sources = new LinkedHashSet<>(sources);
}
這個方法只被SampleSpringXmlApplication類中的main方法調(diào)用冈欢,而Spring Boot的啟動類是通過SpringApplication的run方法啟動的歉铝。因此sources集合是空的。
Set<Object> sources = getAllSources();在Spring Boot工程啟動過程中凑耻,拿到的是SpringBoot啟動類太示,就是傳入到SpringApplication.run()方法的第一個參數(shù)。
驗證:
斷點打在run方法
primarySources 集合中只有一個SpringBootDemoApplication類對象
這個類對象正好是我們的啟動類
斷點打到getAllResource
allSources中只有我們的啟動類對象香浩,sources集合為空类缤,驗證了我們的結(jié)論。
接著看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));
}
// 創(chuàng)建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();
}
在創(chuàng)建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容器直接拿出來餐弱,并且轉(zhuǎn)成BeanDefinitionRegistry。在前面看AnnotationConfigServletWebApplicationContext類繼承關(guān)系的時候知道囱晴,它實現(xiàn)了BeanDefinitionRegistry接口膏蚓,因此可以強轉(zhuǎn)。并且可以驗證這里就是AnnotationConfigServletWebApplicationContext速缆。
BeanDefinitionRegistry定義了很重要的方法registerBeanDefinition()降允,該方法將BeanDefinition 注冊進DefaultListableBeanFactory容器的beanDefinitionMap中。
現(xiàn)在再來看創(chuàng)建BeanDefinitionLoader的過程:
protected BeanDefinitionLoader createBeanDefinitionLoader(BeanDefinitionRegistry registry, Object[] sources) {
return new BeanDefinitionLoader(registry, sources);
}
構(gòu)造方法:
/**
* 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));
}
在該方法中創(chuàng)建了reader和scanner艺糜,這兩個很熟悉的東西,后面肯定會使用reader去讀取配置幢尚,scaner進行包掃描破停。
接著看loader.load();方法:
/**
* Load the sources into the reader.
*/
void load() {
// 遍歷啟動類,分別進行l(wèi)oad
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閉包理茎。不是沒有構(gòu)造方法的
return !(type.isAnonymousClass() || isGroovyClosure(type) || hasNoConstructors(type));
}
這里黑界,啟動類是符合注冊條件的。
所以啟動類會被注冊到IoC容器中皂林。
this.annotatedReader.register(source);
而annotatedReader是BeanDefinitionLoader構(gòu)造函數(shù)中被實例化的朗鸠,并且它是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();
}
直接直接調(diào)用了application的refresh方法
public void refresh() throws BeansException, IllegalStateException {
synchronized(this.startupShutdownMonitor) {
StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
// 第?步:刷新前的預處理
this.prepareRefresh();
/*
第?步:
獲取BeanFactory烛占;默認實現(xiàn)是DefaultListableBeanFactory
加載BeanDefition 并注冊到 BeanDefitionRegistry
*/
ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
// 第三步:BeanFactory的預準備?作(BeanFactory進??些設(shè)置,?如context的類加載器等)
this.prepareBeanFactory(beanFactory);
try {
// 第四步:BeanFactory準備?作完成后進?的后置處理?作
this.postProcessBeanFactory(beanFactory);
StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
// 第五步:實例化并調(diào)?實現(xiàn)了BeanFactoryPostProcessor接?的Bean
this.invokeBeanFactoryPostProcessors(beanFactory);
// 第六步:注冊BeanPostProcessor(Bean的后置處理器),在創(chuàng)建bean的前后等執(zhí)?
this.registerBeanPostProcessors(beanFactory);
beanPostProcess.end();
// 第七步:初始化MessageSource組件(做國際化功能忆家;消息綁定犹菇,消息解析);
this.initMessageSource();
// 第?步:初始化事件派發(fā)器
this.initApplicationEventMulticaster();
// 第九步:?類重寫這個?法芽卿,在容器刷新的時候可以?定義邏輯
this.onRefresh();
// 第?步:注冊應?的監(jiān)聽器揭芍。就是注冊實現(xiàn)了ApplicationListener接?的監(jiān)聽器bean
this.registerListeners();
/*
第??步:
初始化所有剩下的?懶加載的單例bean
初始化創(chuàng)建?懶加載?式的單例Bean實例(未設(shè)置屬性)
填充屬性
初始化?法調(diào)?(?如調(diào)?afterPropertiesSet?法、init-method?法)
調(diào)?BeanPostProcessor(后置處理器)對實例bean進?后置處
*/
this.finishBeanFactoryInitialization(beanFactory);
/*
第??步:
完成context的刷新卸例。主要是調(diào)?LifecycleProcessor的onRefresh()?法沼沈,并且發(fā)布事
件 (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擴展機制實現(xiàn)的自動裝
配,SpringBoot的自動裝配同樣也是在invokeBeanFactoryPostProcessors()方法中實現(xiàn)的呻袭。還有
一種情況眨八,在SpringBoot中有很多的@EnableXXX注解,細心點進去看的應該就知道其底層是
@Import注解左电,在invokeBeanFactoryPostProcessors()方法中也實現(xiàn)了對該注解指定的配置類的
定位加載廉侧。
常規(guī)的在SpringBoot中有三種實現(xiàn)定位,第一個是主類所在包的篓足,第二個是SPI擴展機制實現(xiàn)
的自動裝配(比如各種starter)段誊,第三種就是@Import注解指定的類。(對于非常規(guī)的不說了)
2栈拖,第二步:BeanDefinition的載入
在第一步中說了三種Resource的定位情況连舍,定位后緊接著就是BeanDefinition的分別載入。
所謂的載入就是通過上面的定位得到的basePackage涩哟,SpringBoot會將該路徑拼接成:
classpath:com/lagou/**/.class這樣的形式索赏,然后一個叫做
xPathMatchingResourcePatternResolver的類會將該路徑下所有的.class文件都加載進來,然后
遍歷判斷是不是有@Component注解贴彼,如果有的話潜腻,就是我們要裝載的BeanDefinition。大致過
程就是這樣的了锻弓。
3砾赔、第三個過程:注冊BeanDefinition
這個過程通過調(diào)用上文提到的BeanDefinitionRegister接口的實現(xiàn)來完成。這個注冊過程把載入
過程中解析得到的BeanDefinition向IoC容器進行注冊。通過上文的分析暴心,我們可以看到妓盲,在IoC容
器中將BeanDefinition注入到一個ConcurrentHashMap中,IoC容器就是通過這個HashMap來持
有這些BeanDefinition數(shù)據(jù)的专普。比如DefaultListableBeanFactory 中的beanDefinitionMap屬性悯衬。
OK,總結(jié)完了檀夹,接下來我們通過代碼看看具體是怎么實現(xiàn)的筋粗。
刷新應用上下文后的擴展接口
/**
* Called after the context has been refreshed.
* @param context the application context
* @param args the application arguments
*/
protected void afterRefresh(ConfigurableApplicationContext context, ApplicationArguments args) {
}
擴展接口,設(shè)計模式中的模板方法炸渡,默認為空實現(xiàn)娜亿。如果有自定義需求,可以重寫該方法蚌堵。比如打印一些啟動結(jié)束log买决,或者一些其它后置處理。