• 【Spring boot】整合tomcat底层原理


    本文结论

    • 源码基于spring boot2.6.6
    • 项目的pom.xml中存在spring-boot-starter-web的时候,在项目启动时候就会自动启动一个Tomcat。
    • 自动配置类ServletWebServerFactoryAutoConfiguration找到系统中的所有web容器。我们以tomcat为主。
    • 构建TomcatServletWebServerFactory的bean。
    • SpringBoot的启动过程中,会调用核心的refresh方法,内部会执行onRefresh()方法,onRefresh()方法是一个模板方法,他会执行会执行子类ServletWebServerApplicationContext的onRefresh()方法。
    • onRefresh()方法中调用getWebServer启动web容器。

    spring-boot-starter-web内部有什么?

    • 在spring-boot-starter-web这个starter中,其实内部间接的引入了spring-boot-starter-tomcat这个starter,这个spring-boot-starter-tomcat又引入了tomcat-embed-core依赖,所以只要我们项目中依赖了spring-boot-starter-web就相当于依赖了Tomcat。

    内部的starter-tomcat.png

    内部的tomcat-embed-core.png

    自动配置类:ServletWebServerFactoryAutoConfiguration

    • 在spring-boot-autoconfigure-2.6.6.jar这个包中的spring.factories文件内,配置了大量的自动配置类,其中就包括自动配置tomcat的自动配置类:ServletWebServerFactoryAutoConfiguration

    自动配置类的配置.png

    自动配置类的代码如下

    // full模式
    @Configuration(proxyBeanMethods = false)
    
    // 配置类解析顺序
    @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
    
    // 条件注解:表示项目依赖中要有ServletRequest类(server api)
    @ConditionalOnClass(ServletRequest.class)
    // 表示项目应用类型得是SpringMVC(在启动过程中获取的SpringBoot应用类型)
    @ConditionalOnWebApplication(type = Type.SERVLET)
    
    // 读取server下的配置文件
    @EnableConfigurationProperties(ServerProperties.class)
    
    // import具体的加载配置的类和具体web实现容器
    @Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
    		ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
    		ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
    		ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
    public class ServletWebServerFactoryAutoConfiguration {
    	......
    }
    
    • ServletRequest是存在于tomcat-embed-core-9.0.60.jar中的的一个类,所以@ConditionalOnClass(ServletRequest.clas s)会满足。
    • spring-boot-starter-web中,间接的引入了spring-web、spring-webmvc等依赖,所以@ConditionalOnWebApplication(type = Type.SERVLET)条件满足。
    • 上面的俩个条件都满足,所以spring回去解析这个配置类,在解析过程中会发现他import了三个类!我们重点关注EmbeddedTomcat。其他俩个的内部条件注解不满足!
    @Configuration(proxyBeanMethods = false)
    // tomcat内部的类,肯定都存在
    @ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
    
    // 程序员如果自定义了ServletWebServerFactory的Bean,那么这个Bean就不加载。
    @ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
    static class EmbeddedTomcat {
        @Bean
        TomcatServletWebServerFactory tomcatServletWebServerFactory(
            ObjectProvider connectorCustomizers,
            ObjectProvider contextCustomizers,
            ObjectProvider> protocolHandlerCustomizers) {
                TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
                // orderedStream()调用时会去Spring容器中找到TomcatConnectorCustomizer类型的Bean,默认是没有的,程序员可以自己定义。这个Bean可以设置一些tomcat的配置,比如端口、协议...
                // TomcatConnectorCustomizer:是用来配置Tomcat中的Connector组件的
                factory.getTomcatConnectorCustomizers().addAll(connectorCustomizers.orderedStream().collect(Collectors.toList()));
                // TomcatContextCustomizer:是用来配置Tomcat中的Context组件的
                factory.getTomcatContextCustomizers().addAll(contextCustomizers.orderedStream().collect(Collectors.toList()));
                // TomcatProtocolHandlerCustomizer:是用来配置Tomcat中的ProtocolHandler组件的
                factory.getTomcatProtocolHandlerCustomizers().addAll(protocolHandlerCustomizers.orderedStream().collect(Collectors.toList()));
                return factory;
            }
        }
    }
    
    • 对于另外的EmbeddedJetty和EmbeddedUndertow,逻辑类似,都是判断项目依赖中是否有Jetty和Undertow的依赖,如果有,那么对应在Spring容器中就会存在JettyServletWebServerFactory类型的Bean、或者存在UndertowServletWebServerFactory类型的Bean。

    TomcatServletWebServerFactory的作用:获取WebServer对象

    • TomcatServletWebServerFactory他实现了ServletWebServerFactory这个接口。
    • ServletWebServerFactory接口内部只有一个方法是获取WebServer对象。

    ServletWebServerFactory接口.png

    • WebServer拥有启动、停止、获取端口等方法,就会发现WebServer其实指的就是Tomcat、Jetty、Undertow。

    WebServer接口.png

    • 而TomcatServletWebServerFactory就是用来生成Tomcat所对应的WebServer对象,具体一点就是TomcatWebServer对象,并且在生成TomcatWebServer对象时会把Tomcat给启动起来。
    • 在源码中,调用TomcatServletWebServerFactory对象的getWebServer()方法时就会启动Tomcat。
    public WebServer getWebServer(ServletContextInitializer... initializers) {
        if (this.disableMBeanRegistry) {
            Registry.disableRegistry();
        }
        // 构建tomcat对象
        Tomcat tomcat = new Tomcat();
    
        // 设置相关的属性
        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);
        configureEngine(tomcat.getEngine());
        for (Connector additionalConnector : this.additionalTomcatConnectors) {
            tomcat.getService().addConnector(additionalConnector);
        }
        prepareContext(tomcat.getHost(), initializers);
    
        // 启动tomcat,这个方法内部有this.tomcat.start();
        return getTomcatWebServer(tomcat);
    }
    
    

    spring boot启动的时候启动tomcat

    • SpringBoot的启动过程中,会调用核心的refresh方法,内部会执行onRefresh()方法,onRefresh()方法是一个模板方法,他会执行会执行子类ServletWebServerApplicationContext的onRefresh()方法。
    protected void onRefresh() {
        // 模板方法,先调用它父类的,一般是空方法
        super.onRefresh();
        try {
            // 创建web容器
            createWebServer();
        }
        catch (Throwable ex) {
            throw new ApplicationContextException("Unable to start web server", ex);
        }
    }
    
    • 这个方法会调用createWebServer()方法。
    // 最核心的俩行代码
    private void createWebServer() {
        ......
        // 获取web容器,多个或者没有的时候报错
        ServletWebServerFactory factory = getWebServerFactory();
    
        // 调用这个容器的getWebServer方法,上面的启动tomcat的方法!
        this.webServer = factory.getWebServer(getSelfInitializer());
        ......
    }
    
    • getWebServerFactory控制项目组有且只能有一个web容器!
    protected ServletWebServerFactory getWebServerFactory() {
        // Use bean names so that we don't consider the hierarchy
    
        // 得到所有类型为ServletWebServerFactory的Bean。TomcatServletWebServerFactory、JettyServletWebServerFactory、UndertowServletWebServerFactory都是他得到子类!
        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));
        }
    
        // 返回唯一的一个web容器!
        return getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);
    }
    

    获取tomcat的配置

    • 自动配置类ServletWebServerFactoryAutoConfiguration上除了import三个web容器,还import了BeanPostProcessorsRegistrar。
    • BeanPostProcessorsRegistrar实现了ImportBeanDefinitionRegistrar,所以他会在spring启动的时候调用registerBeanDefinitions方法。
    • registerBeanDefinitions会注册一个Bean:webServerFactoryCustomizerBeanPostProcessor。
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        // Bean工厂,一个Aware回调进行赋值
        if (this.beanFactory == null) {
            return;
        }
        // 注册webServerFactoryCustomizerBeanPostProcessor这个Bean。
        registerSyntheticBeanIfMissing(registry, "webServerFactoryCustomizerBeanPostProcessor",
            WebServerFactoryCustomizerBeanPostProcessor.class,
            WebServerFactoryCustomizerBeanPostProcessor::new);
    
        // 注册errorPageRegistrarBeanPostProcessor
        registerSyntheticBeanIfMissing(registry, "errorPageRegistrarBeanPostProcessor",
            ErrorPageRegistrarBeanPostProcessor.class, ErrorPageRegistrarBeanPostProcessor::new);
    }
    
    • webServerFactoryCustomizerBeanPostProcessor实现了BeanPostProcessor,所以他会在启动的时候调用postProcessBeforeInitialization方法。
    private void postProcessBeforeInitialization(WebServerFactory webServerFactory) {
        // 找到WebServerFactoryCustomizer的Bean
        LambdaSafe.callbacks(WebServerFactoryCustomizer.class, getCustomizers(), webServerFactory)
            // 标记日志用的类
            .withLogger(WebServerFactoryCustomizerBeanPostProcessor.class)
            // 调用customize方法,传入webServerFactory
            .invoke((customizer) -> customizer.customize(webServerFactory));
    }
    
    • postProcessBeforeInitialization中会调用WebServerFactoryCustomizer类customize方法,在系统中的唯一实现:ServletWebServerFactoryCustomizer的customize方法。
    • customize把配置中的内容设置到ConfigurableServletWebServerFactory对象中。他的实现TomcatServletWebServerFactory在启动的时候就会有值!
    @Override
    public void customize(ConfigurableServletWebServerFactory factory) {
        PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
        map.from(this.serverProperties::getPort).to(factory::setPort);
        map.from(this.serverProperties::getAddress).to(factory::setAddress);
        map.from(this.serverProperties.getServlet()::getContextPath).to(factory::setContextPath);
        map.from(this.serverProperties.getServlet()::getApplicationDisplayName).to(factory::setDisplayName);
        map.from(this.serverProperties.getServlet()::isRegisterDefaultServlet).to(factory::setRegisterDefaultServlet);
        map.from(this.serverProperties.getServlet()::getSession).to(factory::setSession);
        map.from(this.serverProperties::getSsl).to(factory::setSsl);
        map.from(this.serverProperties.getServlet()::getJsp).to(factory::setJsp);
        map.from(this.serverProperties::getCompression).to(factory::setCompression);
        map.from(this.serverProperties::getHttp2).to(factory::setHttp2);
        map.from(this.serverProperties::getServerHeader).to(factory::setServerHeader);
        map.from(this.serverProperties.getServlet()::getContextParameters).to(factory::setInitParameters);
        map.from(this.serverProperties.getShutdown()).to(factory::setShutdown);
        for (WebListenerRegistrar registrar : this.webListenerRegistrars) {
            registrar.register(factory);
        }
        if (!CollectionUtils.isEmpty(this.cookieSameSiteSuppliers)) {
            factory.setCookieSameSiteSuppliers(this.cookieSameSiteSuppliers);
        }
    }
    

    ServletWebServerFactoryCustomizer这个Bean是哪里的?

    • 在我们自动配置类ServletWebServerFactoryAutoConfiguration中定义。
    @Bean
    public ServletWebServerFactoryCustomizer servletWebServerFactoryCustomizer(ServerProperties serverProperties, ObjectProvider webListenerRegistrars, ObjectProvider cookieSameSiteSuppliers) {
        return new ServletWebServerFactoryCustomizer(serverProperties,webListenerRegistrars.orderedStream().collect(Collectors.toList()),cookieSameSiteSuppliers.orderedStream().collect(Collectors.toList()));
    }
    
    

    结束语

    • 你的点赞是我提高文章质量最大的动力!!!
    • 获取更多本文的前置知识文章,以及新的有价值的文章,让我们一起成为架构师!
    • 目前已经完成了并发编程、MySQL、spring源码、Mybatis的源码。可以在公众号下方菜单点击查看之前的文章!
    • 这个公众号的所有技术点,会分析的很深入!
    • 这个公众号,无广告!!!
      作者公众号.jpg
  • 相关阅读:
    WPF项目开发-按钮的测试项目和业务项目场景用法对比和区别
    AKHQ Nomad 部署方案
    MYSQL中LIMIT语句(限制语句)
    03_nodejd_npm install报错
    【Java】集合与顶级接口Collection
    Biwen.QuickApi代码生成器功能上线
    dependent '..\fjsp\customtableview.h' does not exist.
    深度学习之wandb的基本使用
    前端为什么不能直接连数据库
    华为云云耀云服务器L实例评测|Elasticsearch的Docker版本的安装和参数设置 & 端口开放和浏览器访问
  • 原文地址:https://www.cnblogs.com/zfcq/p/16790236.html