Spring Boot從main方法到內(nèi)嵌Tomcat的全過(guò)程(自動(dòng)化流程)
Spring Boot的啟動(dòng)過(guò)程是一個(gè)精心設(shè)計(jì)的自動(dòng)化流程,下面我將詳細(xì)闡述從main方法開(kāi)始到內(nèi)嵌Tomcat啟動(dòng)的全過(guò)程。
1. 入口:main方法
一切始于一個(gè)簡(jiǎn)單的main方法:
@SpringBootApplication public class MyApplication { public static void main(String[] args) { SpringApplication.run(MyApplication.class, args); } }
2. SpringApplication初始化
SpringApplication.run()
方法內(nèi)部會(huì)創(chuàng)建一個(gè)SpringApplication實(shí)例:
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) { return new SpringApplication(primarySource).run(args); }
2.1 構(gòu)造階段
在SpringApplication構(gòu)造函數(shù)中完成以下關(guān)鍵操作:
- 推斷應(yīng)用類(lèi)型:判斷是Servlet應(yīng)用(Spring MVC)還是Reactive應(yīng)用(Spring WebFlux)
- 加載ApplicationContextInitializer:通過(guò)META-INF/spring.factories加載
- 加載ApplicationListener:同樣通過(guò)spring.factories機(jī)制加載
- 推斷主配置類(lèi):通過(guò)堆棧分析找到包含main方法的類(lèi)
3. 運(yùn)行階段:run()方法
run()
方法是整個(gè)啟動(dòng)過(guò)程的核心:
public ConfigurableApplicationContext run(String... args) { // 1. 創(chuàng)建并啟動(dòng)計(jì)時(shí)器 StopWatch stopWatch = new StopWatch(); stopWatch.start(); // 2. 初始化應(yīng)用上下文和異常報(bào)告器 ConfigurableApplicationContext context = null; Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>(); configureHeadlessProperty(); // 3. 獲取SpringApplicationRunListeners并啟動(dòng) SpringApplicationRunListeners listeners = getRunListeners(args); listeners.starting(); try { // 4. 準(zhǔn)備環(huán)境 ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); // 5. 打印Banner Banner printedBanner = printBanner(environment); // 6. 創(chuàng)建應(yīng)用上下文 context = createApplicationContext(); // 7. 準(zhǔn)備應(yīng)用上下文 prepareContext(context, environment, listeners, applicationArguments, printedBanner); // 8. 刷新應(yīng)用上下文(關(guān)鍵步驟) refreshContext(context); // 9. 刷新后處理 afterRefresh(context, applicationArguments); // 10. 停止計(jì)時(shí)器并發(fā)布啟動(dòng)完成事件 stopWatch.stop(); if (this.logStartupInfo) { new StartupInfoLogger(this.mainApplicationClass) .logStarted(getApplicationLog(), stopWatch); } listeners.started(context); // 11. 執(zhí)行Runner callRunners(context, applicationArguments); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, listeners); throw new IllegalStateException(ex); } try { listeners.running(context); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, null); throw new IllegalStateException(ex); } return context; }
4. 創(chuàng)建應(yīng)用上下文
createApplicationContext()
方法根據(jù)應(yīng)用類(lèi)型創(chuàng)建不同的應(yīng)用上下文:
- Servlet環(huán)境:創(chuàng)建
AnnotationConfigServletWebServerApplicationContext
- Reactive環(huán)境:創(chuàng)建
AnnotationConfigReactiveWebServerApplicationContext
- 普通環(huán)境:創(chuàng)建
AnnotationConfigApplicationContext
對(duì)于Web應(yīng)用,會(huì)創(chuàng)建AnnotationConfigServletWebServerApplicationContext
,它繼承自ServletWebServerApplicationContext
。
5. 準(zhǔn)備應(yīng)用上下文
prepareContext()
方法完成以下工作:
- 將環(huán)境綁定到上下文
- 后置處理上下文
- 應(yīng)用所有初始化器
- 發(fā)布ContextPrepared事件
- 注冊(cè)主配置類(lèi)bean定義
- 發(fā)布ContextLoaded事件
6. 刷新應(yīng)用上下文
refreshContext()
最終調(diào)用AbstractApplicationContext.refresh()
,這是Spring容器的核心刷新流程:
public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // 1. 準(zhǔn)備刷新 prepareRefresh(); // 2. 獲取新的BeanFactory ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // 3. 準(zhǔn)備BeanFactory prepareBeanFactory(beanFactory); try { // 4. 后置處理BeanFactory postProcessBeanFactory(beanFactory); // 5. 調(diào)用BeanFactoryPostProcessor invokeBeanFactoryPostProcessors(beanFactory); // 6. 注冊(cè)BeanPostProcessor registerBeanPostProcessors(beanFactory); // 7. 初始化MessageSource initMessageSource(); // 8. 初始化事件廣播器 initApplicationEventMulticaster(); // 9. 初始化特殊bean(由子類(lèi)實(shí)現(xiàn)) onRefresh(); // 10. 注冊(cè)監(jiān)聽(tīng)器 registerListeners(); // 11. 初始化所有非懶加載單例 finishBeanFactoryInitialization(beanFactory); // 12. 完成刷新 finishRefresh(); } catch (BeansException ex) { // 處理異常... } } }
7. 內(nèi)嵌Tomcat啟動(dòng)的關(guān)鍵:onRefresh()
對(duì)于Servlet Web應(yīng)用,ServletWebServerApplicationContext
重寫(xiě)了onRefresh()
方法:
protected void onRefresh() { super.onRefresh(); try { createWebServer(); } catch (Throwable ex) { throw new ApplicationContextException("Unable to start web server", ex); } }
createWebServer()
是內(nèi)嵌服務(wù)器啟動(dòng)的關(guān)鍵:
private void createWebServer() { WebServer webServer = this.webServer; ServletContext servletContext = getServletContext(); if (webServer == null && servletContext == null) { // 1. 獲取WebServer工廠(chǎng)(Tomcat, Jetty或Undertow) ServletWebServerFactory factory = getWebServerFactory(); // 2. 創(chuàng)建WebServer this.webServer = factory.getWebServer(getSelfInitializer()); } else if (servletContext != null) { try { getSelfInitializer().onStartup(servletContext); } catch (ServletException ex) { throw new ApplicationContextException("Cannot initialize servlet context", ex); } } initPropertySources(); }
8. Tomcat服務(wù)器創(chuàng)建過(guò)程
以Tomcat為例,TomcatServletWebServerFactory.getWebServer()
方法:
public WebServer getWebServer(ServletContextInitializer... initializers) { // 1. 創(chuàng)建Tomcat實(shí)例 Tomcat tomcat = new Tomcat(); // 2. 配置基礎(chǔ)目錄 File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat"); tomcat.setBaseDir(baseDir.getAbsolutePath()); // 3. 配置連接器 Connector connector = new Connector(this.protocol); connector.setThrowOnFailure(true); tomcat.getService().addConnector(connector); customizeConnector(connector); tomcat.setConnector(connector); // 4. 配置Host tomcat.getHost().setAutoDeploy(false); configureEngine(tomcat.getEngine()); // 5. 準(zhǔn)備上下文 prepareContext(tomcat.getHost(), initializers); // 6. 創(chuàng)建TomcatWebServer并啟動(dòng) return getTomcatWebServer(tomcat); }
9. 啟動(dòng)Tomcat
在TomcatWebServer
構(gòu)造函數(shù)中完成Tomcat的啟動(dòng):
public TomcatWebServer(Tomcat tomcat, boolean autoStart) { this.tomcat = tomcat; this.autoStart = autoStart; initialize(); } private void initialize() throws WebServerException { // 啟動(dòng)Tomcat this.tomcat.start(); // 啟動(dòng)一個(gè)守護(hù)線(xiàn)程來(lái)等待停止命令 startDaemonAwaitThread(); }
10. 自動(dòng)配置的關(guān)鍵
整個(gè)過(guò)程中,自動(dòng)配置是通過(guò)@SpringBootApplication
注解中的@EnableAutoConfiguration
實(shí)現(xiàn)的:
- 在
invokeBeanFactoryPostProcessors()
階段會(huì)處理自動(dòng)配置 AutoConfigurationImportSelector
會(huì)加載META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
文件中的配置類(lèi)- 對(duì)于Tomcat,會(huì)加載
ServletWebServerFactoryAutoConfiguration
- 這個(gè)配置類(lèi)通過(guò)
@Import
引入了EmbeddedTomcat
等配置
總結(jié)流程
- 啟動(dòng)main方法
- 創(chuàng)建SpringApplication實(shí)例
- 運(yùn)行run()方法
- 準(zhǔn)備環(huán)境
- 創(chuàng)建應(yīng)用上下文(AnnotationConfigServletWebServerApplicationContext)
- 準(zhǔn)備上下文(注冊(cè)配置類(lèi)等)
- 刷新上下文(核心)
- 調(diào)用onRefresh()
- 創(chuàng)建內(nèi)嵌Web服務(wù)器(Tomcat)
- 啟動(dòng)Tomcat
- 發(fā)布啟動(dòng)完成事件
- 執(zhí)行Runner
到此這篇關(guān)于Spring Boot從main方法到內(nèi)嵌Tomcat的全過(guò)程(自動(dòng)化流程)的文章就介紹到這了,更多相關(guān)Spring Boot啟動(dòng)過(guò)程內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Springboot使用maven打包指定mainClass問(wèn)題
- springboot中如何通過(guò)main方法調(diào)用service或dao
- 解決@springboottest注解無(wú)法加載src/main/resources目錄下文件
- SpringBoot如何獲取src/main/resource路徑下的文件
- springboot 基于Tomcat容器的自啟動(dòng)流程分析
- SpringBoot中如何啟動(dòng)Tomcat流程
- springboot使用外置tomcat啟動(dòng)方式
- Tomcat啟動(dòng)springboot項(xiàng)目war包報(bào)錯(cuò):?jiǎn)?dòng)子級(jí)時(shí)出錯(cuò)的問(wèn)題
相關(guān)文章
Java服務(wù)假死之生產(chǎn)事故的排查與優(yōu)化問(wèn)題
在服務(wù)器上通過(guò)curl命令調(diào)用一個(gè)Java服務(wù)的查詢(xún)接口,半天沒(méi)有任何響應(yīng),怎么進(jìn)行這一現(xiàn)象排查呢,下面小編給大家記一次生產(chǎn)事故的排查與優(yōu)化——Java服務(wù)假死問(wèn)題,感興趣的朋友一起看看吧2022-07-07如何解決java.net.BindException:地址已在使用問(wèn)題
當(dāng)Zookeeper啟動(dòng)報(bào)錯(cuò)“java.net.BindException:地址已在使用”時(shí),通常是因?yàn)橹付ǖ亩丝谝驯黄渌M(jìn)程占用,解決這個(gè)問(wèn)題需要按照以下步驟操作:首先,使用命令如lsof -i:2181找到占用該端口的進(jìn)程號(hào);其次,使用kill命令終止該進(jìn)程2024-09-09java如何多線(xiàn)程批量更新10萬(wàn)級(jí)的數(shù)據(jù)
在處理大數(shù)據(jù)量的批量更新時(shí),直接使用mybatis的updateBatch可能導(dǎo)致效率低下甚至OOM,通過(guò)每次處理5000條數(shù)據(jù)的方式雖然安全但效率低,更優(yōu)的解決方案是使用多線(xiàn)程處理,將數(shù)據(jù)分批并多線(xiàn)程執(zhí)行,有效提高了處理速度并保證了系統(tǒng)穩(wěn)定性2024-10-10Java的Flowable工作流之加簽轉(zhuǎn)簽詳解
這篇文章主要介紹了Java的Flowable工作流之加簽轉(zhuǎn)簽詳解,Flowable是一個(gè)開(kāi)源的工作流引擎,它提供了一套強(qiáng)大的工具和功能,用于設(shè)計(jì)、執(zhí)行和管理各種類(lèi)型的工作流程,需要的朋友可以參考下2023-11-11IDEA下創(chuàng)建SpringBoot+MyBatis+MySql項(xiàng)目實(shí)現(xiàn)動(dòng)態(tài)登錄與注冊(cè)功能
這篇文章主要介紹了IDEA下創(chuàng)建SpringBoot+MyBatis+MySql項(xiàng)目實(shí)現(xiàn)動(dòng)態(tài)登錄與注冊(cè)功能,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-02-02spring/springboot整合dubbo詳細(xì)教程
今天教大家如何使用spring/springboot整合dubbo,文中有非常詳細(xì)的圖文介紹及代碼示例,對(duì)正在學(xué)習(xí)java的小伙伴有很好地幫助,需要的朋友可以參考下2021-05-05java實(shí)現(xiàn)圖書(shū)檢索系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)圖書(shū)檢索系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-05-05java線(xiàn)程池工作隊(duì)列飽和策略代碼示例
這篇文章主要介紹了java線(xiàn)程池工作隊(duì)列飽和策略代碼示例,涉及線(xiàn)程池的簡(jiǎn)單介紹,工作隊(duì)列飽和策略的分析及代碼示例,具有一定參考價(jià)值,需要的朋友可以了解下。2017-11-11淺談常用Java數(shù)據(jù)庫(kù)連接池(小結(jié))
這篇文章主要介紹了淺談常用Java數(shù)據(jù)庫(kù)連接池(小結(jié)),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-07-07