国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

SpringBoot內(nèi)嵌Tomcat啟動(dòng)流程

這篇具有很好參考價(jià)值的文章主要介紹了SpringBoot內(nèi)嵌Tomcat啟動(dòng)流程。希望對大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問。

前言

Spring MVC 讓開發(fā)者不用了解 Servlet 細(xì)節(jié),專注于 Controller 編寫 API 接口。Spring Boot 更是采用約定大于配置的設(shè)計(jì)思想,通過內(nèi)嵌 Tomcat 的方式讓開發(fā)者可以快速構(gòu)建并部署一個(gè) Web 應(yīng)用。怎么做到的呢?

Tomcat啟動(dòng)方式

早期的開發(fā),一般是基于 Spring 和 Spring MVC 構(gòu)建我們的應(yīng)用,然后把項(xiàng)目打成 War 包。在服務(wù)器上安裝 Tomcat,把我們的 War 包放到對應(yīng)的 webapp 目錄下,啟動(dòng) Tomcat 服務(wù)就可以訪問了。
其實(shí)要部署我們的服務(wù),沒必要這么繁瑣,通過代碼啟動(dòng) Tomcat 早就不是新鮮事了。

我這里寫一個(gè)示例,只引入 Spring MVC 和 Tomcat 依賴:

<dependencies>
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.3.31</version>
  </dependency>
  <dependency>
    <groupId>org.apache.tomcat.embed</groupId>
    <artifactId>tomcat-embed-core</artifactId>
    <version>9.0.83</version>
  </dependency>
</dependencies>

編寫我們的 Controller

@RestController
public class HelloContrller {

    @RequestMapping("hello")
    public String hello() {
        return "hello world!";
    }
}

再編寫我們的啟動(dòng)類,手動(dòng)把 Tomcat 給啟動(dòng)起來并注冊 DispatcherServlet。

@Configuration
@ComponentScan
public class Application {
    public static void main(String[] args) throws Exception {
        AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
        context.register(Application.class);
        context.refresh();

        Tomcat tomcat = new Tomcat();
        Connector connector = new Connector();
        connector.setPort(8080);
        tomcat.getService().addConnector(connector);

        final String contextPath = "";
        StandardContext standardContext = new StandardContext();
        standardContext.setPath(contextPath);
        standardContext.addLifecycleListener(new Tomcat.FixContextListener());
        tomcat.getHost().addChild(standardContext);

        standardContext.addServletContainerInitializer(new ServletContainerInitializer() {
            @Override
            public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException {
                System.err.println("Servlet容器初始化...");
                DispatcherServlet servlet = new DispatcherServlet(context);
                tomcat.addServlet(contextPath, "DispatcherServlet", servlet);
                standardContext.addServletMappingDecoded("/*", "DispatcherServlet");
            }
        }, Collections.EMPTY_SET);
        tomcat.start();
    }
}

運(yùn)行 Application 類,即可訪問服務(wù)

curl localhost:8080/hello
hello world!

Spring Boot 底層其實(shí)也是這么干的,一起來分析下吧。

設(shè)計(jì)實(shí)現(xiàn)

回到程序啟動(dòng)的入口,為什么執(zhí)行下面一行代碼,Web 服務(wù)就起來了。

SpringApplication.run(Application.class, args);

Spring Boot 首先會(huì)實(shí)例化一個(gè) SpringApplication 對象,在構(gòu)造函數(shù)里,首先要推導(dǎo)出 Web 應(yīng)用類型,才好啟對應(yīng)的服務(wù)。

public enum WebApplicationType {
	NONE,
    SERVLET,
    REACTIVE;
}
  • NONE:無需啟動(dòng) Web 服務(wù)
  • SERVLET:基于 Servlet 容器的 Web 應(yīng)用
  • REACTIVE:響應(yīng)時(shí) Web 應(yīng)用

