javascript
SpringBoot中的Tomcat是如何启动的
添加如上 Web 的依賴(lài),Spring Boot 就幫我們內(nèi)置了 Servlet 容器,默認(rèn)使用的是 Tomcat,同樣也支持修改,比如可以使用 jetty、Undertow 等。
因?yàn)閮?nèi)置了啟動(dòng)容器,應(yīng)用程序可以直接通過(guò) Maven 命令將項(xiàng)目編譯成可執(zhí)行的 jar 包,通過(guò) java -jar 命令直接啟動(dòng),不需要再像以前一樣,打包成 War 包,然后部署在 Tomcat 中。
那么:你知道內(nèi)置的 Tomcat 在 Spring Boot 中是怎么啟動(dòng)的嗎?
從啟動(dòng)入口分析
如果不知道從哪開(kāi)始,那么至少應(yīng)該知道 Spring Boot 其實(shí)運(yùn)行的就是一個(gè) main 方法,
本文環(huán)境:Spring Boot:2.2.2.RELEASE
@SpringBootApplication public class MyApplication {public static void main(String[] args) {SpringApplication.run(MyApplication.class, args);} }我們點(diǎn)進(jìn)這個(gè) SpringApplication.run() 方法:
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {return run(new Class[]{primarySource}, args); }這列的 run() 方法返回的是 ConfigurableApplicationContext 對(duì)象,我們繼續(xù)跟蹤這個(gè) run() 方法:
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {return (new SpringApplication(primarySources)).run(args); }又套了一層,繼續(xù)點(diǎn)擊這個(gè)返回的 run() 方法:
public ConfigurableApplicationContext run(String... args) {StopWatch stopWatch = new StopWatch();stopWatch.start();ConfigurableApplicationContext context = null;Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();/** 1、配置屬性* 設(shè)置系統(tǒng)屬性 java.awt.headless,為 true 則啟用headless模式* headless模式是應(yīng)用的一種配置模式,在服務(wù)器缺少顯示設(shè)備、鍵盤(pán)、鼠標(biāo)等外設(shè)的情況下可以使用該模式* 比如我們使用的Linux服務(wù)器就是缺少前述的這些設(shè)備,但是又需要使用這些設(shè)備提供的能力*/configureHeadlessProperty();/** 2、獲取監(jiān)聽(tīng)器,發(fā)布應(yīng)用開(kāi)始啟動(dòng)事件* 通過(guò)SpringFactoriesLoader檢索META-INF/spring.factories,* 找到聲明的所有SpringApplicationRunListener的實(shí)現(xiàn)類(lèi)并將其實(shí)例化,* 之后逐個(gè)調(diào)用其started()方法,廣播SpringBoot要開(kāi)始執(zhí)行了*/SpringApplicationRunListeners listeners = getRunListeners(args);/* 發(fā)布應(yīng)用開(kāi)始啟動(dòng)事件 */listeners.starting();try {/* 3、初始化參數(shù) */ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);/** 4、配置環(huán)境,輸出banner* 創(chuàng)建并配置當(dāng)前SpringBoot應(yīng)用將要使用的Environment(包括配置要使用的PropertySource以及Profile),* 并遍歷調(diào)用所有的SpringApplicationRunListener的environmentPrepared()方法,廣播Environment準(zhǔn)備完畢。*/ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);configureIgnoreBeanInfo(environment);/* 打印banner,如果在resources目錄下創(chuàng)建了我們自己的banner就會(huì)進(jìn)行打印,否則默認(rèn)使用spring的 */Banner printedBanner = printBanner(environment);/* 5、創(chuàng)建應(yīng)用上下文 */context = createApplicationContext();/* 通過(guò)SpringFactoriesLoader檢索META-INF/spring.factories,獲取并實(shí)例化異常分析器。 */exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,new Class[] { ConfigurableApplicationContext.class }, context);/** 6、預(yù)處理上下文* 為ApplicationContext加載environment,之后逐個(gè)執(zhí)行ApplicationContextInitializer的initialize()方法來(lái)進(jìn)一步封裝ApplicationContext,* 并調(diào)用所有的SpringApplicationRunListener的contextPrepared()方法,【EventPublishingRunListener只提供了一個(gè)空的contextPrepared()方法】,* 之后初始化IoC容器,并調(diào)用SpringApplicationRunListener的contextLoaded()方法,廣播ApplicationContext的IoC加載完成,* 這里就包括通過(guò)@EnableAutoConfiguration導(dǎo)入的各種自動(dòng)配置類(lèi)。*/prepareContext(context, environment, listeners, applicationArguments,printedBanner);/* 7、刷新上下文 */refreshContext(context);/* 8、再一次刷新上下文,其實(shí)是空方法,可能是為了后續(xù)擴(kuò)展。 */afterRefresh(context, applicationArguments);stopWatch.stop();if (this.logStartupInfo) {new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);}/* 9、發(fā)布應(yīng)用已經(jīng)啟動(dòng)的事件 */listeners.started(context);/** 遍歷所有注冊(cè)的ApplicationRunner和CommandLineRunner,并執(zhí)行其run()方法。* 我們可以實(shí)現(xiàn)自己的ApplicationRunner或者CommandLineRunner,來(lái)對(duì)SpringBoot的啟動(dòng)過(guò)程進(jìn)行擴(kuò)展。*/callRunners(context, applicationArguments);}catch (Throwable ex) {handleRunFailure(context, ex, exceptionReporters, listeners);throw new IllegalStateException(ex);}try {/* 10、發(fā)布應(yīng)用已經(jīng)啟動(dòng)完成的監(jiān)聽(tīng)事件 */listeners.running(context);}catch (Throwable ex) {handleRunFailure(context, ex, exceptionReporters, null);throw new IllegalStateException(ex);}return context; }如果覺(jué)得這個(gè)方法看的云里霧里的,那么可以概括為如下幾步:
而我們 Tomcat 的啟動(dòng)主要是在第5步創(chuàng)建上下文,以及第 7步刷新上下文實(shí)現(xiàn)的。
創(chuàng)建上下文
第5步中,創(chuàng)建上下文主要是調(diào)用的 createApplicationContext() 方法:
protected ConfigurableApplicationContext createApplicationContext() {/** 1. 根據(jù)Web應(yīng)用類(lèi)型,獲取對(duì)應(yīng)的ApplicationContext子類(lèi) **/Class<?> contextClass = this.applicationContextClass;if (contextClass == null) {try {switch(this.webApplicationType) {case SERVLET:contextClass = Class.forName("org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext");break;case REACTIVE:contextClass = Class.forName("org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext");break;default:contextClass = Class.forName("org.springframework.context.annotation.AnnotationConfigApplicationContext");}} catch (ClassNotFoundException var3) {throw new IllegalStateException("Unable create a default ApplicationContext, please specify an ApplicationContextClass", var3);}}/** 2. 實(shí)例化子類(lèi) **/return (ConfigurableApplicationContext)BeanUtils.instantiateClass(contextClass); }代碼中主要就是通過(guò) switch 語(yǔ)句,根據(jù) webApplicationType 的類(lèi)型來(lái)創(chuàng)建不同的 ApplicationContext:
- SERVLET:Web類(lèi)型,實(shí)例化 AnnotationConfigServletWebServerApplicationContext
- REACTIVE:響應(yīng)式Web類(lèi)型,實(shí)例化 AnnotationConfigReactiveWebServerApplicationContext
- default:非Web類(lèi)型,實(shí)例化 AnnotationConfigApplicationContext
因?yàn)槲覀兊膽?yīng)用是 Web 類(lèi)型,所以實(shí)例化的是 AnnotationConfigServletWebServerApplicationContext,如下是該類(lèi)的關(guān)系圖(由Diagram截圖):
我們?cè)谏蠄D的底部觸發(fā),可以看到 AnnotationConfigServletWebServerApplicationContext > ServletWebServerApplicationContext > … AbstractApplicationContext(>表示繼承),總之,最終繼承到了 AbstractApplicationContext,這個(gè)類(lèi)是 ApplicationContext 的抽象實(shí)現(xiàn)類(lèi),該抽象類(lèi)實(shí)現(xiàn)應(yīng)用上下文的一些具體操作。
至此,并沒(méi)有看到 Tomcat 的相關(guān)代碼,其實(shí)這一步主要就是「創(chuàng)建上下文」,拿到「上下文」之后需要傳遞給「刷新上下文」,交由刷新上下文創(chuàng)建 Web 服務(wù)。
刷新上下文
第7步中,刷新上下文時(shí)調(diào)用的 refreshContext(context) 方法,其中 context 就是第5步創(chuàng)建的上下文,方法如下:
private void refreshContext(ConfigurableApplicationContext context) {refresh(context);if (this.registerShutdownHook) {try {context.registerShutdownHook();}catch (AccessControlException ex) {// Not allowed in some environments.}} }protected void refresh(ApplicationContext applicationContext) {Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);((AbstractApplicationContext) applicationContext).refresh(); }refreshContext() 方法傳遞的 context,經(jīng)由 refresh() 方法強(qiáng)轉(zhuǎn)成父類(lèi) AbstractApplicationContext,具體調(diào)用過(guò)程如下:
public void refresh() throws BeansException, IllegalStateException {synchronized(this.startupShutdownMonitor) {this.prepareRefresh();ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();this.prepareBeanFactory(beanFactory);try {this.postProcessBeanFactory(beanFactory);this.invokeBeanFactoryPostProcessors(beanFactory);this.registerBeanPostProcessors(beanFactory);this.initMessageSource();this.initApplicationEventMulticaster();/** 主要關(guān)系 onRefresh() 方法 ------------- **/this.onRefresh();this.registerListeners();this.finishBeanFactoryInitialization(beanFactory);this.finishRefresh();} catch (BeansException var9) {if (this.logger.isWarnEnabled()) {this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);}this.destroyBeans();this.cancelRefresh(var9);throw var9;} finally {this.resetCommonCaches();}} }在這個(gè)方法中我們主要關(guān)心 onRefresh() 方法,onRefresh() 方法是調(diào)用其子類(lèi)實(shí)現(xiàn)的,也就是 ServletWebServerApplicationContext,
如下是子類(lèi)的 onRefresh() 方法:
其中 createWebServer() 方法是用來(lái)啟動(dòng)web服務(wù)的,但是還沒(méi)有真正啟動(dòng) Tomcat,只是通過(guò)ServletWebServerFactory 創(chuàng)建了一個(gè) WebServer,我們繼續(xù)來(lái)看這個(gè) ServletWebServerFactory:
ServletWebServerFactory 有4個(gè)實(shí)現(xiàn)類(lèi),其中我們最常用的是 TomcatServletWebServerFactory和JettyServletWebServerFactory,而默認(rèn)的 Web 環(huán)境就是 TomcatServletWebServerFactory。
而到這總算是看到 Tomcat 相關(guān)的字眼了。
來(lái)看一下 TomcatServletWebServerFactory 的 getWebServer() 方法:
public WebServer getWebServer(ServletContextInitializer... initializers) {if (this.disableMBeanRegistry) {Registry.disableRegistry();}/** 1、創(chuàng)建Tomcat實(shí)例 **/Tomcat tomcat = new Tomcat();File baseDir = this.baseDirectory != null ? this.baseDirectory : this.createTempDir("tomcat");tomcat.setBaseDir(baseDir.getAbsolutePath());Connector connector = new Connector(this.protocol);connector.setThrowOnFailure(true);tomcat.getService().addConnector(connector);this.customizeConnector(connector);/** 2、給創(chuàng)建好的tomcat設(shè)置連接器connector **/tomcat.setConnector(connector);/** 設(shè)置不自動(dòng)部署 **/tomcat.getHost().setAutoDeploy(false);/** 3、配置Tomcat容器引擎 **/this.configureEngine(tomcat.getEngine());Iterator var5 = this.additionalTomcatConnectors.iterator();while(var5.hasNext()) {Connector additionalConnector = (Connector)var5.next();tomcat.getService().addConnector(additionalConnector);}/*** 準(zhǔn)備Tomcat的StandardContext,并添加到Tomcat中,同時(shí)把initializers 注冊(cè)到類(lèi)型為* TomcatStarter的ServletContainerInitializer中**/this.prepareContext(tomcat.getHost(), initializers);/** 將創(chuàng)建好的Tomcat包裝成WebServer返回**/return this.getTomcatWebServer(tomcat); }public Engine getEngine() {Service service = this.getServer().findServices()[0];if (service.getContainer() != null) {return service.getContainer();} else {Engine engine = new StandardEngine();engine.setName("Tomcat");engine.setDefaultHost(this.hostname);engine.setRealm(this.createDefaultRealm());service.setContainer(engine);return engine;} }getWebServer() 這個(gè)方法創(chuàng)建了 Tomcat 對(duì)象,并且做了兩件重要的事情:
首先說(shuō)一下這個(gè) Connector 連接器,Tomcat 有兩個(gè)核心功能:
針對(duì)這兩個(gè)功能,Tomcat 設(shè)計(jì)了兩個(gè)核心組件來(lái)分別完成這兩件事,即:連接器(Connector)和容器(Container)。
整個(gè)過(guò)程大致就是:Connector 連接器接收連接請(qǐng)求,創(chuàng)建Request和Response對(duì)象用于和請(qǐng)求端交換數(shù)據(jù),然后分配線(xiàn)程讓Engine(也就是Servlet容器)來(lái)處理這個(gè)請(qǐng)求,并把產(chǎn)生的Request和Response對(duì)象傳給Engine。當(dāng)Engine處理完請(qǐng)求后,也會(huì)通過(guò)Connector將響應(yīng)返回給客戶(hù)端。
這里面提到了 Engine,這個(gè)是 Tomcat 容器里的頂級(jí)容器(Container),我們可以通過(guò) Container 類(lèi)查看其他的子容器:Engine、Host、Context、Wrapper
4者的關(guān)系是:Engine 是最高級(jí)別的容器,Engine 子容器是 Host,Host 的子容器是 Context,Context 子容器是 Wrapper,所以這4個(gè)容器的關(guān)系就是父子關(guān)系,即:Wrapper > Context > Host > Engine (>表示繼承)
至此我們了解了 Engine 這個(gè)就是個(gè)容器,然后我們?cè)倏匆幌逻@個(gè) configureEngine(tomcat.getEngine()) 具體干了啥:
private void configureEngine(Engine engine) {engine.setBackgroundProcessorDelay(this.backgroundProcessorDelay);Iterator var2 = this.engineValves.iterator();while(var2.hasNext()) {Valve valve = (Valve)var2.next();engine.getPipeline().addValve(valve);} }其中 engine.setBackgroundProcessorDelay(this.backgroundProcessorDelay) 是指定背景線(xiàn)程的執(zhí)行間隔,例如背景線(xiàn)程會(huì)在每隔多長(zhǎng)時(shí)間后判斷session是否失效之類(lèi)。
再回到 getWebServer() 方法,最終 getWebServer() 方法返回了 TomcatWebServer。
return this.getTomcatWebServer(tomcat);
通過(guò) getTomcatWebServer() 方法,繼續(xù)下沉:
/*** 構(gòu)造函數(shù)實(shí)例化 TomcatWebServer**/ public TomcatWebServer(Tomcat tomcat, boolean autoStart) {this.monitor = new Object();this.serviceConnectors = new HashMap();Assert.notNull(tomcat, "Tomcat Server must not be null");this.tomcat = tomcat;this.autoStart = autoStart;this.initialize(); }private void initialize() throws WebServerException {/** 我們?cè)趩?dòng) Spring Boot 時(shí)經(jīng)常看到打印這句話(huà) **/logger.info("Tomcat initialized with port(s): " + this.getPortsDescription(false));synchronized(this.monitor) {try {this.addInstanceIdToEngineName();Context context = this.findContext();context.addLifecycleListener((event) -> {if (context.equals(event.getSource()) && "start".equals(event.getType())) {this.removeServiceConnectors();}});/** 啟動(dòng) tomcat **/this.tomcat.start();this.rethrowDeferredStartupExceptions();try {ContextBindings.bindClassLoader(context, context.getNamingToken(), this.getClass().getClassLoader());} catch (NamingException var5) {}this.startDaemonAwaitThread();} catch (Exception var6) {this.stopSilently();this.destroySilently();throw new WebServerException("Unable to start embedded Tomcat", var6);}} }至此,Tomcat 啟動(dòng)過(guò)程就很清晰了,總結(jié)一下。
總結(jié)
SpringBoot的啟動(dòng)主要是通過(guò)實(shí)例化SpringApplication來(lái)啟動(dòng)的,啟動(dòng)過(guò)程主要做了如下幾件事情:
而啟動(dòng) Tomcat 是在第7步 刷新上下文 這一步。
從整個(gè)流轉(zhuǎn)過(guò)程中我們知道了 Tomcat 的啟動(dòng)主要是實(shí)例化兩個(gè)組件:Connector、Container。
-
Spring Boot 創(chuàng)建 Tomcat 時(shí),會(huì)先創(chuàng)建一個(gè)根上下文,將 WebApplicationContext 傳給 Tomcat;
-
啟動(dòng) Web 容器,需要調(diào)用 getWebserver(),因?yàn)槟J(rèn)的 Web 環(huán)境就是 TomcatServletWebServerFactory,所以會(huì)創(chuàng)建 Tomcat 的 Webserver,這里會(huì)把根上下文作為參數(shù)給 TomcatServletWebServerFactory 的 getWebServer();
-
啟動(dòng) Tomcat,調(diào)用 Tomcat 中 Host、Engine 的啟動(dòng)方法。
博客園:https://www.cnblogs.com/niceyoo
總結(jié)
以上是生活随笔為你收集整理的SpringBoot中的Tomcat是如何启动的的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 气象数据集
- 下一篇: Spring Security(18)—