傳統(tǒng)的Spring框架實(shí)現(xiàn)一個(gè)Web服務(wù),需要導(dǎo)入各種依賴AR包,然后編寫對(duì)應(yīng)的XML配置文件等,相較而言,Spring Boot顯得更加方便、快捷和高效。那么,Spring Boot究竟如何做到這些的呢?
接下來分別針對(duì)Spring Boot框架的依賴管理、自動(dòng)配置和執(zhí)行流程進(jìn)行深入分析
依賴管理
問題∶(1)為什么導(dǎo)入dependency時(shí)不需要指定版本?在Spring Boot入門程序中,項(xiàng)目pom.xml文件有兩個(gè)核心依賴,分別是spring-boot-starter-parent和spring-boot-starter-web,關(guān)于這兩個(gè)依賴的相關(guān)介紹具體如下∶
spring-boot-starter-parent依賴
在chapter01項(xiàng)目中的pom.xml文件中找到spring-boot-starter-parent依賴,示例代碼如下∶
<!--Spring Boot父項(xiàng)目依賴管理-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent<11./artifactId>
<version>2.2.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
上述代碼中,將spring-bot-starter-parent依賴作為Spring Boot項(xiàng)目的統(tǒng)一父項(xiàng)目依賴管理,并將項(xiàng)目版本號(hào)統(tǒng)一為2.2.2.RELEASE,該版本號(hào)根據(jù)實(shí)際開發(fā)需求是可以修改的
使用"Ctrl+鼠標(biāo)左鍵"進(jìn)入并查看spring-boot-starter-parent底層源文件,發(fā)現(xiàn)spring-boot-starter-parent的底層有一個(gè)父依賴spring-boot-dependencies,核心代碼具體如下
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.2.2.RELEASE</version>
<relativePath>../../spring-boot-dependencies</relativePath>
</parent>
繼續(xù)查看spring-boot-dependencies底層源文件,核心代碼具體如下∶
<properties>
<activemq.version>5.15.11</activemq.version>
...
<solr.version>8.2.0</solr.version>
<mysql.version>8.0.18</mysql.version>
<kafka.version>2.3.1</kafka.version>
<spring-amqp.version>2.2.2.RELEASE</spring-amqp.version>
<spring-restdocs.version>2.0.4.RELEASE</spring-restdocs.version>
<spring-retry.version>1.2.4.RELEASE</spring-retry.version>
<spring-security.version>5.2.1.RELEASE</spring-security.version>
<spring-session-bom.version>Corn-RELEASE</spring-session-bom.version>
<spring-ws.version>3.0.8.RELEASE</spring-ws.version>
<sqlite-jdbc.version>3.28.0</sqlite-jdbc.version>
<sun-mail.version>${jakarta-mail.version}</sun-mail.version>
<tomcat.version>9.0.29</tomcat.version>
<thymeleaf.version>3.0.11.RELEASE</thymeleaf.version>
<thymeleaf-extras-data-attribute.version>2.0.1</thymeleaf-extras-data-attribute.version>
...
</properties>
從spring-boot-dependencies底層源文件可以看出,該文件通過標(biāo)簽對(duì)一些常用技術(shù)框架的依賴文件進(jìn)行了統(tǒng)一版本號(hào)管理,例如activemq、spring、tomcat等,都有與Spring Boot 2.22版本相匹配的版本,這也是pom.xml引入依賴文件不需要標(biāo)注依賴文件版本號(hào)的原因。
需要說明的是,如果pom.xml引入的依賴文件不是spring-botstarter-parent管理的,那么在pom.xml引入依賴文件時(shí),需要使用標(biāo)簽指定依賴文件的版本號(hào)。
(2)問題2∶spring-boot-starter-parent父依賴啟動(dòng)器的主要作用是進(jìn)行版本統(tǒng)一管理,那么項(xiàng)目運(yùn)行依賴的JAR包是從何而來的?
spring-boot-starter-web依賴
查看spring-boot-starter-web依賴文件源碼,核心代碼具體如下
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.2.2.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
<version>2.2.2.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<version>2.2.2.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
<version>2.2.2.RELEASE</version>
<scope>compile</scope>
<exclusions>
<exclusion>
<artifactId>tomcat-embed-el</artifactId>
<groupId>org.apache.tomcat.embed</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.2.2.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.2.RELEASE</version>
<scope>compile</scope>
</dependency>
</dependencies>
從上述代碼可以發(fā)現(xiàn),spring-boot-starter-web依賴啟動(dòng)器的主要作用是提供Web開發(fā)場(chǎng)景所需的底層所有依賴
正是如此,在pom.xml中引入spring-bot-starter-web依賴啟動(dòng)器時(shí),就可以實(shí)現(xiàn)Web場(chǎng)景開發(fā),而不需要額外導(dǎo)入Tomcat服務(wù)器以及其他Web依賴文件等。當(dāng)然,這些引入的依賴文件的版本號(hào)還是由spring-bot-starter-parent父依賴進(jìn)行的統(tǒng)一管理。
Spring Boot除了提供有上述介紹的Web依賴啟動(dòng)器外,還提供了其他許多開發(fā)場(chǎng)景的相關(guān)依賴,我們可以打開Spring Boot官方文檔,搜索"Starters"關(guān)鍵字查詢場(chǎng)景依賴啟動(dòng)器