推導(dǎo)的方法是WebApplicationType#deduceFromClasspath,原理是檢查 ClassPath 路徑下是否存在對應(yīng)的類。比如:存在org.springframework.web.reactive.DispatcherHandler類那就是 SERVLET 類型(不絕對)

private static final String[] SERVLET_INDICATOR_CLASSES = { "javax.servlet.Servlet",
        "org.springframework.web.context.ConfigurableWebApplicationContext" };
private static final String WEBMVC_INDICATOR_CLASS = "org.springframework.web.servlet.DispatcherServlet";
private static final String WEBFLUX_INDICATOR_CLASS = "org.springframework.web.reactive.DispatcherHandler";
private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";

static WebApplicationType deduceFromClasspath() {
    if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
            && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
        return WebApplicationType.REACTIVE;
    }
    for (String className : SERVLET_INDICATOR_CLASSES) {
        if (!ClassUtils.isPresent(className, null)) {
            return WebApplicationType.NONE;
        }
    }
    return WebApplicationType.SERVLET;
}

Spring Boot 本質(zhì)還是一個(gè) Spring 應(yīng)用,所以它肯定是要依賴上下文容器對象的。所以在run()里它會(huì)調(diào)用createApplicationContext()根據(jù) Web 應(yīng)用類型創(chuàng)建對應(yīng)的 ConfigurableApplicationContext。不同的 Web 應(yīng)用類型對應(yīng)不同的實(shí)現(xiàn)類,創(chuàng)建職責(zé)交給了DefaultApplicationContextFactory#create,它會(huì)去解析META-INF/spring.factories文件里配置的工廠類,然后判斷哪個(gè)工廠類支持創(chuàng)建。

private <T> T getFromSpringFactories(WebApplicationType webApplicationType,
        BiFunction<ApplicationContextFactory, WebApplicationType, T> action, Supplier<T> defaultResult) {
    for (ApplicationContextFactory candidate : SpringFactoriesLoader.loadFactories(ApplicationContextFactory.class,
            getClass().getClassLoader())) {
        // 實(shí)例化 AnnotationConfigServletWebServerApplicationContext
        T result = action.apply(candidate, webApplicationType);
        if (result != null) {
            return result;
        }
    }
    return (defaultResult != null) ? defaultResult.get() : null;
}

默認(rèn)配置的工廠類:

org.springframework.boot.ApplicationContextFactory=\
org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext.Factory,\
org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext.Factory

默認(rèn)是 Servlet 環(huán)境,所以會(huì)使用 AnnotationConfigServletWebServerApplicationContext.Factory 工廠類,創(chuàng)建的上下文對象是 AnnotationConfigServletWebServerApplicationContext。
實(shí)例化上下文對象后,緊接著就是調(diào)用其refresh()刷新上下文,這是個(gè)模板方法,流程在分析 Spring 源碼時(shí)已經(jīng)說過了,這里就略過了。
我們這里要重點(diǎn)關(guān)注的是子類重寫后的擴(kuò)展方法ServletWebServerApplicationContext#onRefresh,它會(huì)在父類準(zhǔn)備好整個(gè)環(huán)境后創(chuàng)建 Web 服務(wù)。

@Override
protected void onRefresh() {
    super.onRefresh();
    try {
        createWebServer();
    }
    catch (Throwable ex) {
        throw new ApplicationContextException("Unable to start web server", ex);
    }
}

createWebServer()首先要獲取 ServletWebServerFactory 工廠對象,默認(rèn)的 Servlet 容器是 Tomcat,所以工廠類是 TomcatServletWebServerFactory。在實(shí)例化工廠類時(shí)要求傳入一組 ServletContextInitializer,Spring 在初始化 Servlet 容器時(shí)會(huì)調(diào)用它的onStartup()用于注冊 Servlet。

