SpringBoot內(nèi)置tomcat啟動(dòng)原理

SpringBoot為我提供了很多便捷的功能,讓我們可以很快搭建一個(gè)服務(wù)。
內(nèi)置tomcat就是其中一項(xiàng),他讓我們省去了搭建tomcat容器,生成war,部署,啟動(dòng)tomcat。
那么內(nèi)置tomcat是如何實(shí)現(xiàn)的呢。
從源碼角度我們可以分析一下。

內(nèi)置tomcat啟動(dòng)原理

  1. 在SpringApplication實(shí)例化階段


    image.png

如圖中圈出來的這行代碼,根據(jù)路徑推斷應(yīng)用類型

    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;
    }
  • 如果存在org.springframework.web.reactive.DispatcherHandler 并且不存在org.springframework.web.servlet.DispatcherServlet和org.glassfish.jersey.servlet.ServletContainer 則為REACTIVE項(xiàng)目,項(xiàng)目將啟動(dòng)一個(gè)嵌入式的響應(yīng)式的Web服務(wù)器;
  • 如果不包含javax.servlet.Servlet和org.springframework.web.context.ConfigurableWebApplicationContext 則為None項(xiàng)目,不會啟動(dòng)任何Web服務(wù)器
  • 其他情況都為SERVLET項(xiàng)目,將啟動(dòng)一個(gè)嵌入式servletWeb服務(wù)器
  1. run階段 創(chuàng)建ApplicationContext上下文


    image.png

    如圖中圈出來的這行代碼,創(chuàng)建一個(gè)ApplicationContext

    ApplicationContextFactory DEFAULT = (webApplicationType) -> {
        try {
            switch (webApplicationType) {
            case SERVLET:
                return new AnnotationConfigServletWebServerApplicationContext();
            case REACTIVE:
                return new AnnotationConfigReactiveWebServerApplicationContext();
            default:
                return new AnnotationConfigApplicationContext();
            }
        }
        catch (Exception ex) {
            throw new IllegalStateException("Unable create a default ApplicationContext instance, "
                    + "you may need a custom ApplicationContextFactory", ex);
        }
    };

所以這里會創(chuàng)建一個(gè)AnnotationConfigServletWebServerApplicationContext

  1. refresh階段


    image.png

如圖中圈出來的這行代碼,會執(zhí)行AbstractApplicationContext.refresh()方法


image.png

這里會執(zhí)行onRefresh方法,這個(gè)方法是一個(gè)模板方法,交由子類實(shí)現(xiàn),而我們這里的子類是AnnotationConfigServletWebServerApplicationContext。
它繼承了ServletWebServerApplicationContext,
所以這里會執(zhí)行ServletWebServerApplicationContext.onRefresh();

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


    private void createWebServer() {
        WebServer webServer = this.webServer;
        ServletContext servletContext = getServletContext();
        if (webServer == null && servletContext == null) {
            StartupStep createWebServer = this.getApplicationStartup().start("spring.boot.webserver.create");
            ServletWebServerFactory factory = getWebServerFactory();
            createWebServer.tag("factory", factory.getClass().toString());
            this.webServer = factory.getWebServer(getSelfInitializer());
            createWebServer.end();
            getBeanFactory().registerSingleton("webServerGracefulShutdown",
                    new WebServerGracefulShutdownLifecycle(this.webServer));
            getBeanFactory().registerSingleton("webServerStartStop",
                    new WebServerStartStopLifecycle(this, this.webServer));
        }
        else if (servletContext != null) {
            try {
                getSelfInitializer().onStartup(servletContext);
            }
            catch (ServletException ex) {
                throw new ApplicationContextException("Cannot initialize servlet context", ex);
            }
        }
        initPropertySources();
    }

這里創(chuàng)建了webServer,但是還沒有啟動(dòng)tomcat,這里是通過ServletWebServerFactory創(chuàng)建,那么接著看下ServletWebServerFactory。


image.png

可以看到ServletWebServerFactory的實(shí)現(xiàn)類有三種

  • UndertowServletWebServerFactory 使用 undertow作為web服務(wù)器

