Springboot啟動(dòng)原理詳細(xì)講解
主啟動(dòng)類方法:
@SpringBootApplication
public class MyJavaTestApplication {
public static void main(String[] args) {
SpringApplication.run(MyJavaTestApplication.class, args);
}
}點(diǎn)擊進(jìn)入方法
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}
先看看new SpringApplication(primarySources)里做了什么?
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
//判斷當(dāng)前web容器的類型,一般返回SERVLET,標(biāo)識(shí)是web類型
this.webApplicationType = WebApplicationType.deduceFromClasspath();
//獲取META-INF/spring.factories文件中以//org.springframework.boot.Bootstrapper和
//org.springframework.boot.BootstrapRegistryInitializer為key的class
//創(chuàng)建對象,然后裝入對象屬性中;
this.bootstrapRegistryInitializers = getBootstrapRegistryInitializersFromSpringFactories();
setInitializers((Collection)
//獲取META-INF/spring.factories文件中以
//org.springframework.context.ApplicationContextInitializer的key的對
//存入到initializers集合屬性中;
getSpringFactoriesInstances(ApplicationContextInitializer.class));
//獲取META-INF/spring.factories文件中以
//org.springframework.context.ApplicationListener的key的對
//存入到listeners集合屬性中;
setListeners((Collection)
getSpringFactoriesInstances(ApplicationListener.class));
//找到main方法的啟動(dòng)主類class對象賦值到對象屬性中。
this.mainApplicationClass = deduceMainApplicationClass();
}
spring.factories讀取了對外擴(kuò)展的ApplicationContextInitializer ,ApplicationListener 對外擴(kuò)展, 對類解耦(比如全局配置文件、熱部署插件)
所以我們可以利用這一特性,在容器加載的各個(gè)階段進(jìn)行擴(kuò)展。
上面讀取到的對象getSpringFactoriesInstances(ApplicationContextInitializer.class))
存入到initializers集合中的對象

getSpringFactoriesInstances(ApplicationListener.class));存入到listeners集合屬性中的對象

