SpringBoot啟動流程入口參數(shù)創(chuàng)建對象源碼分析
入口
這不最近到金三銀四的季節(jié)了么,有個朋友去參加了一個面試,回來的時候給我說其它還可以,但是問到SpringBoot的啟動原理了,說了解的不深,我仔細(xì)轉(zhuǎn)過頭來也想了一下自己用了這么長時間的SpringBoot,說實話還真沒有仔細(xì)研究過他的啟動原理。我覺得還是有必要去研究一下。
版本: 2.1.8.RELEASE
于是乎,我便打算從他的啟動類上追下源碼,仔細(xì)去尋找一下答案。
啟動代碼:
@SpringBootApplcation
public static void main(String[] args) {
SpringApplication.run(BlogAdminApplication.class, args);
System.out.println("======== admin start success... ==========");
}
這里傳入了兩個參數(shù),BlogAdminApplication當(dāng)前類和args參數(shù)
我們點擊進入run方法查看
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class[]{primarySource}, args);
}
這里是將我們寫的啟動類傳入到了Class[]數(shù)組中,這步就是個單純的參數(shù)轉(zhuǎn)換。
探討primarySource參數(shù)
那么問題是primarySource能接受的類型是啥樣的,是不是什么類都可以接受,帶著這個疑問,我們做一個測試,把這個參數(shù)給換成一個別的類呢,ManagerController類是一個我寫的接口類
@SpringBootApplication
public class BlogProjectApplication {
public static void main(String[] args) {
SpringApplication.run(ManagerController.class, args);
System.out.println("======== admin start success... ==========");
}
}
控制臺打印
org.springframework.context.ApplicationContextException: Unable to start web server; nested exception is org.springframework.context.ApplicationContextException: Unable to start ServletWebServerApplicationContext due to missing ServletWebServerFactory bean.
提示不能啟動服務(wù),提示缺少了ServletWebServerFactory bean 點進這個類看下
@FunctionalInterface
public interface ServletWebServerFactory {
WebServer getWebServer(ServletContextInitializer... initializers);
}
他被FunctionalInterface標(biāo)注了,是一個函數(shù)式接口,只有一個getWebServer方法,用來獲取webServer的 看下他的實現(xiàn)類,

這不就是提示我們?nèi)鄙賳拥姆?wù)容器么,說的直白點,我的理解就是他缺少可以運行的容器,我們知道,沒有使用springboot項目之前,我們的項目都是跑在tomcat容器上的,當(dāng)然也有使用Jetty容器的。再者,我們知道SpringBoot是對tomcat進行了內(nèi)置。而SpringBoot不僅僅是只有內(nèi)置了tomcat,而且還內(nèi)置了好多的東西,比如我們經(jīng)常使用的mq、redis等等一系列的東西,這個我們可以在spring.factories配置文件中看到,這個文件位于如下位置

