• SpringBoot源码解析(十九)启动内置tomcat


    SpringBoot最初开始流行的时候,一个很重要的区别就是,我们不需要单独配置tomcat了,在传统的SSM项目中,我们要为项目指定tomcat目录,开发完上线的时候,要把项目打成一个war包,部署到tomcat中,而SpringBoot已经默认为我们的web项目内置了一个tomcat

    这个内置tomcat的启动流程,也在容器的refresh方法中,其中有个onRefresh的方法调用
    在这里插入图片描述

    在个方法定义在父类AbstractApplicationContext中,是一个空方法

        protected void onRefresh() throws BeansException {
        }
    
    • 1
    • 2

    在传统比较常用的Spring容器中,比如AnnotationConfigApplicationContext,并没有重写这个方法,依然是一个空实现,而SpringBoot的容器重写了这个方法,在其中完成了内置tomcat的启动

    我们先点进去看下有哪些子类对这个方法做了重写
    在这里插入图片描述

    之前分析过,默认情况下,SpringBoot启动的容器是AnnotationConfigServletWebServerApplicationContext,而这里的ServletWebServerApplicationContext就是其父类,所以我们进入这个类中查看onRefresh方法的实现

        protected void onRefresh() {
            super.onRefresh();
    
            try {
                this.createWebServer();
            } catch (Throwable var2) {
                throw new ApplicationContextException("Unable to start web server", var2);
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    先调用了父类的onRefresh方法

        protected void onRefresh() {
            this.themeSource = UiApplicationContextUtils.initThemeSource(this);
        }
    
    • 1
    • 2
    • 3

    initThemeSource主要是初始化了web项目的UI主题,它检查了容器中是否存在名为themeSource的Bean,如果不存在的话,就初始化一个ResourceBundleThemeSource
    如果项目用了一些模板引擎像thymeleaf或freemarker等,可以通过设置ThemeSource,配合视图解析器调整UI风格,这个东西基本是没什么人使用的,毕竟现在前端的UI框架太多了,而且大多项目都做了前后分离,Spring自带的这种针对UI的组件,基本可以忽略不计了,所以我们主要关注下面的createWebServer方法

        private void createWebServer() {
            WebServer webServer = this.webServer;
            ServletContext servletContext = this.getServletContext();
            if (webServer == null && servletContext == null) {
                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();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    正常情况代码初始化走到这里,servlet容器还是空的,会进入第一个分支,尝试获取一个ServletWebServerFactory,然后由它来创建一个servlet容器
    进入getWebServerFactory方法

        protected ServletWebServerFactory getWebServerFactory() {
            String[] beanNames = this.getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class);
            if (beanNames.length == 0) {
                throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to missing ServletWebServerFactory bean.");
            } else if (beanNames.length > 1) {
                throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to multiple ServletWebServerFactory beans : " + StringUtils.arrayToCommaDelimitedString(beanNames));
            } else {
                return (ServletWebServerFactory)this.getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    它尝试从容器中获取ServletWebServerFactory类型的Bean,当我们在pom文件里引入了spring-boot-starter-web依赖后,这里就可以找到一个TomcatServletWebServerFactory,至于这个bean怎么来的,我们后面再分析
    在这里插入图片描述

    同时在这个bean初始化的时候,它会被一个名为WebServerFactoryCustomizerBeanPostProcessor的BeanPostProcessor处理,将配置文件中的server.port绑定给它,也就是在这个Bean初始化的时候就完成了端口绑定

    有了这个webServerFactory后,继续调用其getWebServer方法
    可以看到这个ServletWebServerFactory是一个接口,除了tomcat的实现外,还有其他常用Servet容器如jetty的实现,可以调整pom依赖切换为jetty容器,默认情况下SpringBoot使用的是tomcat容器,正如上面的beanNames中只有一个tomcatServletWebServerFactory
    在这里插入图片描述
    所以进入tomcatServletWebServerFactory这个实现类中

        public WebServer getWebServer(ServletContextInitializer... initializers) {
            Tomcat tomcat = new Tomcat();
            File baseDir = this.baseDirectory != null ? this.baseDirectory : this.createTempDir("tomcat");
            tomcat.setBaseDir(baseDir.getAbsolutePath());
            Connector connector = new Connector(this.protocol);
            tomcat.getService().addConnector(connector);
            this.customizeConnector(connector);
            tomcat.setConnector(connector);
            tomcat.getHost().setAutoDeploy(false);
            this.configureEngine(tomcat.getEngine());
            Iterator var5 = this.additionalTomcatConnectors.iterator();
    
            while(var5.hasNext()) {
                Connector additionalConnector = (Connector)var5.next();
                tomcat.getService().addConnector(additionalConnector);
            }
    
            this.prepareContext(tomcat.getHost(), initializers);
            return this.getTomcatWebServer(tomcat);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    可以看到这个方法new了一个Tocmat,并完成了一些初始化配置
    最后调用getTomcatWebServer,创建webServer实例

        protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
            return new TomcatWebServer(tomcat, this.getPort() >= 0);
        }
    
    • 1
    • 2
    • 3

    进入构造方法

        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();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    将创建好的tomcat赋给webServer后,最后调用了初始化方法initialize

        private void initialize() throws WebServerException {
            logger.info("Tomcat initialized with port(s): " + this.getPortsDescription(false));
            Object var1 = this.monitor;
            synchronized(this.monitor) {
                try {
                    this.addInstanceIdToEngineName();
                    Context context = this.findContext();
                    context.addLifecycleListener((event) -> {
                        if (context.equals(event.getSource()) && "start".equals(event.getType())) {
                            this.removeServiceConnectors();
                        }
    
                    });
                    this.tomcat.start();
                    this.rethrowDeferredStartupExceptions();
    
                    try {
                        ContextBindings.bindClassLoader(context, context.getNamingToken(), this.getClass().getClassLoader());
                    } catch (NamingException var5) {
                        ;
                    }
    
                    this.startDaemonAwaitThread();
                } catch (Exception var6) {
                    this.stopSilently();
                    throw new WebServerException("Unable to start embedded Tomcat", var6);
                }
    
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    我们在这个initialize方法中,找到了一行代码:this.tomcat.start()
    也就是到这里,SpringBoot内置的tomcat就启动起来了,日志里也会打印tomcat启动的相关信息
    在这里插入图片描述

    在整个流程中,非常关键的一步就是spring要从容器中根据ServletWebServerFactory类型,找到TomcatServletWebServerFactory的实现,那么接下来,我们就分析下这个类是怎么进入容器的

    我们上篇文章分析过SpringBoot自动装配的原理了,它到classpath下的META-INF/spring.factories中找到了一系列的EnableAutoConfiguration的配置类,配合条件注解完成了装配,其中在spring-boot-autoconfigure的文件中,配置了一个类ServletWebServerFactoryAutoConfiguration
    在这里插入图片描述

    @Configuration
    @AutoConfigureOrder(-2147483648)
    @ConditionalOnClass({ServletRequest.class})
    @ConditionalOnWebApplication(
        type = Type.SERVLET
    )
    @EnableConfigurationProperties({ServerProperties.class})
    @Import({ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class, EmbeddedTomcat.class, EmbeddedJetty.class, EmbeddedUndertow.class})
    public class ServletWebServerFactoryAutoConfiguration {
    	......
    	......
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    这个类通过Import导入了EmbeddedTomcat,同时还有其他内嵌servlet容器如EmbeddedJetty,我们看下EmbeddedTomcat的实现

        @Configuration
        @ConditionalOnClass({Servlet.class, Tomcat.class, UpgradeProtocol.class})
        @ConditionalOnMissingBean(
            value = {ServletWebServerFactory.class},
            search = SearchStrategy.CURRENT
        )
        public static class EmbeddedTomcat {
            public EmbeddedTomcat() {
            }
    
            @Bean
            public TomcatServletWebServerFactory tomcatServletWebServerFactory() {
                return new TomcatServletWebServerFactory();
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    若项目中存在Tomcat的class,且此时容器中还没有注入ServletWebServerFactory,就会向容器注入一个TomcatServletWebServerFactory的实现

    一般我们SpringBoot项目要提供web服务,都会导入web的starter

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    
    • 1
    • 2
    • 3
    • 4

    我们点进去这个依赖的内部,它又引入了tomcat的starter

        <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-tomcat</artifactId>
          <version>2.1.4.RELEASE</version>
          <scope>compile</scope>
        </dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    从而将Tomcat类引入到项目中,满足了EmbeddedTomcat的条件,向容器注入了TomcatServletWebServerFactory,然后就像上面分析的流程,在spring容器refresh的过程中,启动内置的tomcat提供web服务

  • 相关阅读:
    Au 入门系列之一:认识 Audition
    2023年中国喷头受益于技术创新,功能不断提升[图]
    Rust的模式匹配
    Vue自动生成二维码并可下载二维码
    设计模式-门面模式
    数据结构-散列表
    shell脚本知识点梳理
    axios从入门到源码分析之axios 源码分析(二)
    【踩坑系列】mysql 修改root密码失败
    Ajax——取消请求
  • 原文地址:https://blog.csdn.net/m0_37298252/article/details/125944196