Undertow是一種基于Java的靈活,快速的Web服務(wù)器,它基于J2SE 新輸入輸出(NIO) API。 Undertow是圍繞基于組合的體系結(jié)構(gòu)設(shè)計(jì)的,該體系結(jié)構(gòu)允許您通過組合稱為處理程序的小型單個(gè)組件來構(gòu)建功能全面的Web服務(wù)器。 這些處理程序鏈接在一起以形成功能齊全的Java EE servlet 3.1容器或嵌入在代碼中的更簡單的HTTP Process處理程序。

  • JettyServletWebServerFactory 使用jetty作為web服務(wù)器

Jetty 是一個(gè)開源的servlet容器,它為基于Java的web容器,例如JSP和servlet提供運(yùn)行環(huán)境。Jetty是使用Java語言編寫的,它的API以一組JAR包的形式發(fā)布。開發(fā)人員可以將Jetty容器實(shí)例化成一個(gè)對象,可以迅速為一些獨(dú)立運(yùn)行(stand-alone)的Java應(yīng)用提供網(wǎng)絡(luò)和web連接。

  • TomcatServletWebServerFactory 使用tomcat作為web服務(wù)器

Tomcat 服務(wù)器是一個(gè)免費(fèi)的開放源代碼的Web 應(yīng)用服務(wù)器,屬于輕量級應(yīng)用服務(wù)器,在中小型系統(tǒng)和并發(fā)訪問用戶不是很多的場合下被普遍使用,是開發(fā)和調(diào)試JSP 程序的首選。對于一個(gè)初學(xué)者來說,可以這樣認(rèn)為,當(dāng)在一臺機(jī)器上配置好Apache 服務(wù)器,可利用它響應(yīng)HTML標(biāo)準(zhǔn)通用標(biāo)記語言下的一個(gè)應(yīng)用)頁面的訪問請求。實(shí)際上Tomcat是Apache 服務(wù)器的擴(kuò)展,但運(yùn)行時(shí)它是獨(dú)立運(yùn)行的,所以當(dāng)你運(yùn)行tomcat 時(shí),它實(shí)際上作為一個(gè)與Apache 獨(dú)立的進(jìn)程單獨(dú)運(yùn)行的。

  1. 這里使用getWebServerFactory()方法獲取一個(gè)ServletWebServerFactory
    protected ServletWebServerFactory getWebServerFactory() {
        // Use bean names so that we don't consider the hierarchy
        String[] beanNames = getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class);
        if (beanNames.length == 0) {
            throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to missing "
                    + "ServletWebServerFactory bean.");
        }
        if (beanNames.length > 1) {
            throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to multiple "
                    + "ServletWebServerFactory beans : " + StringUtils.arrayToCommaDelimitedString(beanNames));
        }
        return getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);
    }

這里會獲取到TomcatServletWebServerFactory,而TomcatServletWebServerFactory類是通過ServletWebServerFactoryConfiguration配置類加載的。

@Configuration(proxyBeanMethods = false)
class ServletWebServerFactoryConfiguration {

    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
    @ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
    static class EmbeddedTomcat {

        @Bean
        TomcatServletWebServerFactory tomcatServletWebServerFactory(
                ObjectProvider<TomcatConnectorCustomizer> connectorCustomizers,
                ObjectProvider<TomcatContextCustomizer> contextCustomizers,
                ObjectProvider<TomcatProtocolHandlerCustomizer<?>> protocolHandlerCustomizers) {
            TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
            factory.getTomcatConnectorCustomizers()
                    .addAll(connectorCustomizers.orderedStream().collect(Collectors.toList()));
            factory.getTomcatContextCustomizers()
                    .addAll(contextCustomizers.orderedStream().collect(Collectors.toList()));
            factory.getTomcatProtocolHandlerCustomizers()
                    .addAll(protocolHandlerCustomizers.orderedStream().collect(Collectors.toList()));
            return factory;
        }

    }