列出了Spring Boot官方提供的部分場(chǎng)景依賴啟動(dòng)器,這些依賴啟動(dòng)器適用于不同的場(chǎng)景開發(fā),使用時(shí)只需要在poxxml文件中導(dǎo)入對(duì)應(yīng)的依賴啟動(dòng)器即可。
需要說明的是,Spring Boot官方并不是針對(duì)所有場(chǎng)景開發(fā)的技術(shù)框架都提供了場(chǎng)景啟動(dòng)器,例如數(shù)據(jù)庫操作框架MyBatis、阿里巴巴的Druid數(shù)據(jù)源等,Spring Boot官方就沒有提供對(duì)應(yīng)的依賴啟動(dòng)器。為了充分利用Spring Boot框架的優(yōu)勢(shì),在Spring Boot官方?jīng)]有整合這些技術(shù)框架的情況下,MyBatis、Druid等技術(shù)框架所在的開發(fā)團(tuán)隊(duì)主動(dòng)與Spring Boot框架進(jìn)行了整合,實(shí)現(xiàn)了各自的依賴啟動(dòng)器,例如mybatis-spring-boot-starter、druid-spring-boot-starter等。我們?cè)趐om.xml文件中引入這些第三方的依賴啟動(dòng)器時(shí),切記要配置對(duì)應(yīng)的版本號(hào)
自動(dòng)配置(啟動(dòng)流程)
概念∶能夠在我們添加ar包依賴的時(shí)候,自動(dòng)為我們配置一些組件的相關(guān)配置,我們無需配置或者只需要少量配置就能運(yùn)行編寫的項(xiàng)目
問題∶Spring Boot到底是如何進(jìn)行自動(dòng)配置的,都把哪些組件進(jìn)行了自動(dòng)配置?
Spring Boot應(yīng)用的啟動(dòng)入口是@SpringBootApplication注解標(biāo)注類中的main(方法,@SpringBootApplication能夠掃描Spring組件并自動(dòng)配置Spring Boot
下面,查看@SpringBootAppliction內(nèi)部源碼進(jìn)行分析,核心代碼具體如下
@SpringBootApplication
public class SpringbootDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootDemoApplication.class, args);
}
}
@Target({ElementType.TYPE}) // 注解的適用范圍,Type表示注解可以描述在類、接口、注解或枚舉
@Retention(RetentionPolicy.RUNTIME) // 表示注解的生命周期,Runtime運(yùn)行時(shí)
@Documented // 表示注解可以記錄在javadoc中
@Inherited // 表示可以被子類繼承該注解
@SpringBootConfiguration // 標(biāo)明該類為配置類
@EnableAutoConfiguration // 啟動(dòng)自動(dòng)配置功能
@ComponentScan( // 包掃描器
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
...
}
從上述源碼可以看出,@SpringBootAplication注解是一個(gè)組合注解,前面4個(gè)是注解的元數(shù)據(jù)信息,我們主要看后面3個(gè)注解∶@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan三個(gè)核心注解,關(guān)于這三個(gè)核心注解的相關(guān)說明具體如下∶
@springBootConfiguration注解
@SpringBootConfiguration注解表示Spring Boot配置類。查看@SpringBootConfiguration注解源碼,核心代碼具體如下。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration // 配置 IOC 容器
public @interface SpringBootConfiguration {
}
從上述源碼可以看出,@SpringBootConfiguration注解內(nèi)部有一個(gè)核心注解@Configuration,該注解是Spring框架提供的,表示當(dāng)前類為一個(gè)配置類(XML配置文件的注解表現(xiàn)形式),并可以被組件掃描器掃描。由此可見,@SpringBootConfiguration注解的作用與@Configuration注解相同,都是標(biāo)識(shí)一個(gè)可以被組件掃描器掃描的配置類,只不過@SpringBootConfiguration是被Spring Boot進(jìn)行了重新封裝命名而已
@EnableAutoConfiguration注解
@EnableAutoConfguration注解表示開啟自動(dòng)配置功能,該注解是Spring Bot框架最重要的注解,也是實(shí)現(xiàn)自動(dòng)化配置的注解。同樣,查看該注解內(nèi)部查看源碼信息,核心代碼具體如下

