我們都知道
SpringBoot自問世以來,一直有一個響亮的口號"約定優于配置",其實一種按約定編程的軟體設計范式,目的在于減少軟體開發人員在作業中的各種繁瑣的配置,我們都知道傳統的SSM框架的組合,會伴隨著大量的繁瑣的配置;稍有不慎,就可能各種bug,被人發現還以為我們技術很菜,而SpringBoot的出現不僅大大提高的開發人員的效率,還能避免由于"手抖"帶來的配置錯誤,
很多程式員都感慨SpringBoot的到來大大解放了生產力,但是也有聰明的程式猿會多思考一下下,SpringBoot是怎么做到的約定的配置?它配置在了哪里?又是怎么啟動的作用等等一系列的問號在跟女朋友花前月下的時候,依然會時不時冒出來,這嚴重影響了程式猿們的"幸"福生活,為了能廣大"程式猿"同胞過上幸福美滿的生活,今天咱么就來一起跟隨原始碼探究下SpringBoot到底是如何做到"約定優于配置"的,
首先,我們先介紹下我們的演示的專案環境,我們先試用 Spring Initializr來創建一個SpirngBoot 工程,我們使用的版本是SpringBoot 2.4.3.RELEASE

接下來就只在 pom.xmL檔案中添加一個web工程的依賴,是為了觀察后面容器型別的原始碼,
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
這樣我們的環境就準備好了,
我們跟著 SpringBoot的原始碼來探究它的啟動流程,首先,先找到這個應用程式的入口主方法,在上面打一個斷點:

啟動之后,F7進入到 run()方法,我的電腦是點擊F7(Step into)

到這里會執行 new SpringApplication(primarySources)創建spring應用物件,繼續F7往下跟會執行 SpringApplication構造器
//SpringApplication構造器
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
// 此處省略原始碼...
// 資源加載器
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
// 1.可能的web應用程式型別的型別,
this.webApplicationType = WebApplicationType.deduceFromClasspath();
this.bootstrappers = new ArrayList(this.getSpringFactoriesInstances(Bootstrapper.class));
// 2.設定初始化應用context
this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 3.設定初始化監聽
this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
// 4.推演主程式類
this.mainApplicationClass = this.deduceMainApplicationClass();
}
很多不為人知的事情都是發生在這個物件初始化的時候,這里我們都來一一解密
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;
}
}
// 這里是我們測驗web容器
return SERVLET;
}
}
1. 推斷web 應用型別
這段代碼是來推斷我們的應用是哪種web應用程式
public enum WebApplicationType{
NONE,// 不是web應用
SERVLET,// servlet容器
REACTIVE; // 反應型web應用(webflux)
}
當然一開始我們加入了web的依賴,所以我們是 servlet 容器,
2. 初始化應用背景關系
在設定初始化應用context的時候,是先執行了
getSpringFactoriesInstances(ApplicationContextInitializer.class)方法,引數是ApplicationContextInitializer.class位元組碼物件,
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = this.getClassLoader();
// Use names and ensure unique to protect against dupLicates
Set<String> names = new LinkedHashSet(
// 加載ApplicationContextInitializer.class型別的類
// 這里傳入就是引數 ApplicationContextInitializer.class
SpringFactoriesLoader.loadFactoryNames(type, classLoader));
// 實體化加載到的類
List<T> instances = this.createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
// 回傳
return instances;
}
我們先來看看他是如何加載到這些類

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 {
// 從資源路徑下加載
Enumeration urls = classLoader.getResources("META-INF/spring.factories");
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);
}
}
}
這里有兩個加載配置類的地方其實都指向了META-INF/spring.factories,通過斷點我們可以看到應用程式是加載了以下幾個jar下的 spring.factories 檔案,
雙擊Shifi搜索spring.factories可以看到它存在于以下工程中
spring-boot-2.4.3.RELEASE.jar 下的 spring.factories (截圖未完整截取)

spring-boot-autoconfigure-2.4.3.RELEASE.jar 下的 spring.factories

spring-beans-2.4.3.RELEASE.jar 下的 spring.factories

從Map中根據 org.springframework.context.ApplicationContextInitializer 的型別拿到需要的類初始化類,斷點進入 getOrDefault(factoryClassName,Collections.emptyList());方法

之后就是把加載到的需要初始化的類進行實體化添加到一個集合中等待備用

3. 初始化監聽器類
最關鍵的的還是這句
當我們跟進去之后,會發現在初始化監聽類的時候和上面初始化應用背景關系是一樣的代碼,唯一不同的是 getSpringFactoriesInstances(ApplicationListener.class))傳進去的是·ApplicationListener.class 所以這里就不再贅述,
4. 推演主程式類
也就是這個最關鍵的代碼了
this.mainApplicationClass = this.deduceMainApplicationClass();

到這里就完成了SpringBoot啟動程序中初始化SpringApplication 的程序,
這篇文章主要是給大家說了下SpringBoot 啟動程序中初始化SpringApplication的流程,大致可以分為四個步驟∶
- 推演web應用的型別(如果沒有加web依賴型別NONE)
- 初始化 ApplicationContextInitializer
- 初始化 ApplicationListener
- 推演出主程式類
通過這樣四個步驟就完成了第一步 SpringApplication 的初始化程序,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/267291.html
標籤:其他
上一篇:python時間模塊的使用