    /**
     * Nested configuration if Jetty is being used.
     */
    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass({ Servlet.class, Server.class, Loader.class, WebAppContext.class })
    @ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
    static class EmbeddedJetty {

        @Bean
        JettyServletWebServerFactory JettyServletWebServerFactory(
                ObjectProvider<JettyServerCustomizer> serverCustomizers) {
            JettyServletWebServerFactory factory = new JettyServletWebServerFactory();
            factory.getServerCustomizers().addAll(serverCustomizers.orderedStream().collect(Collectors.toList()));
            return factory;
        }

    }

    /**
     * Nested configuration if Undertow is being used.
     */
    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass({ Servlet.class, Undertow.class, SslClientAuthMode.class })
    @ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
    static class EmbeddedUndertow {

        @Bean
        UndertowServletWebServerFactory undertowServletWebServerFactory(
                ObjectProvider<UndertowDeploymentInfoCustomizer> deploymentInfoCustomizers,
                ObjectProvider<UndertowBuilderCustomizer> builderCustomizers) {
            UndertowServletWebServerFactory factory = new UndertowServletWebServerFactory();
            factory.getDeploymentInfoCustomizers()
                    .addAll(deploymentInfoCustomizers.orderedStream().collect(Collectors.toList()));
            factory.getBuilderCustomizers().addAll(builderCustomizers.orderedStream().collect(Collectors.toList()));
            return factory;
        }

        @Bean
        UndertowServletWebServerFactoryCustomizer undertowServletWebServerFactoryCustomizer(
                ServerProperties serverProperties) {
            return new UndertowServletWebServerFactoryCustomizer(serverProperties);
        }

    }

}

可以看到,在@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })條件命中后會加載TomcatWebServerFactoryCustomizer。
而Tomcat.class和UpgradeProtocol.class是由spring-boot-starter-web引入的spring-boot-starter-tomcat中加載的依賴。

所以tomcat是默認(rèn)的web服務(wù)器,這個(gè)是寫死在spring-boot-starter-web包中的。
所以如果需要切換web服務(wù)器,那么需要從spring-boot-starter-web包中exclude掉spring-boot-starter-tomcat依賴。

ServletWebServerFactoryConfiguration并不是直接配置在spring.factories文件中的,而是由ServletWebServerFactoryAutoConfiguration加載的。

@Configuration(proxyBeanMethods = false)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(ServletRequest.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ServerProperties.class)
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
        ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
        ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
        ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration {
....
}

而ServletWebServerFactoryAutoConfiguration是配置在spring.factories文件中的,由SpringBoot自動(dòng)裝配。

  1. 執(zhí)行TomcatServletWebServerFactory.getWebServer()
    @Override
    public WebServer getWebServer(ServletContextInitializer... initializers) {
        if (this.disableMBeanRegistry) {
            Registry.disableRegistry();
        }
        Tomcat tomcat = new Tomcat();
        File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
        tomcat.setBaseDir(baseDir.getAbsolutePath());
        Connector connector = new Connector(this.protocol);
        connector.setThrowOnFailure(true);
        tomcat.getService().addConnector(connector);
        customizeConnector(connector);
        tomcat.setConnector(connector);
        tomcat.getHost().setAutoDeploy(false);
        configureEngine(tomcat.getEngine());
        for (Connector additionalConnector : this.additionalTomcatConnectors) {
            tomcat.getService().addConnector(additionalConnector);
        }
        prepareContext(tomcat.getHost(), initializers);
        return getTomcatWebServer(tomcat);
    }
  1. new TomcatWebServer()
    public TomcatWebServer(Tomcat tomcat, boolean autoStart, Shutdown shutdown) {
        Assert.notNull(tomcat, "Tomcat Server must not be null");
        this.tomcat = tomcat;
        this.autoStart = autoStart;
        this.gracefulShutdown = (shutdown == Shutdown.GRACEFUL) ? new GracefulShutdown(tomcat) : null;
        initialize();
    }

    private void initialize() throws WebServerException {
        logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));
        synchronized (this.monitor) {
            try {
                addInstanceIdToEngineName();

                Context context = findContext();
                context.addLifecycleListener((event) -> {
                    if (context.equals(event.getSource()) && Lifecycle.START_EVENT.equals(event.getType())) {
                        // Remove service connectors so that protocol binding doesn't
                        // happen when the service is started.
                        removeServiceConnectors();
                    }
                });

                // Start the server to trigger initialization listeners
                this.tomcat.start();

                // We can re-throw failure exception directly in the main thread
                rethrowDeferredStartupExceptions();

                try {
                    ContextBindings.bindClassLoader(context, context.getNamingToken(), getClass().getClassLoader());
                }
                catch (NamingException ex) {
                    // Naming is not enabled. Continue
                }

                // Unlike Jetty, all Tomcat threads are daemon threads. We create a
                // blocking non-daemon to stop immediate shutdown
                startDaemonAwaitThread();
            }
            catch (Exception ex) {
                stopSilently();
                destroySilently();
                throw new WebServerException("Unable to start embedded Tomcat", ex);
            }
        }
    }