可以發(fā)現(xiàn)它是一個(gè)組合注解,Spring中有很多以Enable開頭的注解,其作用就是借助@lmport來收集并注冊(cè)特定場(chǎng)景相關(guān)的bean,并加載到loC容器。@EnableAutoConfiguration就是借助@lmport來收集所有符合自動(dòng)配置條件的bean定義,并加載到loC容器。
下面,對(duì)這兩個(gè)核心注解分別講解∶
(1)@AutoConfigurationPackage注解
查看@AutoConfigurationPackage注解內(nèi)部源碼信息,核心代碼具體如下∶
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({Registrar.class}) // 導(dǎo)入 Registrar 中注冊(cè)的組件
public @interface AutoConfigurationPackage {
}
從上述源碼可以看出,@AutoConfigurationPackage注解的功能是由@lmport注解實(shí)現(xiàn)的,它是spring 框架的底層注解,它的作用就是給容器中導(dǎo)入某個(gè)組件類,例如@lmportAutoConfigurationPackages.Registrar.cass,它就是將Registrar這個(gè)組件類導(dǎo)入到容器中,可查看Registrar類中registerBeanDefinitions方法,這個(gè)方法就是導(dǎo)入組件類的具體實(shí)現(xiàn)∶

從上述源碼可以看出,在Registrar類中有一個(gè)registerBeanDefinitions0)方法,使用Debug模式啟動(dòng)項(xiàng)目,可以看到選中的部分就是com.lagou。也就是說,@AutoConfigurationPackage注解的主要作用就是將主程序類所在包及所有子包下的組件到掃描到spring容器中。
因此在定義項(xiàng)目包結(jié)構(gòu)時(shí),要求定義的包結(jié)構(gòu)非常規(guī)范,項(xiàng)目主程序啟動(dòng)類要定義在最外層的根目錄位置,然后在根目錄位置內(nèi)部建立子包和類進(jìn)行業(yè)務(wù)開發(fā),這樣才能夠保證定義的類能夠被組件掃描器掃描
(2)@lmport(AutoConfigurationlmportSelector.class):
將AutoConfigurationlmportSelector這個(gè)類導(dǎo)入到spring容器中,AutoConfigurationlmportsSelector可以幫助springboo應(yīng)用將所有符合條件的@Confguration 配置都加載到當(dāng)前SpringBoot創(chuàng)建并使用的loC容器(ApplicationCcontext)中
繼續(xù)研究AutoConfigurationlmportSelector這個(gè)類,通過源碼分析這個(gè)類中是通過selectmports這個(gè)方法告訴springboot都需要導(dǎo)入那些組件∶

深入研究loadMetadata方法

深入getCandidateConfigurations方法
方法中有一個(gè)重要方法load FactoryNames,這個(gè)方法是讓SpringFactoryLoader去加載一些組件的名字。