大概內(nèi)容有下,篇幅有限,就不一一列舉了。
省略。。。 org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\ org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\ org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\ org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\ org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\ 省略。。。
那么回過頭來,我們再看下這個問題,這些個類是如何被加載進來的,我們知道SpingBoot有個注解是開啟自動注解的@EnableAutoConfiguration,他就干這個事情的。他能夠激活SpringBoot內(nèi)建和自定義組件的自動裝配特性。
那么,知道了這些,我們把這個之前修改后的類給改造一下,加上注解@EnableAutoConfiguration,看下執(zhí)行效果。
@RestController
@RequestMapping("project/manager")
@EnableAutoConfiguration
public class ManagerController extends AbstractController {
運行如下

從打印信息就能知道,服務(wù)器有了,只不過下面報錯,提示找不到bean,那這不就簡單了么,他是不是就是沒有掃描到我們的包么,這里就其實可以在配置掃描包的注解繼續(xù)測試,我就懶的不測試了,直接去看@SpringBootApplication注解
@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 {
@SpringBootApplication這個注解其實就是一個組合注解,里面包含了
- 元注解:用來標(biāo)注他是一個注解的,jdk自帶的
- @SpringBootConfiguration:集成自@Configuration,表示是一個配置類
- @EnableAutoConfiguration:激活SpringBoot內(nèi)建和自定義組件的自動裝配特性
- @ComponentScan:掃描注解,添加了排除參數(shù),指定排除了一些類
通過這里的實驗,我們可以得出結(jié)論:
primarySource參數(shù)能接收的類是一個配置類,同時要把符合掃描規(guī)則的類裝配到spring容器中,并且對SpringBoot內(nèi)置的一些類進行自動掃描到,而這里的@SpringBootApplication注解就是把這些特性都整合到了一起,作為了一個引導(dǎo)類而已。那么說白了,primarySource他接受的其實就是一個配置類。
關(guān)于注解詳細(xì)知識的話,這里就聊這么多了,后面再詳細(xì)聊。
args參數(shù)
args是Java命令行參數(shù),我們在DOS中執(zhí)行Java程序的時候使用“java 文件名 args參數(shù)”。args這個數(shù)組可以接收到這些參數(shù)。這個是個基礎(chǔ)常識了。
以下我們將繼續(xù)跟蹤源碼進行分析
我們繼續(xù)追run()方法
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return (new SpringApplication(primarySources)).run(args);
}
這個方法干了兩個事情:
1、new SpringApplication()來創(chuàng)建對象
2、通過創(chuàng)建后的對象,調(diào)用對象里面的run()方法
以下我們將從這兩個地方進行分析,本篇就先研究第一個
創(chuàng)建對象
我們先看下他是怎么創(chuàng)建對象的,創(chuàng)建了哪些對象,
public SpringApplication(Class<?>... primarySources) {
this((ResourceLoader)null, primarySources);
}
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
//資源加載器
this.resourceLoader = resourceLoader;
//斷言
Assert.notNull(primarySources, "PrimarySources must not be null");
//對primarySources進行存儲到LinkedHashSet
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
//1、推斷web應(yīng)用類別
this.webApplicationType = WebApplicationType.deduceFromClasspath();
//2、加載Spring應(yīng)用上下文初始化
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
//3、加載Spring應(yīng)用事件監(jiān)聽器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
//4、推斷應(yīng)用引導(dǎo)類
this.mainApplicationClass = deduceMainApplicationClass();
}
下面研究下主要流程部分
1、推斷web應(yīng)用類別
推斷web應(yīng)用類型屬于SpringBoot應(yīng)用web類型的初始化過程。而該類型也可在SpringApplication構(gòu)造后,run方法執(zhí)行之前,通過setWebApplicationType(WebApplicationType webApplicationType)方法進行調(diào)整。
在推斷Web應(yīng)用類型的過程中,由于當(dāng)前Spring應(yīng)用上下文尚未準(zhǔn)備(可在代碼執(zhí)行順序中看到),所以實現(xiàn)采用的是檢查檢查當(dāng)前ClassLoader下基準(zhǔn)Class的存在性判斷。
上源碼
this.webApplicationType = WebApplicationType.deduceFromClasspath();
private static final String[] SERVLET_INDICATOR_CLASSES = { "javax.servlet.Servlet",
"org.springframework.web.context.ConfigurableWebApplicationContext" };
private static final String WEBMVC_INDICATOR_CLASS = "org.springframework." + "web.servlet.DispatcherServlet";
private static final String WEBFLUX_INDICATOR_CLASS = "org." + "springframework.web.reactive.DispatcherHandler";
private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";
private static final String SERVLET_APPLICATION_CONTEXT_CLASS = "org.springframework.web.context.WebApplicationContext";
private static final String REACTIVE_APPLICATION_CONTEXT_CLASS = "org.springframework.boot.web.reactive.context.ReactiveWebApplicationContext";
static WebApplicationType deduceFromClasspath() {
//當(dāng)DispatcherHandler存在,且DispatcherServlet、ServletContainer兩個不存在時;換言之,SpringBoot僅依賴WebFlux存在時,此時的應(yīng)用類型為REACTIVE
if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
return WebApplicationType.REACTIVE;
}
//當(dāng)Servlet和ConfigurableWebApplicationContext均不存在時,當(dāng)前應(yīng)用為非Web應(yīng)用,即WebApplicationType.NONE,因為這些API均是Spring Web MVC必須的依賴
for (String className : SERVLET_INDICATOR_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
}
//當(dāng)WebFlux和Spring Web MVC同時存在時,Web應(yīng)用類型同樣是Servlet Web,即WebApplicationType.SERVLET
return WebApplicationType.SERVLET;
}
2、加載Spring應(yīng)用上下文初始化
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
此過程包含兩個動作,依次為getSpringFactoriesInstances(ApplicationContextInitializer.class)和setInitializers方法。 先看第一個過程
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
return getSpringFactoriesInstances(type, new Class<?>[] {});
}
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
//獲取類加載器
ClassLoader classLoader = getClassLoader();
// Use names and ensure unique to protect against duplicates
//加載了META-INF/spring.factories資源中配置的ApplicationContextInitializer實現(xiàn)類名單。
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
//初始化
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
此處使用了Spring工廠加載機制方法SpringFactoriesLoader.loadFactoryNames(type, classLoader)。加載了META-INF/spring.factories資源中配置的ApplicationContextInitializer實現(xiàn)類名單。