所以在onRefresh方法中就進(jìn)行創(chuàng)建,并啟動(dòng)了。

切換其他web服務(wù)器

Jetty

修改pom.xml

<dependencies>  
        <dependency>  
            <groupId>org.springframework.boot</groupId>  
            <artifactId>spring-boot-starter-web</artifactId>  
            <exclusions>  
                <exclusion>  
                    <groupId>org.springframework.boot</groupId>  
                    <artifactId>spring-boot-starter-tomcat</artifactId>  
                </exclusion>  
            </exclusions>  
        </dependency>  
  
        <!-- Jetty適合長連接應(yīng)用,就是聊天類的長連接 -->  
        <!-- 使用Jetty,需要在spring-boot-starter-web排除spring-boot-starter-tomcat,因?yàn)镾pringBoot默認(rèn)使用tomcat -->  
        <dependency>  
            <groupId>org.springframework.boot</groupId>  
            <artifactId>spring-boot-starter-jetty</artifactId>  
        </dependency>  
        <dependency>  
            <groupId>org.springframework.boot</groupId>  
            <artifactId>spring-boot-starter-test</artifactId>  
            <scope>test</scope>  
        </dependency>  
</dependencies>  

undertow

修改pom.xml

<dependencies>  
        <dependency>  
            <groupId>org.springframework.boot</groupId>  
            <artifactId>spring-boot-starter-web</artifactId>  
            <exclusions>  
                <exclusion>  
                    <groupId>org.springframework.boot</groupId>  
                    <artifactId>spring-boot-starter-tomcat</artifactId>  
                </exclusion>  
            </exclusions>  
        </dependency>  
          
        <!-- undertow不支持jsp -->  
        <!-- 使用undertow,需要在spring-boot-starter-web排除spring-boot-starter-tomcat,因?yàn)镾pringBoot默認(rèn)使用tomcat -->  
        <dependency>  
            <groupId>org.springframework.boot</groupId>  
            <artifactId>spring-boot-starter-undertow</artifactId>  
        </dependency>  
          
        <dependency>  
            <groupId>org.springframework.boot</groupId>  
            <artifactId>spring-boot-starter-test</artifactId>  
            <scope>test</scope>  
        </dependency>  
</dependencies>  

SpringBoot項(xiàng)目打war包,部署外部Tomcat

修改pom.xml

groupId>com.example</groupId>
<artifactId>application</artifactId>
<version>0.0.1-SNAPSHOT</version>
<!--默認(rèn)為jar方式-->
<!--<packaging>jar</packaging>-->
<!--改為war方式-->
<packaging>war</packaging>
...
<dependencies>  
        <dependency>  
            <groupId>org.springframework.boot</groupId>  
            <artifactId>spring-boot-starter-web</artifactId>  
            <exclusions>  
                <exclusion>  
                    <groupId>org.springframework.boot</groupId>  
                    <artifactId>spring-boot-starter-tomcat</artifactId>  
                </exclusion>  
            </exclusions>  
        </dependency>    
</dependencies>  
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容