SpringBoot中的Tomcat是如何啓動的?

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

添加如上 Web 的依賴,Spring Boot 就幫咱們內置了 Servlet 容器,默認使用的是 Tomcat,一樣也支持修改,好比可使用 jetty、Undertow 等。java

由於內置了啓動容器,應用程序能夠直接經過 Maven 命令將項目編譯成可執行的 jar 包,經過 java -jar 命令直接啓動,不須要再像之前同樣,打包成 War 包,而後部署在 Tomcat 中。react

那麼:你知道內置的 Tomcat 在 Spring Boot 中是怎麼啓動的嗎?web

從啓動入口分析

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

本文環境:Spring Boot:2.2.2.RELEASEtomcat

@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 對象,咱們繼續跟蹤這個 run() 方法:網絡

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

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

public ConfigurableApplicationContext run(String... args) {
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    ConfigurableApplicationContext context = null;
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    /*
     * 一、配置屬性
     * 設置系統屬性 java.awt.headless,爲 true 則啓用headless模式
     * headless模式是應用的一種配置模式,在服務器缺乏顯示設備、鍵盤、鼠標等外設的狀況下可使用該模式
     * 好比咱們使用的Linux服務器就是缺乏前述的這些設備,可是又須要使用這些設備提供的能力
     */
    configureHeadlessProperty();
    /*
     * 二、獲取監聽器,發佈應用開始啓動事件
     * 經過SpringFactoriesLoader檢索META-INF/spring.factories,
     * 找到聲明的全部SpringApplicationRunListener的實現類並將其實例化,
     * 以後逐個調用其started()方法,廣播SpringBoot要開始執行了
     */
    SpringApplicationRunListeners listeners = getRunListeners(args);
    /* 發佈應用開始啓動事件 */
    listeners.starting();
    try {
        /* 三、初始化參數 */
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                args);
        /*
         * 四、配置環境,輸出banner
         * 建立並配置當前SpringBoot應用將要使用的Environment(包括配置要使用的PropertySource以及Profile),
         * 並遍歷調用全部的SpringApplicationRunListener的environmentPrepared()方法,廣播Environment準備完畢。
         */
        ConfigurableEnvironment environment = prepareEnvironment(listeners,
                applicationArguments);
        configureIgnoreBeanInfo(environment);
        /* 打印banner,若是在resources目錄下建立了咱們本身的banner就會進行打印,不然默認使用spring的 */
        Banner printedBanner = printBanner(environment);
        /* 五、建立應用上下文 */
        context = createApplicationContext();
        /* 經過SpringFactoriesLoader檢索META-INF/spring.factories,獲取並實例化異常分析器。 */
        exceptionReporters = getSpringFactoriesInstances(
                SpringBootExceptionReporter.class,
                new Class[] { ConfigurableApplicationContext.class }, context);
        /*
         * 六、預處理上下文
         * 爲ApplicationContext加載environment,以後逐個執行ApplicationContextInitializer的initialize()方法來進一步封裝ApplicationContext,
         * 並調用全部的SpringApplicationRunListener的contextPrepared()方法,【EventPublishingRunListener只提供了一個空的contextPrepared()方法】,
         * 以後初始化IoC容器,並調用SpringApplicationRunListener的contextLoaded()方法,廣播ApplicationContext的IoC加載完成,
         * 這裏就包括經過@EnableAutoConfiguration導入的各類自動配置類。
         */
        prepareContext(context, environment, listeners, applicationArguments,
                printedBanner);
        /* 七、刷新上下文 */
        refreshContext(context);
        /* 八、再一次刷新上下文,實際上是空方法,多是爲了後續擴展。 */
        afterRefresh(context, applicationArguments);
        stopWatch.stop();
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass)
                    .logStarted(getApplicationLog(), stopWatch);
        }
        /* 九、發佈應用已經啓動的事件 */
        listeners.started(context);
        /*
         * 遍歷全部註冊的ApplicationRunner和CommandLineRunner,並執行其run()方法。
         * 咱們能夠實現本身的ApplicationRunner或者CommandLineRunner,來對SpringBoot的啓動過程進行擴展。
         */
        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;
}

若是以爲這個方法看的雲裏霧裏的,那麼能夠歸納爲以下幾步:app

  1. 配置系統屬性
  2. 獲取監聽,發佈應用開始啓動時間
  3. 初始化輸入參數
  4. 配置環境,輸出banner
  5. 建立上下文
  6. 預處理上下文
  7. 刷新上下文
  8. 再次刷新上下文
  9. 發佈應用已經啓動事件
  10. 發佈應用啓動完成事件