繼續(xù)點(diǎn)開loadFactory方法
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
//獲取出入的鍵
String factoryTypeName = factoryType.getName();
return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
if (result != null) {
return result;
} else {
try {
//如果類加載器不為null,則加載類路徑下spring.factories文件,將其中設(shè)置的配置類的全路徑信息封裝 為Enumeration類對(duì)象
Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
LinkedMultiValueMap result = new LinkedMultiValueMap();
//循環(huán)Enumeration類對(duì)象,根據(jù)相應(yīng)的節(jié)點(diǎn)信息生成Properties對(duì)象,通過傳入的鍵獲取值,在將值切割為一個(gè)個(gè)小的字符串轉(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[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
int var10 = var9.length;
for(int var11 = 0; var11 < var10; ++var11) {
String factoryImplementationName = var9[var11];
result.add(factoryTypeName, factoryImplementationName.trim());
}
}
}
cache.put(classLoader, result);
return result;
} catch (IOException var13) {
throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
}
}
}
會(huì)去讀取一個(gè)spring.factories的文件,讀取不到會(huì)表這個(gè)錯(cuò)誤,我們繼續(xù)根據(jù)會(huì)看到,最終路徑的長這樣,而這個(gè)是spring提供的一個(gè)工具類
public final class SpringFactoriesLoader {
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
}
它其實(shí)是去加載一個(gè)外部的文件,而這文件是在