private void createWebServer() {
    WebServer webServer = this.webServer;
    ServletContext servletContext = getServletContext();
    if (webServer == null && servletContext == null) {// 默認(rèn)走這里
        StartupStep createWebServer = this.getApplicationStartup().start("spring.boot.webserver.create");
        ServletWebServerFactory factory = getWebServerFactory();
        createWebServer.tag("factory", factory.getClass().toString());
        // 通過工廠獲取WebServer,會(huì)直接啟動(dòng)
        this.webServer = factory.getWebServer(getSelfInitializer());
        createWebServer.end();
        getBeanFactory().registerSingleton("webServerGracefulShutdown",
                new WebServerGracefulShutdownLifecycle(this.webServer));
        getBeanFactory().registerSingleton("webServerStartStop",
                new WebServerStartStopLifecycle(this, this.webServer));
    }
    ......
}
private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
    return this::selfInitialize;
}

// Spring初始化Servlet容器時(shí)觸發(fā)
private void selfInitialize(ServletContext servletContext) throws ServletException {
    prepareWebApplicationContext(servletContext);
    registerApplicationScope(servletContext);
    WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
    for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
        beans.onStartup(servletContext);
    }
}

TomcatServletWebServerFactory#getWebServer會(huì)實(shí)例化 Tomcat 并啟動(dòng)。

@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
    if (this.disableMBeanRegistry) {
        Registry.disableRegistry();
    }
    Tomcat tomcat = new Tomcat();
    // 基礎(chǔ)目錄
    File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
    tomcat.setBaseDir(baseDir.getAbsolutePath());
    for (LifecycleListener listener : this.serverLifecycleListeners) {
        tomcat.getServer().addLifecycleListener(listener);
    }
    // 連接器
    Connector connector = new Connector(this.protocol);
    connector.setThrowOnFailure(true);
    tomcat.getService().addConnector(connector);
    customizeConnector(connector);
    tomcat.setConnector(connector);
    tomcat.getHost().setAutoDeploy(false);
    // 配置Engine
    configureEngine(tomcat.getEngine());
    for (Connector additionalConnector : this.additionalTomcatConnectors) {
        tomcat.getService().addConnector(additionalConnector);
    }
    /**
     * 配置上下文,這里會(huì)把ServletContextInitializer封裝成TomcatStarter
     * 并設(shè)置到Host.Context
     */
    prepareContext(tomcat.getHost(), initializers);
    return getTomcatWebServer(tomcat);
}

配置 Tomcat 是個(gè)復(fù)雜的過程,這里不贅述,與我們最相關(guān)的就是 Port、ContextPath、Servlet 的配置,我們重點(diǎn)關(guān)注 Servlet 的配置。
我們知道,Spring MVC 的核心是 DispatcherServlet,它是何時(shí)被注冊到 Tomcat 的呢???這就不得不提到另一個(gè)組件 ServletContainerInitializer。
ServletContainerInitializer 是 Servlet 3.0 提供的用來初始化 Servlet 容器的接口,通過實(shí)現(xiàn)這個(gè)接口可以讓第三方組件有機(jī)會(huì)來對容器做一些初始化的工作,比如動(dòng)態(tài)的注冊 Servlet、Filter 等等。
顯然,Spring Boot 需要注冊 DispatcherServlet。所以 Spring Boot 首先會(huì)把容器內(nèi)的所有 ServletContextInitializer Bean 統(tǒng)一封裝成 TomcatStarter,而 TomcatStarter 恰恰就是 ServletContainerInitializer 的實(shí)現(xiàn)類。所以 Tomcat 啟動(dòng)時(shí)會(huì)觸發(fā)其onStartup()

@Override
public void onStartup(Set<Class<?>> classes, ServletContext servletContext) throws ServletException {
    try {
        for (ServletContextInitializer initializer : this.initializers) {
            initializer.onStartup(servletContext);
        }
    }
    catch (Exception ex) {
    }
}

代碼很簡單,就是挨個(gè)調(diào)用ServletContextInitializer#onStartup,其中有個(gè)最關(guān)鍵的實(shí)現(xiàn)類就是 DispatcherServletRegistrationBean,顧名思義,它就是用來注冊 DispatcherServlet。
SpringBoot內(nèi)嵌Tomcat啟動(dòng)流程,Spring Boot,spring boot,tomcat,后端
注冊的方法是ServletRegistrationBean#addRegistration,這里就會(huì)注冊我們最關(guān)心的 DispatcherServlet