而咱們 Tomcat 的啓動主要是在第5步建立上下文,以及第 7步刷新上下文實現的。less

建立上下文

第5步中,建立上下文主要是調用的 createApplicationContext() 方法:

protected ConfigurableApplicationContext createApplicationContext() {
  /** 1. 根據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 語句,根據 webApplicationType 的類型來建立不一樣的 ApplicationContext:

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

由於咱們的應用是 Web 類型,因此實例化的是 AnnotationConfigServletWebServerApplicationContext,以下是該類的關係圖(由Diagram截圖):

咱們在上圖的底部觸發,能夠看到 AnnotationConfigServletWebServerApplicationContext > ServletWebServerApplicationContext > ... AbstractApplicationContext(>表示繼承),總之,最終繼承到了 AbstractApplicationContext,這個類是 ApplicationContext 的抽象實現類,該抽象類實現應用上下文的一些具體操做。

至此,並無看到 Tomcat 的相關代碼,其實這一步主要就是「建立上下文」,拿到「上下文」以後須要傳遞給「刷新上下文」,交由刷新上下文建立 Web 服務。

刷新上下文

第7步中,刷新上下文時調用的 refreshContext(context) 方法,其中 context 就是第5步建立的上下文,方法以下:

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,經由 refresh() 方法強轉成父類 AbstractApplicationContext,具體調用過程以下:

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() 方法是調用其子類實現的,也就是 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 建立了一個 WebServer,咱們繼續來看這個 ServletWebServerFactory:

ServletWebServerFactory 有4個實現類,其中咱們最經常使用的是 TomcatServletWebServerFactory和JettyServletWebServerFactory,而默認的 Web 環境就是 TomcatServletWebServerFactory。

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

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

public WebServer getWebServer(ServletContextInitializer... initializers) {
    if (this.disableMBeanRegistry) {
        Registry.disableRegistry();
    }
  /** 一、建立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);
    /** 二、給建立好的tomcat設置鏈接器connector **/
    tomcat.setConnector(connector);
    /** 設置不自動部署 **/
    tomcat.getHost().setAutoDeploy(false);
    /** 三、配置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);
    /** 將建立好的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() 這個方法建立了 Tomcat 對象,而且作了兩件重要的事情:

  1. 把鏈接器 Connector 對象添加到 Tomcat 中;
  2. 配置容器引擎,configureEngine(tomcat.getEngine());

首先說一下這個 Connector 鏈接器,Tomcat 有兩個核心功能:

  1. 處理 Socket 鏈接,負責網絡字節流與 Request 和 Response 對象的轉化。
  2. 加載和管理 Servlet,以及具體處理 Request 請求。

針對這兩個功能,Tomcat 設計了兩個核心組件來分別完成這兩件事,即:鏈接器(Connector)和容器(Container)。

整個過程大體就是:Connector 鏈接器接收鏈接請求,建立Request和Response對象用於和請求端交換數據,而後分配線程讓Engine(也就是Servlet容器)來處理這個請求,並把產生的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) 是指定背景線程的執行間隔,例如背景線程會在每隔多長時間後判斷session是否失效之類。

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

return this.getTomcatWebServer(tomcat);

經過 getTomcatWebServer() 方法,繼續下沉:

/**
 * 構造函數實例化 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 時常常看到打印這句話 **/
    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 啓動過程就很清晰了,總結一下。

總結

SpringBoot的啓動主要是經過實例化SpringApplication來啓動的,啓動過程主要作了以下幾件事情:

  1. 配置系統屬性
  2. 獲取監聽,發佈應用開始啓動時間
  3. 初始化輸入參數
  4. 配置環境,輸出banner
  5. 建立上下文
  6. 預處理上下文
  7. 刷新上下文
  8. 再次刷新上下文
  9. 發佈應用已經啓動事件
  10. 發佈應用啓動完成事件

而啓動 Tomcat 是在第7步 刷新上下文 這一步。

從整個流轉過程當中咱們知道了 Tomcat 的啓動主要是實例化兩個組件:Connector、Container。

  • Spring Boot 建立 Tomcat 時,會先建立一個根上下文,將 WebApplicationContext 傳給 Tomcat;

  • 啓動 Web 容器,須要調用 getWebserver(),由於默認的 Web 環境就是 TomcatServletWebServerFactory,因此會建立 Tomcat 的 Webserver,這裏會把根上下文做爲參數給 TomcatServletWebServerFactory 的 getWebServer();

  • 啓動 Tomcat,調用 Tomcat 中 Host、Engine 的啓動方法。

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

相關文章
相關標籤/搜索