@EnableAutoConfiguration就是從classpath中搜尋META-INF/spring.factories配置文件,并將其中
org.springframeworkboot.autoconfigure.EnableutoConfiguration對(duì)應(yīng)的配置項(xiàng)通過反射Java Refletion)實(shí)例化為對(duì)應(yīng)的標(biāo)注了@Configuration的avaConfig形式的配置類,并加載到IOC容器中
以剛剛的項(xiàng)目為例,在項(xiàng)目中加入了Web環(huán)境依賴啟動(dòng)器,對(duì)應(yīng)的WebMvcAutoConfiguration自動(dòng)配置類就會(huì)生效,打開該自動(dòng)配置類會(huì)發(fā)現(xiàn),在該配置類中通過全注解配置類的方式對(duì)Spring MVC運(yùn)行所需環(huán)境進(jìn)行了默認(rèn)配置,包括默認(rèn)前綴、默認(rèn)后綴、視圖解析器、MVC校驗(yàn)器等。而這些自動(dòng)配置類的本質(zhì)是傳統(tǒng)Spring MVC框架中對(duì)應(yīng)的XML配置文件,只不過在Spring Boot中以自動(dòng)配置類的形式進(jìn)行了預(yù)先配置。因此,在Spring Boot項(xiàng)目中加入相關(guān)依賴啟動(dòng)器后,基本上不需要任何配置就可以運(yùn)行程序,當(dāng)然,我們也可以對(duì)這些自動(dòng)配置類中默認(rèn)的配置進(jìn)行更改
-
總結(jié)
因此springboot底層實(shí)現(xiàn)自動(dòng)配置的步驟是∶
- springboot應(yīng)用啟動(dòng);
2.@SpringBootApplication起作用;
3.@EnableAutoConfiguration;
4.@AutoConfigurationPackage∶這個(gè)組合注解主要是@lmport(AutoConfigurationPackages.Registrar.class),它通過將Registrar類導(dǎo)入到容器中,而Registrar類作用是掃描主配置類同級(jí)目錄以及子包,并將相應(yīng)的組件導(dǎo)入到springboot創(chuàng)建管理的容器中;
5.@lmport(AutoConfigurationlmportSelector.cdass)∶它通過將AutoConfigurationlmportSelector類導(dǎo)入到容器中,AutoConfigurationlmportSelector類作用是通過selectmports方法執(zhí)行的過程中,會(huì)使用內(nèi)部工具類SpringFactoriesLoader,查找classpath上所有jar包中的META-INF/spring.factories進(jìn)行加載,實(shí)現(xiàn)將配置類信息交給SpringFactory加載器進(jìn)行一系列的容器創(chuàng)建過程
@ComponentScan注解
@ComponentScan注解具體掃描的包的根路徑由Spring Boot項(xiàng)目主程序啟動(dòng)類所在包位置決定,在掃描過程中由前面介紹的@AutoConfigurationPackage注解進(jìn)行解析,從而得到Spring Boot項(xiàng)目主程序啟動(dòng)類所在包的具體位置
總結(jié)∶
@SpringBootApplication的注解的功能就分析差不多了,簡(jiǎn)單來說就是3個(gè)注解的組合注解∶
|- @SpringBootConfiguration
|- @Configuration //通過javaConfig的方式來添加組件到IOc容器中
|- @EnableAutoConfiguration
|- @AutoConfigurationPackage //自動(dòng)配置包,與eCcomponentscan掃描到的添加到IOC
|- @Import(AutoConfigurationImportSelector.class) //到META-INF/spring.factories中定義的bean添加到IOC容器中
|- @ComponentScan //包掃描
自定義Stater
SpringBoot starter機(jī)制
SpringBoo由眾多Starter組成(一系列的自動(dòng)化配置的starter插件),SpringBoot之所以流行,也是因?yàn)閟tarter。
starter是SpringBoo誹常重要的一部分,可以理解為一個(gè)可拔插式的插件,正是這些starter使得使用某個(gè)功能的開發(fā)者不需要關(guān)注各種依賴庫的處理,不需要具體的配置信息,由Spring Boot自動(dòng)通過classpath路徑下的類發(fā)現(xiàn)需要的Bean,并織入相應(yīng)的Bean。
例如,你想使用Reids插件,那么可以使用spring·boot-starter-redis;如果想使用MongoDB,可以使用spring-boot-starter-data-mongodb
為什么要自定義starter
開發(fā)過程中,經(jīng)常會(huì)有一些獨(dú)立于業(yè)務(wù)之外的配置模塊。如果我們將這些可獨(dú)立于業(yè)務(wù)代碼之外的功能配置模塊封裝成一個(gè)個(gè)starter,復(fù)用的時(shí)候只需要將其在pom中引用依賴即可,SpringBoot為我們完成自動(dòng)裝配
自定義starter的命名規(guī)則
SpringBoot提供的starter以spring-boot-starter-xxx的方式命名的。官方建議自定義的starter使用xx-spring-boot-starter命名規(guī)則。以區(qū)分SpringBoot生態(tài)提供的starter
整個(gè)過程分為兩部分∶
● 自定義starter
● 使用starter
首先,先完成自定義starter
(1)新建mavenjar工程,工程名為zdy-spring-boot-starter,導(dǎo)入依賴∶
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>2.2.2.RELEASE</version>
</dependency>
</dependencies>
(2)編寫javaBean
package com.study.pojo;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
@EnableConfigurationProperties(SimpleBean.class)
@ConfigurationProperties(prefix = "simplebean")
public class SimpleBean {
private Integer id;
private String name;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "SimpleBean{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
(3)編寫配置類MyAutoConfiguration
@Configuration
@ConditionalOnClass // Conditional0OnClass∶當(dāng)類路徑classpath下有指定的類的情況下進(jìn)行自動(dòng)配置
public class MyConfiguration {
static {
System.out.println("MyAutoConfiguration init....");
}
@Bean
public SimpleBean simpleBean(){
return new SimpleBean();
}
}
(4)resources下創(chuàng)建/META-INF/spring.factories
注意∶META-NF是自己手動(dòng)創(chuàng)建的目錄,spring.factories也是手動(dòng)創(chuàng)建的文件,在該文件中配置自己的自動(dòng)配置類

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.study.config.MyAutoConfiguration
使用自定義starter
(1)導(dǎo)入自定義starter的依賴
<dependency>
<groupId>com.study</groupId>
<artifactId>zdy-spring-boot-starter</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
(2)在全局配置文件中配置屬性值
simplebean.id=1
simplebean.name=自定義starter
(3)編寫測(cè)試方法
// 測(cè)試自定義starter
@Autowired
private SimpleBean simpleBean;
@Test
void contextLoads() {
System.out.println(simpleBean);
}
執(zhí)行原理
每個(gè)Spring Boot項(xiàng)目都有一個(gè)主程序啟動(dòng)類,在主程序啟動(dòng)類中有一個(gè)啟動(dòng)項(xiàng)目的main0方法,在該方法中通過執(zhí)行SpringApplication.run0即可啟動(dòng)整個(gè)Spring Boot程序。
問題∶那么SpringAplication.run()方法到底是如何做到啟動(dòng)Spring Boot項(xiàng)目的呢?
下面我們查看run()方法內(nèi)部的源碼,核心代碼具體如下∶
@SpringBootApplication
public class SpringBootDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootDemoApplication.class, args);
}
}
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class[]{primarySource}, args);
}
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return (new SpringApplication(primarySources)).run(args);
}
從上述源碼可以看出,SpringApplication.run()方法內(nèi)部執(zhí)行了兩個(gè)操作,分別是SpringApplication實(shí)例的初始化創(chuàng)建和調(diào)用run0)啟動(dòng)項(xiàng)目,這兩個(gè)階段的實(shí)現(xiàn)具體說明如下
SpringApplication實(shí)例的初始化創(chuàng)建
查看SpringApplication實(shí)例對(duì)象初始化創(chuàng)建的源碼信息,核心代碼具體如下
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 = new HashSet();
this.isCustomEnvironment = false;
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
//項(xiàng)目啟動(dòng)類 SpringbootDemoApplication.class設(shè)置為屬性存儲(chǔ)起來
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
//設(shè)置應(yīng)用類型是SERVLET應(yīng)用(Spring 5之前的傳統(tǒng)MVC應(yīng)用)還是REACTIVE應(yīng)用(Spring 5開始出現(xiàn)的WebFlux交互式應(yīng)用)
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 設(shè)置初始化器(Initializer),最后會(huì)調(diào)用這些初始化器
//所謂的初始化器就是org.springframework.context.ApplicationContextInitializer的實(shí)現(xiàn)類,在Spring上下文被刷新之前進(jìn)行初始化的操作
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 設(shè)置監(jiān)聽器(Listener)
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 初始化 mainApplicationClass 屬性:用于推斷并設(shè)置項(xiàng)目main()方法啟動(dòng)的主程序啟動(dòng)類
this.mainApplicationClass = deduceMainApplicationClass();
}
從上述源碼可以看出,SpringApplication的初始化過程主要包括4部分,具體說明如下。
(1)this.webApplicationType=WebApplicationType.deduceFromClasspath()
用于判斷當(dāng)前webApplicationType應(yīng)用的類型。deduceFromClasspath)方法用于查看Classpath類路徑下是否存在某個(gè)特征類,從而判斷當(dāng)前webApplicationType類型是SERVLET應(yīng)用(Spring 5之前的傳統(tǒng)MVC應(yīng)用)還是REACTVE應(yīng)用(Spring 5開始出現(xiàn)的WebFlux交互式應(yīng)用)
(2)thissetnitilizers(this.getSpringFactoriesinstances(ApplicationContextInitializer.cass)
用于SpringApplication應(yīng)用的初始化器設(shè)置。在初始化器設(shè)置過程中,會(huì)使用Spring類加載器SpringFactoriesLoader從META-INF/spring.factories類路徑下的META-INF下的spring.factores文件中獲取所有可用的應(yīng)用初始化器類ApplicationContextInitializer。
(3)this.setListeners(this. getSpringFactoriesinstances(ApplicationListener.dass)
用于SpringApplication應(yīng)用的監(jiān)聽器設(shè)置。監(jiān)聽器設(shè)置的過程與上一步初始化器設(shè)置的過程基本一樣,也是使用SpringFactoriesLoader從META-INF/spring.factries類路徑下的METAINF下的spring factores文件中獲取所有可用的監(jiān)聽器類ApplicationListener。
(4)this.mainAplicationClass= this.deduceMainApplicationlass()
用于推斷并設(shè)置項(xiàng)目main()方法啟動(dòng)的主程序啟動(dòng)類
項(xiàng)目的初始化啟動(dòng)
分析完(new SpringApplication(primarySources).run(args)源碼前一部分SpringApplication實(shí)例對(duì)象的初始化創(chuàng)建后,查看run(args)方法執(zhí)行的項(xiàng)目初始化啟動(dòng)過程,核心代碼具體如下∶
public ConfigurableApplicationContext run(String... args) {
// 創(chuàng)建 StopWatch 對(duì)象,并啟動(dòng)。StopWatch 主要用于簡(jiǎn)單統(tǒng)計(jì) run 啟動(dòng)過程的時(shí)長。
StopWatch stopWatch = new StopWatch();
stopWatch.start();
// 初始化應(yīng)用上下文和異常報(bào)告集合
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
// 配置 headless 屬性
configureHeadlessProperty();
// (1)獲取并啟動(dòng)監(jiān)聽器
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
// 創(chuàng)建 ApplicationArguments 對(duì)象 初始化默認(rèn)應(yīng)用參數(shù)類
// args是啟動(dòng)Spring應(yīng)用的命令行參數(shù),該參數(shù)可以在Spring應(yīng)用中被訪問。如:--server.port=9000
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//(2)項(xiàng)目運(yùn)行環(huán)境Environment的預(yù)配置
// 創(chuàng)建并配置當(dāng)前SpringBoot應(yīng)用將要使用的Environment
// 并遍歷調(diào)用所有的SpringApplicationRunListener的environmentPrepared()方法
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
configureIgnoreBeanInfo(environment);
// 準(zhǔn)備Banner打印器 - 就是啟動(dòng)Spring Boot的時(shí)候打印在console上的ASCII藝術(shù)字體
Banner printedBanner = printBanner(environment);
// (3)創(chuàng)建Spring容器
context = createApplicationContext();
// 獲得異常報(bào)告器 SpringBootExceptionReporter 數(shù)組
//這一步的邏輯和實(shí)例化初始化器和監(jiān)聽器的一樣,
// 都是通過調(diào)用 getSpringFactoriesInstances 方法來獲取配置的異常類名稱并實(shí)例化所有的異常處理類。
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
// (4)Spring容器前置處理
//這一步主要是在容器刷新之前的準(zhǔn)備動(dòng)作。包含一個(gè)非常關(guān)鍵的操作:將啟動(dòng)類注入容器,為后續(xù)開啟自動(dòng)化配置奠定基礎(chǔ)。
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
// (5):刷新容器
refreshContext(context);
// (6):Spring容器后置處理
//擴(kuò)展接口,設(shè)計(jì)模式中的模板方法,默認(rèn)為空實(shí)現(xiàn)。
// 如果有自定義需求,可以重寫該方法。比如打印一些啟動(dòng)結(jié)束log,或者一些其它后置處理
afterRefresh(context, applicationArguments);
// 停止 StopWatch 統(tǒng)計(jì)時(shí)長
stopWatch.stop();
// 打印 Spring Boot 啟動(dòng)的時(shí)長日志。
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
// (7)發(fā)出結(jié)束執(zhí)行的事件通知
listeners.started(context);
// (8):執(zhí)行Runners
//用于調(diào)用項(xiàng)目中自定義的執(zhí)行器XxxRunner類,使得在項(xiàng)目啟動(dòng)完成后立即執(zhí)行一些特定程序
//Runner 運(yùn)行器用于在服務(wù)啟動(dòng)時(shí)進(jìn)行一些業(yè)務(wù)初始化操作,這些操作只在服務(wù)啟動(dòng)后執(zhí)行一次。
//Spring Boot提供了ApplicationRunner和CommandLineRunner兩種服務(wù)接口
callRunners(context, applicationArguments);
} catch (Throwable ex) {
// 如果發(fā)生異常,則進(jìn)行處理,并拋出 IllegalStateException 異常
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
// (9)發(fā)布應(yīng)用上下文就緒事件
//表示在前面一切初始化啟動(dòng)都沒有問題的情況下,使用運(yùn)行監(jiān)聽器SpringApplicationRunListener持續(xù)運(yùn)行配置好的應(yīng)用上下文ApplicationContext,
// 這樣整個(gè)Spring Boot項(xiàng)目就正式啟動(dòng)完成了。
try {
listeners.running(context);
} catch (Throwable ex) {
// 如果發(fā)生異常,則進(jìn)行處理,并拋出 IllegalStateException 異常
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
//返回容器
return context;
}
從上述源碼可以看出,項(xiàng)目初始化啟動(dòng)過程大致包括以下部分∶
● 第一步∶獲取并啟動(dòng)監(jiān)聽器
this.getRunListeners(args)和listeners.starting()方法主要用于獲取SpringApplication 實(shí)例初始化過程中
初始化的springApplicationRunListener監(jiān)聽器并運(yùn)行。
●第二步∶根據(jù)SpringApplicationRunListeners以及參數(shù)來準(zhǔn)備環(huán)境
this.prepareEnvironment(listeners,applicationArguments)方法主要用于對(duì)項(xiàng)目運(yùn)行環(huán)境進(jìn)行預(yù)設(shè)置,同
時(shí)通過this.configureIgnoreBeanInfo(environment)方法排除一些不需要的運(yùn)行環(huán)境
● 第三步∶創(chuàng)建Spring容器
根據(jù)vebApplicationType進(jìn)行判斷,確定容器類型,如果該類型為SERVLET類型,會(huì)通過反射裝載對(duì)應(yīng)的字
節(jié)碼,也就是AnnotationConfigservletwebserverApplicationContext,接著使用之前初始化設(shè)置的context(應(yīng)
用上下文環(huán)境)、environment(項(xiàng)目運(yùn)行環(huán)境)、listeners(運(yùn)行監(jiān)聽器)、applicationArguments(項(xiàng)目參
數(shù))和printedBanner(項(xiàng)目圖標(biāo)信息)進(jìn)行應(yīng)用上下文的組裝配置,并刷新配置
●第四步∶Spring容器前置處理
這一步主要是在容器刷新之前的準(zhǔn)備動(dòng)作。設(shè)置容器環(huán)境,包括各種變量等等,其中包含一個(gè)非常關(guān)鍵的操
作∶將啟動(dòng)類注入容器,為后續(xù)開啟自動(dòng)化配置奠定基礎(chǔ)
●第五步∶刷新容器
開啟刷新spring容器,通過refresh方法對(duì)整個(gè)roc容器的初始化(包括bean資源的定位,解析,注冊(cè)等等),
同時(shí)向Jw運(yùn)行時(shí)注冊(cè)一個(gè)關(guān)機(jī)鉤子,在Jv%關(guān)機(jī)時(shí)會(huì)關(guān)閉這個(gè)上下文,除非當(dāng)時(shí)它已經(jīng)關(guān)閉
● 第六步∶Spring容器后置處理
擴(kuò)展接口,設(shè)計(jì)模式中的模板方法,默認(rèn)為空實(shí)現(xiàn)。如果有自定義需求,可以重寫該方法。比如打印一些啟
動(dòng)結(jié)束log,或者一些其它后置處理。
● 第七步∶發(fā)出結(jié)束執(zhí)行的事件
獲取EventPublishingRunListener監(jiān)聽器,并執(zhí)行其started方法,并且將創(chuàng)建的spring容器傳進(jìn)去了,創(chuàng)建一
個(gè)ApplicationstartedEvent事件,并執(zhí)行ConfigurableApplicationContext 的publishEvent方法,也就是說這里
是在Spring容器中發(fā)布事件,并不是在SpringApplication中發(fā)布事件,和前面的starting是不同的,前面的
starting是直接向springApplication中的監(jiān)聽器發(fā)布啟動(dòng)事件。
● 第八步∶執(zhí)行Runners
用于調(diào)用項(xiàng)目中自定義的執(zhí)行器xxRunner類,使得在項(xiàng)目啟動(dòng)完成后立即執(zhí)行一些特定程序。其中,Spring
Boot提供的執(zhí)行器接口有AplicationRunner和CcommandLinerunner兩種,在使用時(shí)只需要自定義一個(gè)執(zhí)行器
類實(shí)現(xiàn)其中一個(gè)接口并重寫對(duì)應(yīng)的run()方法接口,然后spring Boot項(xiàng)目啟動(dòng)后會(huì)立即執(zhí)行這些特定程序
下面,通過一個(gè)Spring Boot執(zhí)行流程圖,讓大家更清晰的知道Spring Boot的整體執(zhí)行流程和主要啟動(dòng)階段∶
