日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

SpringBoot中的Tomcat是如何启动的

發(fā)布時間:2023/12/10 javascript 34 豆豆
生活随笔 收集整理的這篇文章主要介紹了 SpringBoot中的Tomcat是如何启动的 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId> </dependency>

添加如上 Web 的依賴,Spring Boot 就幫我們內(nèi)置了 Servlet 容器,默認使用的是 Tomcat,同樣也支持修改,比如可以使用 jetty、Undertow 等。

因為內(nèi)置了啟動容器,應用程序可以直接通過 Maven 命令將項目編譯成可執(zhí)行的 jar 包,通過 java -jar 命令直接啟動,不需要再像以前一樣,打包成 War 包,然后部署在 Tomcat 中。

那么:你知道內(nèi)置的 Tomcat 在 Spring Boot 中是怎么啟動的嗎?

從啟動入口分析

如果不知道從哪開始,那么至少應該知道 Spring Boot 其實運行的就是一個 main 方法,

本文環(huán)境:Spring Boot:2.2.2.RELEASE

@SpringBootApplication public class MyApplication {public static void main(String[] args) {SpringApplication.run(MyApplication.class, args);} }

我們點進這個 SpringApplication.run() 方法:

public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {return run(new Class[]{primarySource}, args); }

這列的 run() 方法返回的是 ConfigurableApplicationContext 對象,我們繼續(xù)跟蹤這個 run() 方法:

public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {return (new SpringApplication(primarySources)).run(args); }

又套了一層,繼續(xù)點擊這個返回的 run() 方法:

public ConfigurableApplicationContext run(String... args) {StopWatch stopWatch = new StopWatch();stopWatch.start();ConfigurableApplicationContext context = null;Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();/** 1、配置屬性* 設置系統(tǒng)屬性 java.awt.headless,為 true 則啟用headless模式* headless模式是應用的一種配置模式,在服務器缺少顯示設備、鍵盤、鼠標等外設的情況下可以使用該模式* 比如我們使用的Linux服務器就是缺少前述的這些設備,但是又需要使用這些設備提供的能力*/configureHeadlessProperty();/** 2、獲取監(jiān)聽器,發(fā)布應用開始啟動事件* 通過SpringFactoriesLoader檢索META-INF/spring.factories,* 找到聲明的所有SpringApplicationRunListener的實現(xiàn)類并將其實例化,* 之后逐個調(diào)用其started()方法,廣播SpringBoot要開始執(zhí)行了*/SpringApplicationRunListeners listeners = getRunListeners(args);/* 發(fā)布應用開始啟動事件 */listeners.starting();try {/* 3、初始化參數(shù) */ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);/** 4、配置環(huán)境,輸出banner* 創(chuàng)建并配置當前SpringBoot應用將要使用的Environment(包括配置要使用的PropertySource以及Profile),* 并遍歷調(diào)用所有的SpringApplicationRunListener的environmentPrepared()方法,廣播Environment準備完畢。*/ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);configureIgnoreBeanInfo(environment);/* 打印banner,如果在resources目錄下創(chuàng)建了我們自己的banner就會進行打印,否則默認使用spring的 */Banner printedBanner = printBanner(environment);/* 5、創(chuàng)建應用上下文 */context = createApplicationContext();/* 通過SpringFactoriesLoader檢索META-INF/spring.factories,獲取并實例化異常分析器。 */exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,new Class[] { ConfigurableApplicationContext.class }, context);/** 6、預處理上下文* 為ApplicationContext加載environment,之后逐個執(zhí)行ApplicationContextInitializer的initialize()方法來進一步封裝ApplicationContext,* 并調(diào)用所有的SpringApplicationRunListener的contextPrepared()方法,【EventPublishingRunListener只提供了一個空的contextPrepared()方法】,* 之后初始化IoC容器,并調(diào)用SpringApplicationRunListener的contextLoaded()方法,廣播ApplicationContext的IoC加載完成,* 這里就包括通過@EnableAutoConfiguration導入的各種自動配置類。*/prepareContext(context, environment, listeners, applicationArguments,printedBanner);/* 7、刷新上下文 */refreshContext(context);/* 8、再一次刷新上下文,其實是空方法,可能是為了后續(xù)擴展。 */afterRefresh(context, applicationArguments);stopWatch.stop();if (this.logStartupInfo) {new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);}/* 9、發(fā)布應用已經(jīng)啟動的事件 */listeners.started(context);/** 遍歷所有注冊的ApplicationRunner和CommandLineRunner,并執(zhí)行其run()方法。* 我們可以實現(xiàn)自己的ApplicationRunner或者CommandLineRunner,來對SpringBoot的啟動過程進行擴展。*/callRunners(context, applicationArguments);}catch (Throwable ex) {handleRunFailure(context, ex, exceptionReporters, listeners);throw new IllegalStateException(ex);}try {/* 10、發(fā)布應用已經(jīng)啟動完成的監(jiān)聽事件 */listeners.running(context);}catch (Throwable ex) {handleRunFailure(context, ex, exceptionReporters, null);throw new IllegalStateException(ex);}return context; }

