來源:北大青鳥總部 2023年01月12日 11:05
我們都知道SpringBoot自問世以來,一直有一個響亮的口號"約定優(yōu)于配置",其實一種按約定編程的軟件設(shè)計范式,目的在于減少軟件開發(fā)人員在工作中的各種繁瑣的配置,我們都知道傳統(tǒng)的SSM框架的組合,會伴隨著大量的繁瑣的配置;稍有不慎,就可能各種bug,被人發(fā)現(xiàn)還以為我們技術(shù)很菜。而SpringBoot的出現(xiàn)不僅大大提高的開發(fā)人員的效率,還能避免由于"手抖"帶來的配置錯誤。
很多程序員都感慨SpringBoot的到來大大解放了生產(chǎn)力,但是也有聰明的程序猿會多思考一下下,SpringBoot是怎么做到的約定的配置?它配置在了哪里?又是怎么啟動的作用等等一系列的問號在跟女朋友花前月下的時候,依然會是不是冒出來。這嚴(yán)重影響了程序猿們的"幸"福生活,為了能廣大"程序猿"同胞過上幸福美滿的生活,今天咱么就來一起跟隨源碼探究下SpringBoot到底是如何做到"約定優(yōu)于配置"的。
首先,我們先介紹下我們的演示的項目環(huán)境,我們先試用Spring Initializr來創(chuàng)建一個SpirngBoot工程。我們使用的版本是SpringBoot 2.1.5.RELEASE。
接下來就只在pom.xml文件中添加一個web工程的依賴,是為了觀察后面容器類型的源碼。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
這樣我們的環(huán)境就準(zhǔn)備好了。
我們跟著SpringBoot的源碼來探究它的啟動流程,首先,先找到這個應(yīng)用程序的入口主方法,在上面打一個斷點
啟動之后,F(xiàn)5進(jìn)入到run()方法
public static ConfigurableApplicationContext run(Class<?>[] primarySources,String[] args) {
return new SpringApplication(primarySources).run(args);
}
到這里會執(zhí)行new SpringApplication(primarySources)創(chuàng)建spring應(yīng)用對象,繼續(xù)F5往下跟會執(zhí)行SpringApplication構(gòu)造器
// SpringApplication構(gòu)造器
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應(yīng)用程序類型的類型。
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 2. 設(shè)置初始化應(yīng)用context
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
// 3.設(shè)置初始化監(jiān)聽
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 4. 推演主程序類
this.mainApplicationClass = deduceMainApplicationClass();
}
很多不為人知的事情都是發(fā)生在這個對象初始化的時候,這里我們都來一一解密
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;
}
}
// 這里是我們測試web容器
return WebApplicationType.SERVLET;
}
這段代碼是來推斷我們的應(yīng)用是哪種web應(yīng)用程序
public enum WebApplicationType {
/**
* The application should not run as a web application and should not start an
* embedded web server.
*/
NONE, // 不是web應(yīng)用
/**
* The application should run as a servlet-based web application and should start an
* embedded servlet web server.
*/
SERVLET, // servlet容器
/**
* The application should run as a reactive web application and should start an
* embedded reactive web server.
*/
REACTIVE; // 反應(yīng)型web應(yīng)用(webflux)
當(dāng)然一開始我們加入了web的依賴,所以我們是servlet容器,
在設(shè)置初始化應(yīng)用context的時候 ,是先執(zhí)行了`getSpringFactoriesInstances(ApplicationContextInitializer.class)方法,參數(shù)是ApplicationContextInitializer.class字節(jié)碼對象
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = getClassLoader();
// Use names and ensure unique to protect against duplicates
Set<String> names = new LinkedHashSet<>(
// 加載ApplicationContextInitializer.class類型的類
// 這里傳入就是參數(shù) ApplicationContextInitializer.clas
SpringFactoriesLoader.loadFactoryNames(type, classLoader));
// 實例化加載到的類
List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
// 返回
return instances;
}
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
我們先來看看他是如何加載到這些類
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
// 先從緩存中拿
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
// 去資源路徑下加載
public static final String ACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION); result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryClassName = ((String) entry.getKey()).trim();
for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryClassName, factoryName.trim());
}
}
}
cache.put(classLoader, result);
// 返回所有的加載的類
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
這里有兩個加載配置類的地方其實都指向了META-INF/spring.factories,通過斷點我們可以看到應(yīng)用程序是加載了以下幾個jar下的spring.factores文件。
雙擊Shift搜索spring.factories可以看到它存在于以下工程中
spring-boot-2.1.5.RELEASE.jar下的spring.factores(截圖未完整截?。?/p>
spring-boot-autoconfigure-2.1.5.RELEASE.jar下的spring.factores
spring-beans-5.1.7.RELEASE.jar下的spring.factores
從Map中根據(jù)org.springframework.context.ApplicationContextInitializer的類型拿到需要的類初始化類,斷點進(jìn)入getOrDefault(factoryClassName, Collections.emptyList());方法
之后就是把加載到的需要初始化的類進(jìn)行實例化添加到一個集合中等待備用
public void setInitializers(
Collection<? extends ApplicationContextInitializer<?>> initializers) {
this.initializers = new ArrayList<>();
this.initializers.addAll(initializers);
}
最關(guān)鍵的的還是這句
當(dāng)我們跟進(jìn)去之后,會發(fā)現(xiàn)在初始化監(jiān)聽類的時候和上面初始化應(yīng)用上下文是一樣的代碼。唯一不同的是getSpringFactoriesInstances(ApplicationListener.class))傳進(jìn)去的是·ApplicationListener.class所以這里就不再贅述。
也就是這個最關(guān)鍵的代碼了
this.mainApplicationClass = deduceMainApplicationClass();
到這里就完成了SpringBoot啟動過程中初始化SpringApplication的過程。
這篇文章主要是給大家說了下SpringBoot啟動過程中初始化SpringApplication的流程,大致可以分為四個步驟:
推演web應(yīng)用的類型(如果沒有加web依賴類型NONE)
初始化ApplicationContextInitializer
初始化ApplicationListener
推演出主程序類
通過這樣四個步驟就完成了第一步SpringApplication的初始化過程。