前面給大家介紹了SpringBoot啟動的核心流程,本文開始給大家詳細的來介紹SpringBoot啟動中的具體實作的相關細節。
SpringApplication構造器
首先我們來看下在SpringApplication的構造方法中是如何幫我們完成這4個核心操作的。
@SuppressWarnings({ "unchecked", "rawtypes" })
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
// 傳遞的resourceLoader為null
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
// 記錄主方法的配置類名稱
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// 記錄目前項目的類型
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 加載配置在spring.factories檔案中的ApplicationContextInitializer對應的類型并執行個體化
// 并将加載的資料存儲在了 initializers 成員變量中。
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 初始化監聽器 并将加載的監聽器執行個體對象存儲在了listeners成員變量中
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 反推main方法所在的Class對象 并記錄在了mainApplicationClass對象中
this.mainApplicationClass = deduceMainApplicationClass();
}
1.webApplicationType
首先來看下webApplicationType是如何來推導出目前啟動的項目的類型。通過代碼可以看到是通過deduceFromClassPath()方法根據ClassPath來推導出來的。
this.webApplicationType = WebApplicationType.deduceFromClasspath();
跟蹤進去看代碼
static WebApplicationType deduceFromClasspath() {
if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null)
&& !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
return WebApplicationType.REACTIVE;
}
for (String className : SERVLET_INDICATOR_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
}
return WebApplicationType.SERVLET;
}
在看整體的實作邏輯之前,我們先分别看兩個内容,第一就是在上面的代碼中使用到了相關的靜态變量。
這些靜态變量其實就是一些綁定的Java類的全類路徑。第二個就是
ClassUtils.isPresent()
方法,該方法的邏輯也非常簡單,就是通過反射的方式擷取對應的類型的Class對象,如果存在傳回true,否則傳回false
是以到此推導的邏輯就非常清楚了
2.setInitializers
然後我們再來看下如何實作加載初始化器的。
// 加載配置在spring.factories檔案中的ApplicationContextInitializer對應的類型并執行個體化
// 并将加載的資料存儲在了 initializers 成員變量中。
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
首先所有的初始化器都實作了
ApplicationContextInitializer
接口,也就是根據這個類型來加載相關的實作類。
public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {
void initialize(C var1);
}
然後加載的關鍵方法是
getSpringFactoriesInstances()
方法。該方法會加載
spring.factories
檔案中的key為
org.springframework.context.ApplicationContextInitializer
的值。
spring-boot項目下
# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.rsocket.context.RSocketPortInfoApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer
spring-boot-autoconfigure項目下
# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
具體的加載方法為
getSpringFacotiesInstance()
方法,我們進入檢視
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
// 擷取目前上下文類加載器
ClassLoader classLoader = getClassLoader();
// 擷取到的擴充類名存入set集合中防止重複
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
// 建立擴充點執行個體
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
先進入
SpringFactoriesLoader.loadFactoryNames(type, classLoader)
中具體檢視加載檔案的過程.
然後我們來看下
loadSpringFactories
方法
通過Debug的方式檢視會更清楚哦
通過
loadSpringFactories
方法我們看到把
spring.factories
檔案中的所有資訊都加載到了記憶體中了,但是我們現在隻需要加載
ApplicationContextInitializer
類型的資料。這時我們再通過
getOrDefault()
方法來檢視。
進入方法中檢視
然後會根據反射擷取對應的執行個體對象。
好了到這其實我們就清楚了
getSpringFactoriesInstances
方法的作用就是幫我們擷取定義在
META-INF/spring.factories
檔案中的可以為
ApplicationContextInitializer
的值。并通過反射的方式擷取執行個體對象。然後把執行個體的對象資訊存儲在了SpringApplication的
initializers
屬性中。
3.setListeners
清楚了
setInitializers()
方法的作用後,再看
setListeners()
方法就非常簡單了,都是調用了
getSpringFactoriesInstances
方法,隻是傳入的類型不同。也就是要擷取的
META-INF/spring.factories
檔案中定義的不同資訊罷了。
即加載定義在
META-INF/spring.factories
檔案中聲明的所有的監聽器,并将擷取後的監聽器存儲在了
SpringApplication
的
listeners
屬性中。
預設加載的監聽器為:
4.mainApplicationClass
最後我們來看下
duduceMainApplicaitonClass()
方法是如何反推導出main方法所在的Class對象的。通過源碼我們可以看到是通過
StackTrace
來實作的。
StackTrace: 我們在學習函數調用時,都知道每個函數都擁有自己的棧空間。 一個函數被調用時,就建立一個新的棧空間。那麼通過函數的嵌套調用最後就形成了一個函數調用堆棧
StackTrace
其實就是記錄了程式方法執行的鍊路。通過Debug方式可以更直覺的來呈現。
那麼相關的調用鍊路我們都可以擷取到,剩下的就隻需要擷取每鍊路判斷執行的方法名稱是否是
main
就可以了。