@Override
protected ServletRegistration.Dynamic addRegistration(String description, ServletContext servletContext) {
    String name = getServletName();
    return servletContext.addServlet(name, this.servlet);
}

為了便于理解,這里畫了一張流程圖:
SpringBoot內(nèi)嵌Tomcat啟動(dòng)流程,Spring Boot,spring boot,tomcat,后端

尾巴

Spring Boot 本身也是個(gè) Spring 應(yīng)用,它也要依賴于上下文容器對象,如果我們構(gòu)建的是 Web 應(yīng)用,它就會(huì)創(chuàng)建適用于 Web 環(huán)境的上下文容器,例如 ServletWebServerApplicationContext,然后通過父類的模板方法來 refresh,只不過它重寫了 onRefresh 方法,等待父類準(zhǔn)備好環(huán)境后會(huì)創(chuàng)建 WebServer,啟動(dòng)我們的 Web 服務(wù),默認(rèn)啟動(dòng)的是 Tomcat,然后通過實(shí)現(xiàn) ServletContainerInitializer 的方式來注冊 DispatcherServlet。這就是 Spring Boot 內(nèi)嵌 Tomcat 的秘密。文章來源地址http://www.zghlxwxcb.cn/news/detail-784318.html

到了這里,關(guān)于SpringBoot內(nèi)嵌Tomcat啟動(dòng)流程的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • SpringBoot內(nèi)嵌Tomcat啟動(dòng)流程

    SpringBoot內(nèi)嵌Tomcat啟動(dòng)流程

    Spring MVC 讓開發(fā)者不用了解 Servlet 細(xì)節(jié),專注于 Controller 編寫 API 接口。Spring Boot 更是采用約定大于配置的設(shè)計(jì)思想,通過內(nèi)嵌 Tomcat 的方式讓開發(fā)者可以快速構(gòu)建并部署一個(gè) Web 應(yīng)用。怎么做到的呢? 早期的開發(fā),一般是基于 Spring 和 Spring MVC 構(gòu)建我們的應(yīng)用,然后把項(xiàng)目打

    2024年02月02日
    瀏覽(78)
  • 全網(wǎng)最清楚的:Spring Boot 啟動(dòng)流程講解

    Spring Boot 啟動(dòng)流程 簡介 步驟 加載配置 創(chuàng)建應(yīng)用程序上下文 執(zhí)行自動(dòng)配置 啟動(dòng)應(yīng)用程序 處理請求 源碼層說明 擴(kuò)展 自定義注解以及自定義注解實(shí)現(xiàn)中有bean,與啟動(dòng)流程什么有關(guān) Bean掃描 注解處理 Spring Boot 的啟動(dòng)流程 充分利用了 Spring 框架的強(qiáng)大功能,同時(shí)又為開發(fā)者提供

    2024年02月07日
    瀏覽(19)
  • Spring Boot 3.x 系列【51】啟動(dòng)流程 | 最后階段

    有道無術(shù),術(shù)尚可求,有術(shù)無道,止于術(shù)。 本系列Spring Boot版本3.1.0 源碼地址:https://gitee.com/pearl-organization/study-spring-boot3 終于啟動(dòng)流程到了最后一個(gè)階段,這里主要是打印啟動(dòng)完成日志、調(diào)用監(jiān)聽器、運(yùn)行 Runners 等 。

    2024年02月15日
    瀏覽(24)
  • 還不懂 Spring Boot 啟動(dòng)流程的,看這一篇就夠了!

    通常,我們只需為一個(gè)類添加@SpringBootApplication注解,然后再添加一個(gè)main方法,其內(nèi)固定的寫法為SpringApplication.run(Application.class, args)。由此,便可啟動(dòng)Spring Boot服務(wù)。 具體而言,Spring Boot的啟動(dòng)流程包括以下幾個(gè)步驟: 載入 Spring Boot 應(yīng)用的啟動(dòng)類 根據(jù)啟動(dòng)類所在的包路徑掃

    2024年02月05日
    瀏覽(20)
  • SpringBoot源碼學(xué)習(xí)4——SpringBoot內(nèi)嵌Tomcat啟動(dòng)流程源碼分析

    SpringBoot源碼學(xué)習(xí)4——SpringBoot內(nèi)嵌Tomcat啟動(dòng)流程源碼分析

    系列文章目錄和關(guān)于我 我在初學(xué)spring的時(shí)候,很懵逼,因?yàn)檎麄€(gè)項(xiàng)目中不存在main方法,讓我有點(diǎn)摸不著頭腦。那時(shí)候我知道有個(gè)東西叫tomcat是它監(jiān)聽了端口,解析了協(xié)議調(diào)到了我的servlet。 在我初學(xué)SpringBoot的時(shí)候,很懵逼,有main方法了,但是tomcat在哪里呢,又是如何啟動(dòng)起

    2024年02月04日
    瀏覽(20)
  • Spring Boot 3.x 系列【49】啟動(dòng)流程 | 創(chuàng)建、準(zhǔn)備應(yīng)用上下文

    有道無術(shù),術(shù)尚可求,有術(shù)無道,止于術(shù)。 本系列Spring Boot版本3.1.0 源碼地址:https://gitee.com/pearl-organization/study-spring-boot3

    2024年02月14日
    瀏覽(24)
  • Spring Boot學(xué)習(xí)隨筆-第一個(gè)SpringBoot項(xiàng)目快速啟動(dòng)(org.springframework.boot、@SpringBootApplication、application.yml)

    Spring Boot學(xué)習(xí)隨筆-第一個(gè)SpringBoot項(xiàng)目快速啟動(dòng)(org.springframework.boot、@SpringBootApplication、application.yml)

    學(xué)習(xí)視頻:【編程不良人】2021年SpringBoot最新最全教程 創(chuàng)建第一個(gè)Module 環(huán)境要求 jdk1.8+ maven3.2+ Spring Framework 5.x+ Tomcat 9.0+ IDEA 2021 自動(dòng)保存刷新pom 在resources下添加application.yml文件后,即可啟動(dòng)springboot應(yīng)用 由于tomcat內(nèi)嵌在springboot里面了,所以我們在修改端口號等設(shè)置也在配置

    2024年02月05日
    瀏覽(38)
  • Spring Boot——Spring Boot啟動(dòng)原理

    Spring Boot——Spring Boot啟動(dòng)原理

    2.1.1Spring Boot入口 2.1.2初始化SpringApplication 準(zhǔn)備階段,在程序運(yùn)行之前初始化一些屬性,用于在后序啟動(dòng)應(yīng)用程序過程中。 2.1.2.1判斷當(dāng)前應(yīng)用程序類型 2.1.2.2設(shè)置應(yīng)用程序的所有初始化器(initializers) 上面這段代碼主要是通過加載 “spring.factories” 配置文件中指定類型的工廠名

    2024年02月15日
    瀏覽(19)
  • 【Spring Boot】以博客管理系統(tǒng)舉例,完整表述SpringBoot從對接Vue到數(shù)據(jù)庫的流程與結(jié)構(gòu)。

    博客管理系統(tǒng)是一個(gè)典型的前后端分離的應(yīng)用,其中前端使用Vue框架進(jìn)行開發(fā),后端使用Spring Boot框架進(jìn)行開發(fā),數(shù)據(jù)庫使用MySQL進(jìn)行存儲(chǔ)。下面是從對接Vue到數(shù)據(jù)庫的完整流程和結(jié)構(gòu)。 對接Vue 在前端Vue應(yīng)用中,需要訪問后端Spring Boot應(yīng)用的REST API接口,與其進(jìn)行數(shù)據(jù)交互。具

    2024年02月11日
    瀏覽(37)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包