加載完成后使用createSpringFactoriesInstances方法對其進行初始化。
private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes,
ClassLoader classLoader, Object[] args, Set<String> names) {
List<T> instances = new ArrayList<>(names.size());
for (String name : names) {
try {
//從類加載器中獲取指定類
Class<?> instanceClass = ClassUtils.forName(name, classLoader);
//判斷instanceClass是不是type的子類
Assert.isAssignable(type, instanceClass);
//根據(jù)以上獲取的類名創(chuàng)建類的實例
Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
//排序
T instance = (T) BeanUtils.instantiateClass(constructor, args);
instances.add(instance);
}
catch (Throwable ex) {
throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
}
}
return instances;
}
3、加載Spring應(yīng)用事件監(jiān)聽器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
此過程與2、加載上下文初始化基本類似。 只不過初始化的對象類型變成了ApplicationListener.class,setListeners方法也只是賦值而已
public void setListeners(Collection<? extends ApplicationListener<?>> listeners) {
this.listeners = new ArrayList<>();
this.listeners.addAll(listeners);
}

4、推斷應(yīng)用引導(dǎo)類
this.mainApplicationClass = deduceMainApplicationClass();
private Class<?> deduceMainApplicationClass() {
try {
StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
for (StackTraceElement stackTraceElement : stackTrace) {
if ("main".equals(stackTraceElement.getMethodName())) {
return Class.forName(stackTraceElement.getClassName());
}
}
}
catch (ClassNotFoundException ex) {
// Swallow and continue
}
return null;
}
這里使用到了new RuntimeException().getStackTrace()來獲取堆棧信息,找到調(diào)用執(zhí)行的main方法,從而確定他的類。
這里有個疑問:他不是傳了primarySources數(shù)組,里面包含了類名么,怎么還用堆棧的方式去獲取,此外,這里的堆棧獲取也只能獲取一個調(diào)用的主main方法,他為啥還要傳一個Class數(shù)組呢?
具體咋獲取的,可以追下源碼,一直跟蹤他的父類Throwable,找到如下代碼
/**
* Fills in the execution stack trace. This method records within this
* {@code Throwable} object information about the current state of
* the stack frames for the current thread.
*
* <p>If the stack trace of this {@code Throwable} {@linkplain
* Throwable#Throwable(String, Throwable, boolean, boolean) is not
* writable}, calling this method has no effect.
*
* @return a reference to this {@code Throwable} instance.
* @see java.lang.Throwable#printStackTrace()
*/
public synchronized Throwable fillInStackTrace() {
if (stackTrace != null ||
backtrace != null /* Out of protocol state */ ) {
fillInStackTrace(0);
stackTrace = UNASSIGNED_STACK;
}
return this;
}
private native Throwable fillInStackTrace(int dummy);
這里最后調(diào)用了native本地方法,去爬取線程堆棧信息,為運行時棧做一份快照。

通過這個圖片,可以看到整個方法的調(diào)用鏈,從下往上看哦
本文僅為個人能力范圍內(nèi)理解,旨在分享出來和大家討論技術(shù),共同努力,共同進步!
參考:《SpringBoot編程思想》
以上就是SpringBoot啟動流程入口參數(shù)創(chuàng)建對象源碼分析的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot 啟動參數(shù)創(chuàng)建的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
詳解SpringBoot修改啟動端口server.port的四種方式
這篇文章主要介紹了詳解SpringBoot修改啟動端口server.port的四種方式,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07
Java使用wait() notify()方法操作共享資源詳解
這篇文章主要為大家詳細(xì)介紹了Java使用wait() notify()方法操作共享資源,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-10-10
Struts2中validate數(shù)據(jù)校驗的兩種方法詳解附Struts2常用校驗器
這篇文章主要介紹了Struts2中validate數(shù)據(jù)校驗的兩種方法及Struts2常用校驗器,本文介紹的非常詳細(xì),具有參考借鑒價值,感興趣的朋友一起看看吧2016-09-09
在springboot項目中同時接收文件和多個參數(shù)的方法總結(jié)
在開發(fā)接口中,遇到了需要同時接收文件和多個參數(shù)的情況,可以有多種方式實現(xiàn)文件和參數(shù)的同時接收,文中給大家介紹了兩種實現(xiàn)方法,感興趣的同學(xué)跟著小編一起來看看吧2023-08-08
Java實現(xiàn)的不同圖片居中剪裁生成同一尺寸縮略圖功能示例
這篇文章主要介紹了Java實現(xiàn)的不同圖片居中剪裁生成同一尺寸縮略圖功能,涉及java針對圖片的讀取、屬性修改等相關(guān)操作技巧,需要的朋友可以參考下2017-09-09
解決Maven parent.relativePath帶給我的坑
在Linux環(huán)境下使用Maven進行項目打包時,可能會遇到“當(dāng)前目錄沒有pom文件”的錯誤,需要確認(rèn)在包含pom.xml文件的項目目錄下執(zhí)行Maven命令,另外,如果遇到“parent.relativePath points at wrong local POM”錯誤,可能是父模塊依賴問題2024-09-09