再看一下最重要的run方法:
public ConfigurableApplicationContext run(String... args) {
// 用來記錄當(dāng)前springboot啟動(dòng)耗時(shí)
StopWatch stopWatch = new StopWatch();
// 就是記錄了啟動(dòng)開始時(shí)間
stopWatch.start();
//創(chuàng)建DefaultBootstrapContext bootstrapContext = new
//DefaultBootstrapContext();對象,然后循環(huán)執(zhí)行上面文件中賦值的bootstrapRegistryInitializers集合中對象的方法,入?yún)⒕褪莃ootstrapContext對象,利用此處我們可以擴(kuò)展改變bootstrapContext對象中的一項(xiàng)屬性值等,在這里還不知道此bootstrapContext對象的用處。
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
// 它是任何spring上下文的接口, 所以可以接收任何ApplicationContext實(shí)現(xiàn)
ConfigurableApplicationContext context = null;
// 開啟了Headless模式,暫時(shí)不知道此模式的作用
configureHeadlessProperty();
//去spring.factroies中讀取了 org.springframework.boot.SpringApplicationRunListener為key的對象,默認(rèn)是EventPublishingRunListener對象,然后封裝進(jìn)SpringApplicationRunListeners 對象中,此對象還是比較有用的,用來發(fā)布springboot啟動(dòng)進(jìn)行中的各個(gè)狀態(tài)的事件,上面方法中讀取到的監(jiān)聽器就可以監(jiān)聽到這些事件,所以可以運(yùn)用這些特性進(jìn)行自己的擴(kuò)展。
SpringApplicationRunListeners listeners = getRunListeners(args);
// 發(fā)布1.ApplicationStartingEvent事件,在運(yùn)行開始時(shí)發(fā)送 ,發(fā)送springboot啟動(dòng)開始事件;
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
//根據(jù)啟動(dòng)項(xiàng)目命令所帶的參數(shù)創(chuàng)建applicationArguments 對象
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//初始化環(huán)境變量:讀取系統(tǒng)環(huán)境變量、發(fā)送了一個(gè)ApplicationEnvironmentPreparedEvent事件,利用相關(guān)監(jiān)聽器來解析項(xiàng)目中配置文件中的配置,(EnvironmentPostProcessorApplicationListener
監(jiān)聽器解析的配置文件)
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
// 忽略beaninfo的bean
configureIgnoreBeanInfo(environment);
// 打印Banner 橫幅
Banner printedBanner = printBanner(environment);
//根據(jù)webApplicationType 容器類型,創(chuàng)建對應(yīng)的spring上下文,一般是AnnotationConfigServletWebServerApplicationContext;
context = createApplicationContext();
//給spring上下文賦值DefaultApplicationStartup對象
context.setApplicationStartup(this.applicationStartup);
//預(yù)初始化上下文,這里做了給上下文添加environment對象,上面獲取到的initializers集合中的ApplicationContextInitializer對象執(zhí)行其入?yún)樯舷挛膇nitialize方法,對上下文做編輯,所以此處我們可以做擴(kuò)展;發(fā)送ApplicationContextInitializedEvent容器初始化事件;發(fā)送BootstrapContextClosedEvent事件;最重要的一個(gè)方法就是把啟動(dòng)配置類注冊成了beanDefinition;發(fā)送ApplicationPreparedEvent事件,并把listeners集合屬性中的事件添加到上下文中;
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
//刷新容器:和spring中的refresh()方法的作用是一樣的,主要的作用就是讀取所有的bean轉(zhuǎn)成beanDefinition然后再創(chuàng)建bean對象;不過這個(gè)容器重寫了其中的onRefresh()方法,在此方法中,創(chuàng)建了springboot內(nèi)置的tomcat對象并進(jìn)行啟動(dòng);接下來特別說明一下這個(gè)內(nèi)置tomcat
refreshContext(context);
afterRefresh(context, applicationArguments);
stopWatch.stop();
//打印啟動(dòng)時(shí)間;
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
//發(fā)布容器啟動(dòng)事件ApplicationStartedEvent;發(fā)布AvailabilityChangeEvent容器可用實(shí)踐;
listeners.started(context);
//執(zhí)行容器中ApplicationRunner、ApplicationRunner類型對象的run方法
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, listeners);
throw new IllegalStateException(ex);
}
try {
//發(fā)布ApplicationReadyEvent容器已經(jīng)正常事件;
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, null);
throw new IllegalStateException(ex);
}
return context;
}prepareEnvironment方法
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// 根據(jù)webApplicationType 創(chuàng)建Environment 創(chuàng)建就會(huì)讀?。?java環(huán)境變量和系統(tǒng)環(huán)境變量
ConfigurableEnvironment environment = getOrCreateEnvironment();
// 將命令行參數(shù)讀取環(huán)境變量中
configureEnvironment(environment, applicationArguments.getSourceArgs());
// 將@PropertieSource的配置信息 放在第一位, 因?yàn)樽x取配置文件@PropertieSource優(yōu)先級(jí)是最低的
ConfigurationPropertySources.attach(environment);
// 發(fā)布了ApplicationEnvironmentPreparedEvent 的監(jiān)聽器 讀取了全局配置文件
listeners.environmentPrepared(environment);
// 將所有spring.main 開頭的配置信息綁定SpringApplication
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
deduceEnvironmentClass());
}
//更新PropertySources
ConfigurationPropertySources.attach(environment);
return environment;
}lprepareContext
l預(yù)初始化上下文
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
context.setEnvironment(environment);
postProcessApplicationContext(context);
// 拿到之前讀取到所有ApplicationContextInitializer的組件, 循環(huán)調(diào)用initialize方法
applyInitializers(context);
// 發(fā)布了ApplicationContextInitializedEvent
listeners.contextPrepared(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// 獲取當(dāng)前spring上下文beanFactory (負(fù)責(zé)創(chuàng)建bean)
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
// 在Spring下 如果出現(xiàn)2個(gè)重名的bean, 則后讀取到的會(huì)覆蓋前面
// 在SpringBoot 在這里設(shè)置了不允許覆蓋, 當(dāng)出現(xiàn)2個(gè)重名的bean 會(huì)拋出異常
if (beanFactory instanceof DefaultListableBeanFactory) {
((DefaultListableBeanFactory) beanFactory)
.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
// 設(shè)置當(dāng)前spring容器是不是要將所有的bean設(shè)置為懶加載
if (this.lazyInitialization) {
context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
}
// Load the sources
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
// 讀取主啟動(dòng)類,將它注冊為BD、就像我們以前register(啟動(dòng)類);一個(gè)意思 (因?yàn)楹罄m(xù)要根據(jù)配置類解析配置的所有bean)
load(context, sources.toArray(new Object[0]));
//4.讀取完配置類后發(fā)送ApplicationPreparedEvent。
listeners.contextLoaded(context);
}
1.初始化SpringApplication 從spring.factories 讀取 listener ApplicationContextInitializer 。
2.運(yùn)行run方法
3.讀取 環(huán)境變量 配置信息…
4.創(chuàng)建springApplication上下文:ServletWebServerApplicationContext
5.預(yù)初始化上下文 : 讀取啟動(dòng)類
6.調(diào)用refresh 加載ioc容器
7.加載所有的自動(dòng)配置類
8.創(chuàng)建servlet容器
9.ps.在這個(gè)過程中springboot會(huì)調(diào)用很多監(jiān)聽器對外進(jìn)行擴(kuò)展
看一下內(nèi)置tomcat如何啟動(dòng)的:
Springboot的spring容器ServletWebServerApplicationContext對象重新了refresh()方法中的onRefresh()放用來啟動(dòng)tomcat
@Override
protected void onRefresh() {
super.onRefresh();
try {
//創(chuàng)建tomcat
createWebServer();
}
catch (Throwable ex) {
throw new ApplicationContextException("Unable to start web server", ex);
}
}createWebServer創(chuàng)建tomcat的方法(也可以是Jetty,根據(jù)配置)
private void createWebServer() {
WebServer webServer = this.webServer;
ServletContext servletContext = getServletContext();
//如果servletContext 為null說明是內(nèi)置tomcat,不為null,則使用的是外置tomcat,這個(gè)servletContext 是tomcat創(chuàng)建傳入的;
if (webServer == null && servletContext == null) {
StartupStep createWebServer = this.getApplicationStartup().start("spring.boot.webserver.create");
//獲取servlet容器創(chuàng)建工廠,tomcat的是TomcatServletWebServerFactory
ServletWebServerFactory factory = getWebServerFactory();
createWebServer.tag("factory", factory.getClass().toString());
//創(chuàng)建內(nèi)置tomcat對象,并啟動(dòng);getSelfInitializer()這個(gè)方法也是很重要的,返回的是ServletWebServerApplicationContext#selfInitialize方法的引用函數(shù),其作用是tomcat啟動(dòng)時(shí)會(huì)回調(diào)此方法,并傳入servletContext對象,進(jìn)行DispacherServlet添加到servletContext中,把當(dāng)前spring容器和filter過濾器也添加到servletContext中
this.webServer = factory.getWebServer(getSelfInitializer());
createWebServer.end();
getBeanFactory().registerSingleton("webServerGracefulShutdown",
new WebServerGracefulShutdownLifecycle(this.webServer));
getBeanFactory().registerSingleton("webServerStartStop",
new WebServerStartStopLifecycle(this, this.webServer));
}
else if (servletContext != null) {
try {
getSelfInitializer().onStartup(servletContext);
}
catch (ServletException ex) {
throw new ApplicationContextException("Cannot initialize servlet context", ex);
}
}
initPropertySources();
}selfInitializ方法
private void selfInitialize(ServletContext servletContext) throws ServletException {
//把spring容器和servletContext進(jìn)行相互引用
prepareWebApplicationContext(servletContext);
registerApplicationScope(servletContext);
WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
//servletContext中添加Dispacherservlet和多個(gè)fileter
for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
beans.onStartup(servletContext);
}
}到此這篇關(guān)于Springboot啟動(dòng)原理詳細(xì)講解的文章就介紹到這了,更多相關(guān)Springboot啟動(dòng)原理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Springboot詳解底層啟動(dòng)過程
- SpringBoot超詳細(xì)分析啟動(dòng)流程
- 詳解SpringBoot啟動(dòng)代碼和自動(dòng)裝配源碼分析
- SpringBoot圖文并茂帶你掌握devtools熱啟動(dòng)
- SpringBoot中jar啟動(dòng)下如何讀取文件路徑
- springboot項(xiàng)目啟動(dòng)后執(zhí)行方法的三種方式
- Springboot項(xiàng)目啟動(dòng)到一半卡住了,不報(bào)錯(cuò)問題及解決
- Springboot啟動(dòng)后立即某個(gè)執(zhí)行方法的四種方式
- 快速掌握SpringBoot應(yīng)用的啟動(dòng)入口
相關(guān)文章
Java 實(shí)戰(zhàn)范例之員工管理系統(tǒng)的實(shí)現(xiàn)
讀萬卷書不如行萬里路,只學(xué)書上的理論是遠(yuǎn)遠(yuǎn)不夠的,只有在實(shí)戰(zhàn)中才能獲得能力的提升,本篇文章手把手帶你用java+vue+Springboot+ssm+mysql+maven+redis實(shí)現(xiàn)一個(gè)前后端分離的員工管理系統(tǒng),大家可以在過程中查缺補(bǔ)漏,提升水平2021-11-11
Java中java.lang.ClassCastException異常原因以及解決方法詳解
這篇文章主要給大家介紹了關(guān)于Java中java.lang.ClassCastException異常原因以及解決方法的相關(guān)資料,ClassCastException從字面上看是類型轉(zhuǎn)換錯(cuò)誤,通常是進(jìn)行強(qiáng)制類型轉(zhuǎn)換時(shí)候出的錯(cuò)誤,需要的朋友可以參考下2024-02-02
基于斷點(diǎn)續(xù)傳下載原理的實(shí)現(xiàn)
下面小編就為大家?guī)硪黄跀帱c(diǎn)續(xù)傳下載原理的實(shí)現(xiàn)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-09-09
阿里Druid數(shù)據(jù)連接池引發(fā)的線上異常解決
這篇文章主要為大家介紹了一次關(guān)于阿里Druid數(shù)據(jù)連接池引發(fā)的線上異常問題的解決方案,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步2022-03-03
java實(shí)現(xiàn)簡單的驗(yàn)證碼功能
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)簡單的驗(yàn)證碼功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-04-04
MAC算法之消息摘要算法HmacMD5的實(shí)現(xiàn)
這篇文章主要介紹了MAC算法之消息摘要算法HmacMD5的實(shí)現(xiàn)的相關(guān)資料,這里提供實(shí)例,幫助大家學(xué)習(xí)理解這部分知識(shí),需要的朋友可以參考下2017-08-08
Java實(shí)現(xiàn)基于JDBC操作mysql數(shù)據(jù)庫的方法
這篇文章主要介紹了Java實(shí)現(xiàn)基于JDBC操作mysql數(shù)據(jù)庫的方法,結(jié)合實(shí)例形式分析了java使用JDBC實(shí)現(xiàn)針對mysql數(shù)據(jù)庫的連接、查詢、輸出等相關(guān)操作技巧,需要的朋友可以參考下2017-12-12

