Tomcat源码阅读笔记一 为什么SpringBoot web应用默认使用的web容器是tomcat

101 阅读2分钟

一、启动代码

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

    @GetMapping("/a")
    public String a(){
       return "111";
    }
}

以spring boot的方式启动tomcat

二、源码分析

直接快进到refresh()方法

ServletWebServerApplicationContext类
@Override
protected void onRefresh() {
    // 调用父类的onRefresh()方法
    super.onRefresh();
    try {
       // 创建web容器 tomcat
       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 = getApplicationStartup().start("spring.boot.webserver.create");
       // 1、获取web容器工厂
       ServletWebServerFactory factory = getWebServerFactory();
       createWebServer.tag("factory", factory.getClass().toString());
       // 2、获取web容器
       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();
}

// 获取web容器工厂
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 MissingWebServerFactoryBeanException(getClass(), ServletWebServerFactory.class,
             WebApplicationType.SERVLET);
    }
    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);
}

为什么springboot web应用默认web容器是tomcat?

在createWebServer方法的第一步获取web容器工厂,在不修改任何依赖的情况下,默认返回的 TomcatServletWebServerFactory,也就是默认使用tomcat容器,那么TomcatServletWebServerFactory是怎么被注入的呢?

要从springboot的自动装配说起,我们看到,在getWebServerFactory()方法中,是返回的类型为ServletWebServerFactory的bean对象,在最新的springboot3自动装配的文件org.springframework.boot.autoconfigure.AutoConfiguration.imports中有自动装配org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration

ServletWebServerFactoryAutoConfiguration类片段
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
       ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
       ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
       ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration

@Import注解解析注入bean对象

@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().toList());
          factory.getTomcatContextCustomizers().addAll(contextCustomizers.orderedStream().toList());
          factory.getTomcatProtocolHandlerCustomizers().addAll(protocolHandlerCustomizers.orderedStream().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().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().toList());
          factory.getBuilderCustomizers().addAll(builderCustomizers.orderedStream().toList());
          return factory;
       }

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

    }

}

ServletWebServerFactoryConfiguration中的@Bean的对象就是web容器对应的ServletWebServerFactory了,EmbeddedTomcat类上面有@ConditionalOnClass@ConditionalOnMissingBean注解,@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })就是该bean被注入的条件,在Servlet,Tomcat,UpgradeProtocol三个类都能被加载的情况下才能注入,即当前类路径下要存在这些类;@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)是当前spring容器中没有ServletWebServerFactory类型的bean时才注入该bean,防止同时启动多个web容器。

spring-boot-starter-web依赖项,这里是以springboot源码启动的,所以使用的构建工具是gradle

plugins {
    id "org.springframework.boot.starter"
}

description = "Starter for building web, including RESTful, applications using Spring MVC. Uses Tomcat as the default embedded container"

dependencies {
    api(project(":spring-boot-project:spring-boot-starters:spring-boot-starter"))
    api(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-json"))
    api(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-tomcat"))
//  api(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-jetty"))
    api("org.springframework:spring-web")
    api("org.springframework:spring-webmvc")

我们看到spring-boot-starter-web默认依赖了spring-boot-starter-tomcat,所以默认使用的web容器是tomcat,要想使用别的web容器,直接更换依赖就可以了。