Spring Boot啟動流程分析
引言
早在15年的時候就開始用spring boot進(jìn)行開發(fā)了,然而一直就只是用用,并沒有深入去了解spring boot是以什么原理怎樣工作的,說來也慚愧。今天讓我們從spring boot啟動開始,深入了解一下spring boot的工作原理。
為什么用spring boot
在使用一個東西或者一個工具之前,我們總是會問自己,我為什么要用?用他能給我?guī)硎裁春锰帲?/p>
* 最大的好處就是spring boot遵從了java**約定大于配置**不用面對一大堆的配置文件,spring boot是根據(jù)你用的包來決定提供什么配置。
* 服務(wù)器以jar包的形式內(nèi)嵌于項目中,對于微服務(wù)滿天飛的情況,spring boot天生適合微服務(wù)架構(gòu),方便部署。
* 提供devtools從此改代碼就需重啟成為歷史。
有優(yōu)點就一定有缺點,缺點來源于優(yōu)點優(yōu)點來源于缺點(感覺在說哲學(xué)問題了哈哈哈)
* 正因為配置對開發(fā)者不透明,不看源碼會不清楚spring boot如何進(jìn)行諸如JDBC加載、事務(wù)管理等,出現(xiàn)錯誤也很難調(diào)錯。
* 自動配置之后要自定義配置需編碼javaConfig,需要了解這些配置類api。
* 版本迭代太快,新版本對老版本改動太多導(dǎo)致不兼容,比如1.3.5之前的springBootTest和1.4.0之后的springBootTest。
只有合適的架構(gòu)才是最好的架構(gòu)如果能接受spring boot這些缺點,spring boot確實是一個可以提高開發(fā)效率的不錯的選擇。
啟動流程
扯了這么多,該上正題了,讓我們來看看spring boot是怎樣啟動和啟動做了哪些事情。
以下代碼是spring boot項目標(biāo)準(zhǔn)的啟動方式,使用注解@SpringBootApplication并且在main方法中調(diào)用SpringApplication的run方法,就可以完成。我們就從這個run方法開始看看spring boot的啟動過程。
@SpringBootApplication
public class Application {
public static void main(String[] args){
SpringApplication.run(Application.class,args);
}
}
我們進(jìn)入run方法,可以看到最終是調(diào)用了 new SpringApplication(sources).run(args);new SpringApplication(sources).run(args); 這個方法,可以看到,springBoot的啟動可以分為兩個部分,第一部分:SpringApplication的實例化;第二部分:調(diào)用該實例運行run方法。我們先來看看這個SpringApplication的實例化過程。
private void initialize(Object[] sources) {
if (sources != null && sources.length > 0) {
this.sources.addAll(Arrays.asList(sources));
}
//判定是否為webEnvironment
this.webEnvironment = deduceWebEnvironment();
//實例化并加載所有可以加載的ApplicationContextInitializer
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
//實例化并加載所有可以加載的ApplicationListener
setListeners((Collection)
getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
關(guān)鍵點在兩個set方法上**
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class))** 和 **setListeners((Collection)
getSpringFactoriesInstances(ApplicationListener.class))** 這兩個方法一毛一樣,挑實例化ApplicationContextInitializer講一講。
private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type,
Class<?>[] parameterTypes, Object... args) {
//拿到類加載器
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// Use names and ensure unique to protect against duplicates
//使用loadFactoryNames方法載入所有的ApplicationContextInitializer的類全限定名
Set<String> names = new LinkedHashSet<String>(
SpringFactoriesLoader.loadFactoryNames(type, classLoader));
//使用反射將所有的ApplicationContextInitializer實例化
List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
classLoader, args, names);
//排序
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
自動配置的關(guān)鍵就是這個 getSpringFactoriesInstances方法,確切的說是這個方法里的loadFactoryNames方法,浪我們看看這個loadFactoryNames方法干了啥,咋就能實現(xiàn)自動配置。
public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
try {
Enumeration<URL> urls = classLoader != null?classLoader.getResources("META-INF/spring.factories"):ClassLoader.getSystemResources("META-INF/spring.factories");
ArrayList result = new ArrayList();
while(urls.hasMoreElements()) {
URL url = (URL)urls.nextElement();
Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
String factoryClassNames = properties.getProperty(factoryClassName);
result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
}
return result;
} catch (IOException var8) {
throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() + "] factories from location [" + "META-INF/spring.factories" + "]", var8);
}
}
可以看到這個方法就做了一件事,就是從META-INF/spring.factories這個路徑取出所有”url”來,我們可以去到這個路徑下看看到底是些啥?
# Initializers org.springframework.context.ApplicationContextInitializer=\ org.springframework.boot.autoconfigure.logging.AutoConfigurationReportLoggingInitializer # Application Listeners org.springframework.context.ApplicationListener=\ org.springframework.boot.autoconfigure.BackgroundPreinitializer
這下大家都應(yīng)該明白了,spring是通過將所有你加載的jar包中找到它需要的ApplicationContextInitializer來進(jìn)行動態(tài)的配置的,只要你有用到特定的maven包,初始化的時候會找這個包下的META-INF/spring.factories的需要的類比如ApplicationContextInitializer進(jìn)行實例化bean,你就可以用了,不需要任何配置。
說到這已經(jīng)將所有SpringApplication實例化說完了,只是在加載完ApplicationContextInitializer和ApplicationListener這之后還有一步,就是找到啟動類所在的位置并且設(shè)入屬性mainApplicationClass中。
接下來讓我們回到new SpringApplication(sources).run(args)方法來看看run方法是怎么run的。
public ConfigurableApplicationContext run(String... args) {
//開啟啟動計時器,項目啟動完會打印執(zhí)行時間出來
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
FailureAnalyzers analyzers = null;
configureHeadlessProperty();
//獲取SpringApplicationRunListener并啟動監(jiān)聽器
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
//環(huán)境變量的加載
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
//啟動后console的打印出來的一堆配置信息
Banner printedBanner = printBanner(environment);
//終極大boss->ApplicationContext實例化
context = createApplicationContext();
analyzers = new FailureAnalyzers(context);
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
listeners.finished(context, null);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
return context;
}
catch (Throwable ex) {
handleRunFailure(context, listeners, analyzers, ex);
throw new IllegalStateException(ex);
}
}
從這個方法里面做的最關(guān)鍵的三件事情就是:
獲取監(jiān)聽器并啟動
加載環(huán)境變量,該環(huán)境變量包括system environment、classpath environment和用戶自己加的application.properties
創(chuàng)建ApplicationContext
private void prepareContext(ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
context.setEnvironment(environment);
postProcessApplicationContext(context);
applyInitializers(context);
listeners.contextPrepared(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// Add boot specific singleton beans
context.getBeanFactory().registerSingleton("springApplicationArguments",
applicationArguments);
if (printedBanner != null) {
context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
}
// Load the sources
Set<Object> sources = getSources();
Assert.notEmpty(sources, "Sources must not be empty");
load(context, sources.toArray(new Object[sources.size()]));
listeners.contextLoaded(context);
}
前兩點沒什么好說的,重點說說第三個,創(chuàng)建ApplicationContext。創(chuàng)建applicationContext又分為幾部:實例化applicationContext、prepareContext、refreshContext。實例化applicationContext會根據(jù)在之前我們說的webEnvironment這個屬性判斷是使用webContext類AnnotationConfigEmbeddedWebApplicationContext還是普通context類AnnotationConfigApplicationContext(在這里我們使用的是webContext為例)然后通過反射進(jìn)行實例化。applicationContext實例化完了會進(jìn)入prepareContext流程,這個prepareContext方法會加載之前準(zhǔn)備好的environment進(jìn)入context中,然后如果有beanNameGenerator和resourceLoader那么提前創(chuàng)建bean加載進(jìn)applicationContext,但是一般這兩個都是空的,所以直接進(jìn)入applyInitializers方法,將之前實例化的所有initializers進(jìn)行初始化,所有的bean就是在這里進(jìn)行bean的掃描和加載的因這次講的是啟動過程,所以不再細(xì)講。最后把創(chuàng)建好的applicationContext設(shè)置進(jìn)入listener,prepareContext過程就結(jié)束了。最后是refreshContext,這個就和spring的bean加載過程一致了,bean的注入、beanFactory、postProcessBeanFactory等等,詳情可以去看看spring bean的生命周期。
總結(jié)
spring boot 初始化內(nèi)容還是很多的,但是總結(jié)起來就四點:
* 創(chuàng)建SpringApplication實例,判定環(huán)境,是web環(huán)境還是普通環(huán)境。加載所有需要用到的Initializers和Listeners,這里使用約定大于配置的理念揭開了自動配置的面紗。
* 加載環(huán)境變量,環(huán)境變量包括system environment、classpath environment、application environment(也就是我們自定義的application.properties配置文件)
* 創(chuàng)建SpringApplicationRunListeners
* 創(chuàng)建ApplicationContext,設(shè)置裝配context,在這里將所有的bean進(jìn)行掃描最后在refreshContext的時候進(jìn)行加載、注入。最終將裝配好的context作為屬性設(shè)置進(jìn)SpringApplicationRunListeners,這就完成了一個spring boot項目的啟動。
以上所述是小編給大家介紹的Spring Boot啟動流程,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復(fù)大家的。在此也非常感謝大家對腳本之家網(wǎng)站的支持!
相關(guān)文章
Java如何使用Jetty實現(xiàn)嵌入式的Servlet容器
這篇文章主要介紹了Java使用Jetty實現(xiàn)嵌入式的Servlet容器,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,下面我們來一起了解一下吧2019-06-06
SpringBoot項目運行一段時間后自動關(guān)閉的坑及解決
這篇文章主要介紹了SpringBoot項目運行一段時間后自動關(guān)閉的坑及解決方案,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-09-09
mybatis注解動態(tài)sql注入map和list方式(防sql注入攻擊)
這篇文章主要介紹了mybatis注解動態(tài)sql注入map和list方式(防sql注入攻擊),具有很好的參考價值,希望對大家有所幫助。2021-11-11
Springboot如何使用OSHI獲取和操作系統(tǒng)和硬件信息
這篇文章主要介紹了Springboot如何使用OSHI獲取和操作系統(tǒng)和硬件信息問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-10-10
nacos在liunx系統(tǒng)中啟動成功瀏覽器卻訪問不了的解決方法
在linux下搭建nacos,現(xiàn)在想要啟動,訪問nacos頁面,訪問不了,所以本文小編將給大家介紹nacos在liunx系統(tǒng)中啟動成功,瀏覽器卻訪問不了?全面的解決辦法,需要的朋友可以參考下2023-09-09
Java 實戰(zhàn)項目之疫情防控管理系統(tǒng)詳解
讀萬卷書不如行萬里路,只學(xué)書上的理論是遠(yuǎn)遠(yuǎn)不夠的,只有在實戰(zhàn)中才能獲得能力的提升,本篇文章手把手帶你用Java實現(xiàn)一個疫情防控管理系統(tǒng),大家可以在過程中查缺補(bǔ)漏,提升水平2021-11-11
SpringBoot項目Jar包如何瘦身部署的實現(xiàn)
這篇文章主要介紹了SpringBoot項目Jar包如何瘦身部署的實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09

