美文网首页
嵌入式Servlet容器

嵌入式Servlet容器

作者: bit_拳倾天下 | 来源:发表于2021-03-15 22:38 被阅读0次

SpringBoot 最显著的特点之一,就是 web 项目不用打成 war 包,放在自己安装的 tomcat 中运行,而是直接打成 jar 包,直接用 java -jar 运行即可。在项目的 jar 包中可以看到有关 tomcat 的包,Spring Boot 自动配置了 tomcat,并且在启动项目时启动容器。

1. 原理&源码

容器工厂自动配置类源码:

容器工厂,顾名思义,就是用来生产容器的类

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.springframework.boot.autoconfigure.web.servlet;

@Configuration
class ServletWebServerFactoryConfiguration {
    ServletWebServerFactoryConfiguration() {
    }

    @Configuration
    @ConditionalOnClass({Servlet.class, Undertow.class, SslClientAuthMode.class})
    @ConditionalOnMissingBean(
        value = {ServletWebServerFactory.class},
        search = SearchStrategy.CURRENT
    )
    public static class EmbeddedUndertow {
        public EmbeddedUndertow() {
        }

        @Bean
        public UndertowServletWebServerFactory undertowServletWebServerFactory() {
            return new UndertowServletWebServerFactory();
        }
    }

    @Configuration
    @ConditionalOnClass({Servlet.class, Server.class, Loader.class, WebAppContext.class})
    @ConditionalOnMissingBean(
        value = {ServletWebServerFactory.class},
        search = SearchStrategy.CURRENT
    )
    public static class EmbeddedJetty {
        public EmbeddedJetty() {
        }

        @Bean
        public JettyServletWebServerFactory JettyServletWebServerFactory() {
            return new JettyServletWebServerFactory();
        }
    }

    @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();
        }
    }
}

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.springframework.boot.autoconfigure.web.servlet;

@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 {
    public ServletWebServerFactoryAutoConfiguration() {
    }

    @Bean
    public ServletWebServerFactoryCustomizer servletWebServerFactoryCustomizer(ServerProperties serverProperties) {
        return new ServletWebServerFactoryCustomizer(serverProperties);
    }

    @Bean
    @ConditionalOnClass(
        name = {"org.apache.catalina.startup.Tomcat"}
    )
    public TomcatServletWebServerFactoryCustomizer tomcatServletWebServerFactoryCustomizer(ServerProperties serverProperties) {
        return new TomcatServletWebServerFactoryCustomizer(serverProperties);
    }

    public static class BeanPostProcessorsRegistrar implements ImportBeanDefinitionRegistrar, BeanFactoryAware {
        private ConfigurableListableBeanFactory beanFactory;

        public BeanPostProcessorsRegistrar() {
        }

        public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
            if (beanFactory instanceof ConfigurableListableBeanFactory) {
                this.beanFactory = (ConfigurableListableBeanFactory)beanFactory;
            }

        }

        public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
            if (this.beanFactory != null) {
                this.registerSyntheticBeanIfMissing(registry, "webServerFactoryCustomizerBeanPostProcessor", WebServerFactoryCustomizerBeanPostProcessor.class);
                this.registerSyntheticBeanIfMissing(registry, "errorPageRegistrarBeanPostProcessor", ErrorPageRegistrarBeanPostProcessor.class);
            }
        }

        private void registerSyntheticBeanIfMissing(BeanDefinitionRegistry registry, String name, Class<?> beanClass) {
            if (ObjectUtils.isEmpty(this.beanFactory.getBeanNamesForType(beanClass, true, false))) {
                RootBeanDefinition beanDefinition = new RootBeanDefinition(beanClass);
                beanDefinition.setSynthetic(true);
                registry.registerBeanDefinition(name, beanDefinition);
            }

        }
    }
}

配置文件中又有三个内部配置类,分别对应 undertow, jetty, tomcat三种容器,通过 @Import 导入,并且利用 @ConditionalOnClass, @ConditionalOnMissingBean 进行条件装配。总的来说,实现自动配置有两个前提条件:

  1. 引入了对应的依赖,这样才能找到相应的类
  2. 没有自定义容器工厂

spring-boot-starter-web 中已经默认引入了 tomcat 依赖(并且没有其他几种容器的依赖),这就满足了第一条;而且一般情况下也不会自定义容器工厂,所以容器中默认会注入 tomcatServletWebServerFactory,也就是 tomcat 容器工厂。
@EnableConfigurationProperties({ServerProperties.class})则是将配置文件中相关属性和 ServerProperties 绑定,并且注入了该组件,组件属性的用这个组件的属性进进行填充。所以我们可以通过修改配置文件来配置容器。
程序会将 ApplicationContext 包装成 ServletWebServerApplicationContext,

ServletWebServerApplicationContext 部分源码:

    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) {
            //获取容器工厂(即上面三种之一)
            ServletWebServerFactory factory = this.getWebServerFactory();
            //调用工厂的getWebServer方法
            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();
    }

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);
    }

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

所以,ServletWebServerApplicationContext 调用 onfresh,createWebServer 获取容器工厂,并用容器工厂创建容器并返回。而且,TomcatWebServer 的构造器拥有初始化方法initialize---this.tomcat.start(),所以,容器初始化就自动启动了。

2. 切换容器

根据上面的结论,我们只需要导入对应的依赖,并且在pom中将默认的 tomcat 依赖排除即可。

<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>

3. 配置&定制容器

*3.1. 在配置文件中配置:
自动配置类中,可以看到 @EnableConfigurationProperties({ServerProperties.class}),属性是和 ServerProperties 绑定的,即 server 开头的配置,如:server.port,server.tomcat.**。这种方法最简单直接。

3.2. 直接自定义 ConfigurableServletWebServerFactory
因为上面源码可以看到,创建容器是基于容器工厂的,所以可以直接用 @Bean 注入一个该类组件,创建自己的工厂,并给工厂设置属性。

3.3. 实现 WebServerFactoryCustomizer<ConfigurableServletWebServerFactory>
Customizer 就是把配置文件的值和 ServletWebServerFactory 进行绑定,所以也可以自己用 @Bean 注入一个 Customizer,自己绑定属性。

相关文章

网友评论

      本文标题:嵌入式Servlet容器

      本文链接:https://www.haomeiwen.com/subject/zvjlcltx.html