什麼樣的經歷,才能領悟成為架構師? >>>
什麼樣的經歷,才能領悟成為架構師? >>>
本文主要分析 SpringBoot 的啟動過程。
SpringBoot的版本為:2.1.0 release,最新版本。
一.時序圖
還是老套路,先把分析過程的時序圖擺出來:時序圖-SpringBoot2.10啟動分析
二.原始碼分析
首先從我們的一個SpringBoot Demo開始,這裡使用 SPRING INITIALIZR 網站生成的starter開始的:
@SpringBootApplication
public class SpringBootDemoApplication {
public static void main(String[] args) {
// 分析的入口,從 run 方法開始
SpringApplication.run(SpringBootDemoApplication.class, args);
}
}
經過SpringApplication多個過載的構造方法,最後到達:
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
// 從 run() 傳入的 resourceLoader 此處為 null
this.resourceLoader = resourceLoader;
// 使用斷言判斷 resourceLoader 不為空
Assert.notNull(primarySources, "PrimarySources must not be null");
// 把 primarySources 陣列轉為List,最後放入 primarySources 的一個LinkedHashSet中
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// 判斷應用的型別:REACTIVE NONE SERVLET
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 實現 SpringBoot 自動裝配的基礎,此處Spring自己實現的SPI(從META-INF/spring.factories載入class)
// 載入並例項化以 ApplicationContextInitializer 為key的類
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
// 載入並例項化以 ApplicationListener 為key的類
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 獲取程式當前執行堆疊,看是執行的是哪個類的 main 方法,儲存到上下文中
this.mainApplicationClass = deduceMainApplicationClass();
}
看一眼,WebApplicationType#deduceFromClasspath ,deduce意為推斷,即根據classpath下的內容推斷出應用的型別。實現是通過ClassUtils#isPresent來嘗試載入代表不同應用型別特徵的Class檔案:
static WebApplicationType deduceFromClasspath() {// 判斷應用的型別
if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null)// 載入到DispatcherHandler
&& !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)// mvc的DispatcherServlet
&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {// jersey的ServletContainer
return WebApplicationType.REACTIVE;
}
for (String className : SERVLET_INDICATOR_CLASSES) {// 遍歷陣列:Servlet和ConfigurableWebApplicationContext
if (!ClassUtils.isPresent(className, null)) {// 沒有載入到Servlet相關的class
return WebApplicationType.NONE;
}
}
return WebApplicationType.SERVLET;
}
SpringApplication#getSpringFactoriesInstances,從類路徑下 META-INF/spring.factories 下載入 SpringFactory 例項,類似的操作在 Dubbo SPI中也有:
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
Class<?>[] parameterTypes, Object... args) {
// 獲取類載入器
ClassLoader classLoader = getClassLoader();
// 此處呼叫了SpringFactoriesLoader的loadFactoryNames()
Set<String> names = new LinkedHashSet<>(
SpringFactoriesLoader.loadFactoryNames(type, classLoader));
// Use names and ensure unique to protect against duplicates
// 例項化獲取到的類
List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);// 排序
return instances;// 返回例項化好的物件
}
SpringFactoriesLoader#loadFactoryNames,載入工廠名字:
public static List<String> www.yongshiyule178.com loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
String factoryClassName =www.mcyllpt.com/ factoryClass.getName(www.leyouzaixian2.com);
return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
繼續捉迷藏,到了 SpringFactoriesLoader#loadSpringFactories:下面的內容就是找到所有classpath下的 spring.factories 檔案,讀取裡面的內容,放到快取中,此處和Dubbo SPI中ExtensionLoader#loadDirectory幾乎是一模一樣,可以參考我寫過的 Dubbo原始碼 裡面的註釋。
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
// 從快取中獲取Map,key為classLoader
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
// 載入資源的urls,被載入的資源為 "META-INF/spring.factories"
//先從Resources中載入,沒有載入到再從SystemResources中載入
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) {// 遍歷載入到的 spring.factories 檔案
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
// 讀取檔案到記憶體為Properties物件
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<www.michenggw.com/?, ?> entry :www.yigouyule2.cn properties.entrySet()) {
// Entry的key作為工程Class的名字
String factoryClassName = ((String) www.gcyl152.com/ entry.getKey()).trim();
for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
// 如果有多個value,都放在Map中,注意此處為 MultiValueMap ,不是普通的Map,其實現內容的value對應一個LinkedList
result.add(factoryClassName, factoryName.trim(www.gcyl159.com));
}
}
}
// 最後把讀取配置的結果都放入快取中,cache物件為一個ConcurrentReferenceHashMap
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
我們也來看一下上面讀取的檔案 spring.factories 的內容,大概長這個樣子:
# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer
# Auto Configuration Import Listeners
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener
......
是時候跳出來了,回到主線,返回例項化物件後,到了 SpringApplication#deduceMainApplicationClass,獲取程式當前執行堆疊,看現在執行的是哪個類的 main 方法,然後儲存到上下文:
private Class<?> deduceMainApplicationClass() {
try {
// 拿到執行時的堆疊資訊
StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
for (StackTraceElement stackTraceElement : stackTrace) {
// 如果發現哪個堆疊元素裡面有運行了main方法,則返回該類
if ("main".equals(stackTraceElement.getMethodName())) {
return Class.forName(stackTraceElement.getClassName());
}
}
}
catch (ClassNotFoundException ex) {
// Swallow and continue
}
return null;
}
至此,SpringApplication的建構函式的分析完成,後面我們繼續分析SpringApplication的run()方法中做了哪些操作。