如果覺得這個方法看的云里霧里的,那么可以概括為如下幾步:

  • 配置系統(tǒng)屬性
  • 獲取監(jiān)聽,發(fā)布應用開始啟動時間
  • 初始化輸入?yún)?shù)
  • 配置環(huán)境,輸出banner
  • 創(chuàng)建上下文
  • 預處理上下文
  • 刷新上下文
  • 再次刷新上下文
  • 發(fā)布應用已經(jīng)啟動事件
  • 發(fā)布應用啟動完成事件
  • 而我們 Tomcat 的啟動主要是在第5步創(chuàng)建上下文,以及第 7步刷新上下文實現(xiàn)的。

    創(chuàng)建上下文

    第5步中,創(chuàng)建上下文主要是調(diào)用的 createApplicationContext() 方法:

    protected ConfigurableApplicationContext createApplicationContext() {/** 1. 根據(jù)Web應用類型,獲取對應的ApplicationContext子類 **/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. 實例化子類 **/return (ConfigurableApplicationContext)BeanUtils.instantiateClass(contextClass); }

    代碼中主要就是通過 switch 語句,根據(jù) webApplicationType 的類型來創(chuàng)建不同的 ApplicationContext:

    • SERVLET:Web類型,實例化 AnnotationConfigServletWebServerApplicationContext
    • REACTIVE:響應式Web類型,實例化 AnnotationConfigReactiveWebServerApplicationContext
    • default:非Web類型,實例化 AnnotationConfigApplicationContext

    因為我們的應用是 Web 類型,所以實例化的是 AnnotationConfigServletWebServerApplicationContext,如下是該類的關系圖(由Diagram截圖):

    我們在上圖的底部觸發(fā),可以看到 AnnotationConfigServletWebServerApplicationContext > ServletWebServerApplicationContext > … AbstractApplicationContext(>表示繼承),總之,最終繼承到了 AbstractApplicationContext,這個類是 ApplicationContext 的抽象實現(xiàn)類,該抽象類實現(xiàn)應用上下文的一些具體操作。

    至此,并沒有看到 Tomcat 的相關代碼,其實這一步主要就是「創(chuàng)建上下文」,拿到「上下文」之后需要傳遞給「刷新上下文」,交由刷新上下文創(chuàng)建 Web 服務。

    刷新上下文

    第7步中,刷新上下文時調(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() 方法強轉(zhuǎn)成父類 AbstractApplicationContext,具體調(diào)用過程如下:

    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();/** 主要關系 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();}} }

    在這個方法中我們主要關心 onRefresh() 方法,onRefresh() 方法是調(diào)用其子類實現(xiàn)的,也就是 ServletWebServerApplicationContext,

    如下是子類的 onRefresh() 方法:

    protected void onRefresh() {super.onRefresh();try {this.createWebServer();} catch (Throwable var2) {throw new ApplicationContextException("Unable to start web server", var2);} }private void createWebServer() {WebServer webServer = this.webServer;ServletContext servletContext = this.getServletContext();if (webServer == null && servletContext == null) {/** 得到Servlet工廠 **/ServletWebServerFactory factory = this.getWebServerFactory();this.webServer = factory.getWebServer(new ServletContextInitializer[]{this.getSelfInitializer()});} else if (servletContext != null) {try {this.getSelfInitializer().onStartup(servletContext);} catch (ServletException var4) {throw new ApplicationContextException("Cannot initialize servlet context", var4);}}this.initPropertySources(); }

    其中 createWebServer() 方法是用來啟動web服務的,但是還沒有真正啟動 Tomcat,只是通過ServletWebServerFactory 創(chuàng)建了一個 WebServer,我們繼續(xù)來看這個 ServletWebServerFactory:

    ServletWebServerFactory 有4個實現(xiàn)類,其中我們最常用的是 TomcatServletWebServerFactory和JettyServletWebServerFactory,而默認的 Web 環(huán)境就是 TomcatServletWebServerFactory。

    而到這總算是看到 Tomcat 相關的字眼了。

    來看一下 TomcatServletWebServerFactory 的 getWebServer() 方法:

    public WebServer getWebServer(ServletContextInitializer... initializers) {if (this.disableMBeanRegistry) {Registry.disableRegistry();}/** 1、創(chuàng)建Tomcat實例 **/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設置連接器connector **/tomcat.setConnector(connector);/** 設置不自動部署 **/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);}/*** 準備Tomcat的StandardContext,并添加到Tomcat中,同時把initializers 注冊到類型為* 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() 這個方法創(chuàng)建了 Tomcat 對象,并且做了兩件重要的事情:

  • 把連接器 Connector 對象添加到 Tomcat 中;
  • 配置容器引擎,configureEngine(tomcat.getEngine());
  • 首先說一下這個 Connector 連接器,Tomcat 有兩個核心功能:

  • 處理 Socket 連接,負責網(wǎng)絡字節(jié)流與 Request 和 Response 對象的轉(zhuǎn)化。
  • 加載和管理 Servlet,以及具體處理 Request 請求。
  • 針對這兩個功能,Tomcat 設計了兩個核心組件來分別完成這兩件事,即:連接器(Connector)和容器(Container)。

    整個過程大致就是:Connector 連接器接收連接請求,創(chuàng)建Request和Response對象用于和請求端交換數(shù)據(jù),然后分配線程讓Engine(也就是Servlet容器)來處理這個請求,并把產(chǎn)生的Request和Response對象傳給Engine。當Engine處理完請求后,也會通過Connector將響應返回給客戶端。

    這里面提到了 Engine,這個是 Tomcat 容器里的頂級容器(Container),我們可以通過 Container 類查看其他的子容器:Engine、Host、Context、Wrapper

    4者的關系是:Engine 是最高級別的容器,Engine 子容器是 Host,Host 的子容器是 Context,Context 子容器是 Wrapper,所以這4個容器的關系就是父子關系,即:Wrapper > Context > Host > Engine (>表示繼承)

    至此我們了解了 Engine 這個就是個容器,然后我們再看一下這個 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) 是指定背景線程的執(zhí)行間隔,例如背景線程會在每隔多長時間后判斷session是否失效之類。

    再回到 getWebServer() 方法,最終 getWebServer() 方法返回了 TomcatWebServer。

    return this.getTomcatWebServer(tomcat);

    通過 getTomcatWebServer() 方法,繼續(xù)下沉:

    /*** 構(gòu)造函數(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 {/** 我們在啟動 Spring Boot 時經(jīng)常看到打印這句話 **/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();}});/** 啟動 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 啟動過程就很清晰了,總結(jié)一下。

    總結(jié)

    SpringBoot的啟動主要是通過實例化SpringApplication來啟動的,啟動過程主要做了如下幾件事情:

  • 配置系統(tǒng)屬性
  • 獲取監(jiān)聽,發(fā)布應用開始啟動時間
  • 初始化輸入?yún)?shù)
  • 配置環(huán)境,輸出banner
  • 創(chuàng)建上下文
  • 預處理上下文
  • 刷新上下文
  • 再次刷新上下文
  • 發(fā)布應用已經(jīng)啟動事件
  • 發(fā)布應用啟動完成事件
  • 而啟動 Tomcat 是在第7步 刷新上下文 這一步。

    從整個流轉(zhuǎn)過程中我們知道了 Tomcat 的啟動主要是實例化兩個組件:Connector、Container。

    • Spring Boot 創(chuàng)建 Tomcat 時,會先創(chuàng)建一個根上下文,將 WebApplicationContext 傳給 Tomcat;

    • 啟動 Web 容器,需要調(diào)用 getWebserver(),因為默認的 Web 環(huán)境就是 TomcatServletWebServerFactory,所以會創(chuàng)建 Tomcat 的 Webserver,這里會把根上下文作為參數(shù)給 TomcatServletWebServerFactory 的 getWebServer();

    • 啟動 Tomcat,調(diào)用 Tomcat 中 Host、Engine 的啟動方法。

    博客園:https://www.cnblogs.com/niceyoo

    總結(jié)

    以上是生活随笔為你收集整理的SpringBoot中的Tomcat是如何启动的的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。