avatar

07.SpringBoot中web嵌入式容器的配置及定制

一、嵌入式容器的配置

  1. SpringBoot中,默认的web容器为Tomcat。因为我SpringBoot版本是2.0.2,所以tomcat的版本为8.5.31,这些都可以从依赖树中看到。如下图

  1. 既然SpringBoot默认嵌入了tomcat容器,那么我们该如何去配置它的一些属性呢?比如说端口号之类的。SpringBoot给我们以下2种方式去配置。

    1. 通过yml配置文件

      • 在yml中配置,我们只要配置server.xxx即可 。如:

        1
        2
        3
        server:
        port: 80 # 配置端口
        address: 127.0.0.1 # 配置服务器绑定的地址
    2. 通过自定义实现了WebServerFactoryCustomizer的类(**在SpringBoot2.0.x以下,需要实现EmbeddedServletContainerCustomizer 接口 **)

      • 2.0.x以下版本

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        @Bean  //一定要将这个定制器加入到容器中
        public EmbeddedServletContainerCustomizer embeddedServletContainerCustomizer(){
        return new EmbeddedServletContainerCustomizer() {

        //定制嵌入式的Servlet容器相关的规则
        @Override
        public void customize(ConfigurableEmbeddedServletContainer container) {
        //设置端口
        container.setPort(8083);
        }
        };
        }
      • 2.0.x版本

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        @Configuration
        public class MyServerConfig {
        @Bean
        public WebServerFactoryCustomizer tomcatCustomizer (){
        return new WebServerFactoryCustomizer<ConfigurableServletWebServerFactory>() {
        @Override
        public void customize(ConfigurableServletWebServerFactory customizer) {
        customizer.setPort(8088);
        }
        };
        }
        }

    二、配置原理:

    1. 我们首先来看看yml中server绑定的类ServerProperties 在里面定义了各种和web容器配置相关的属性。

      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
      @ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
      public class ServerProperties {

      /**
      * Server HTTP port.
      */
      private Integer port;

      /**
      * Network address to which the server should bind.
      */
      private InetAddress address;
      /**
      * Servlet properties.
      */
      public static class Servlet {
      ....
      }
      /**
      * Tomcat properties.
      */
      public static class Tomcat {
      ...
      }
      ...
      }
    2. 我们现在看看EmbeddedWebServerFactoryCustomizerAutoConfiguration 该类简单翻译出来就是“嵌入式Web服务器工厂定制器”声明如下:

      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
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      @Configuration
      @EnableConfigurationProperties(ServerProperties.class) //为ServerProperties类绑定配置文件
      public class EmbeddedWebServerFactoryCustomizerAutoConfiguration {
      //只有在classPath下引入了Tomcat的jar包才会生效
      @ConditionalOnClass({ Tomcat.class, UpgradeProtocol.class })
      public static class TomcatWebServerFactoryCustomizerConfiguration {
      @Bean
      //创建一个TomcatWebServerFactoryCustomizer对象,并把配置文件传进去。
      public TomcatWebServerFactoryCustomizer tomcatWebServerFactoryCustomizer(
      Environment environment, ServerProperties serverProperties) {
      return new TomcatWebServerFactoryCustomizer(environment, serverProperties);
      }
      }

      @Configuration
      //只有在classp下引入了jetty的jar包才生效,Server.class是jetty的特征。
      @ConditionalOnClass({ Server.class, Loader.class, WebAppContext.class })
      public static class JettyWebServerFactoryCustomizerConfiguration {

      @Bean
      //创建jetty的web容器工厂定制器。把配置文件传进去
      public JettyWebServerFactoryCustomizer jettyWebServerFactoryCustomizer(
      Environment environment, ServerProperties serverProperties) {
      return new JettyWebServerFactoryCustomizer(environment, serverProperties);
      }

      }

      @Configuration
      //只有在classp下引入了Undertow的jar包才生效,Undertow.class是Undertow的特征。
      @ConditionalOnClass({ Undertow.class, SslClientAuthMode.class })
      public static class UndertowWebServerFactoryCustomizerConfiguration {
      @Bean
      //创建Undertow的web容器工厂定制器。把配置文件传进去
      public UndertowWebServerFactoryCustomizer undertowWebServerFactoryCustomizer(
      Environment environment, ServerProperties serverProperties) {
      return new UndertowWebServerFactoryCustomizer(environment, serverProperties);
      }
      }
      }

      从上面的源码看,这个自动配置类在启动的时候,为ServerProperties类绑定配置文件,然后把ServerProperties注入进来。再通过导入的web容器的jar包判断是导入的哪个web容器,生成对应的容器工厂定制器。并把配置文件传进去。下面我们就针对tomcat来进行分析。TomcatWebServerFactoryCustomizer源码如下(在SpringBoot2.0.x以下,是EmbeddedServletContainerCustomizer ) :

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      public class TomcatWebServerFactoryCustomizer implements
      //该类也实现了WebServerFactoryCustomizer接口
      WebServerFactoryCustomizer<ConfigurableTomcatWebServerFactory>, Ordered {
      //重写customize方法
      @Override
      public void customize(ConfigurableTomcatWebServerFactory factory) {
      ServerProperties properties = this.serverProperties;
      ServerProperties.Tomcat tomcatProperties = properties.getTomcat();
      ....//设置各种配置
      //设置静态资源路径
      customizeStaticResources(factory);
      //把设置好的Tomcat容器放进去,
      customizeErrorReportValve(properties.getError(), factory);
      }
      }

      以上说明,其实使用ServerProperties类配置,也是通过实现WebServerFactoryCustomizer接口重写customize来设置容器配置的。那么是在什么时候调用customize 这个方法进行设置呢?这就在类WebServerFactoryCustomizerBeanPostProcessor

      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
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      //该类实现了BeanPostProcessor接口,Spring将在初始化bean前后对BeanPostProcessor实现类进行回调
      public class WebServerFactoryCustomizerBeanPostProcessor
      implements BeanPostProcessor, BeanFactoryAware {

      @Override
      //在Bean初始化之前调用
      public Object postProcessBeforeInitialization(Object bean, String beanName)
      throws BeansException {
      //如果bean是WebServerFactory类型的。就执行postProcessBeforeInitialization()方法
      if (bean instanceof WebServerFactory) {
      postProcessBeforeInitialization((WebServerFactory) bean);
      }
      return bean;
      }

      @SuppressWarnings("unchecked")
      private void postProcessBeforeInitialization(WebServerFactory webServerFactory) {
      //Java8的Lambda表达式,大概的意识是,调用getCustomizers()方法获取所有的WebServerFactoryCustomizer实现类,然后执行里面的customize()方法。这样就在创建WebServerFactory实例前,先调用customize()方法,进行设置。
      LambdaSafe
      .callbacks(WebServerFactoryCustomizer.class, getCustomizers(),
      webServerFactory)
      .withLogger(WebServerFactoryCustomizerBeanPostProcessor.class)
      .invoke((customizer) -> customizer.customize(webServerFactory));
      }
      //获取所有WebServerFactoryCustomizer实现类
      private Collection<WebServerFactoryCustomizer<?>> getCustomizers() {
      if (this.customizers == null) {
      // Look up does not include the parent context
      this.customizers = new ArrayList<>(getWebServerFactoryCustomizerBeans());
      this.customizers.sort(AnnotationAwareOrderComparator.INSTANCE);
      this.customizers = Collections.unmodifiableList(this.customizers);
      }
      return this.customizers;
      }
      //获取所有WebServerFactoryCustomizer实现类
      @SuppressWarnings({ "unchecked", "rawtypes" })
      private Collection<WebServerFactoryCustomizer<?>> getWebServerFactoryCustomizerBeans() {
      return (Collection) this.beanFactory
      .getBeansOfType(WebServerFactoryCustomizer.class, false, false).values();
      }
      }

      总结下步骤:

      1. SpringBoot根据导入的依赖情况,给容器中添加相应的WebServerFactoryCustomizer,例如TomcatWebServerFactoryCustomizer
      2. 容器中某个组件要创建对象就会触发后置处理器;WebServerFactoryCustomizerBeanPostProcessor, 只要是WebServerFactory类型的,都会触发 .
      3. WebServerFactoryCustomizerBeanPostProcessor通过调用 getCustomizers()方法,获取所有实现了WebServerFactoryCustomizer接口 的实例,执行其中的customize()方法。

    三、如何注册三大组件(Servlet、Filter、Listener)

    在Servlet、jsp时代,这三大组件是重中之重,在现在作用也不可忽视。之前的web项目在WEB-INF下面会有一个web.xml文件,用来配置这三大组件的名称、拦截路径等。但是在SpringBoot中已经没有了web.xml文件,那么我们该如何去注册呢?

    1. Servlet

      我们只需要创建个ServletRegistrationBean,并加入到容器中就行了。如下

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      @Configuration
      public class ServletConfig {
      @Bean
      public ServletRegistrationBean servlet(){
      //创建ServletRegistrationBean
      ServletRegistrationBean<HttpServlet> servletRegistrationBean = new ServletRegistrationBean();
      //设置Servlet。SayHelloServlet是我们自定义的一个Servlet。该类继承HTTPServlet
      servletRegistrationBean.setServlet(new SayHelloServlet());
      //设置url映射。当我们访问localhost:8080/sayHello 这个路径时,会执行SayHelloServlet中相应请求的方法。
      servletRegistrationBean.setUrlMappings(Arrays.asList("/sayHello"));
      return servletRegistrationBean;
      }
      }
    2. Filter

      Filter也同理。只需要创建个FilterRegistrationBean并加入到容器中即可 。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      @Configuration
      public class FilterConfig {
      @Bean
      public FilterRegistrationBean servlet(){
      //创建FilterRegistrationBean
      FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean();
      //设置Filter。MyFilter是我们自定义的一个Filter。该类继承Filter
      filterRegistrationBean.setFilter(new MyFilter());
      //设置url映射。当我们访问localhost:8080/sayHello 这个路径时,该请求会被MyFilter所拦截。
      filterRegistrationBean.setUrlPatterns(Arrays.asList("/sayHello"));
      return filterRegistrationBean;
      }
      }
    3. Listener

      同上,只需要创建个ServletListenerRegistrationBean实例,并加入到Spring容器即可。

      1
      2
      3
      4
      5
      @Bean
      public ServletListenerRegistrationBean myListener(){
      ServletListenerRegistrationBean<MyListener> registrationBean = new ServletListenerRegistrationBean<>(new MyListener());
      return registrationBean;
      }
    4. SpringBoot在启动SpringMVC时,会自动帮我们把SpringMVC的前端控制器DispatcherServlet在DispatcherServletAutoConfiguration中注册好。源码如下:

      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
      31
      32
      33
      34
      35
      36
      @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
      @Configuration
      @ConditionalOnWebApplication(type = Type.SERVLET) //只有是Web应用才会生效
      @ConditionalOnClass(DispatcherServlet.class) //拥有DispatcherServlet这个类才会生效
      @AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)
      @EnableConfigurationProperties(ServerProperties.class)
      public class DispatcherServletAutoConfiguration {

      @Configuration
      @Conditional(DispatcherServletRegistrationCondition.class)
      @ConditionalOnClass(ServletRegistration.class)
      @EnableConfigurationProperties(WebMvcProperties.class)
      @Import(DispatcherServletConfiguration.class)
      protected static class DispatcherServletRegistrationConfiguration {
      @Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
      @ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
      //创建一个ServletRegistrationBean实例
      public ServletRegistrationBean<DispatcherServlet> dispatcherServletRegistration(
      DispatcherServlet dispatcherServlet) {
      //创建ServletRegistrationBean,把dispatcherServlet这个类放进去,并绑定url映射
      ServletRegistrationBean<DispatcherServlet> registration = new ServletRegistrationBean<>(
      dispatcherServlet,
      //url映射。默认为"/"
      this.serverProperties.getServlet().getServletMapping());
      registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
      //设置优先级
      registration.setLoadOnStartup(
      this.webMvcProperties.getServlet().getLoadOnStartup());
      if (this.multipartConfig != null) {
      registration.setMultipartConfig(this.multipartConfig);
      }
      return registration;
      }

      }
      }

四、替换其他web嵌入容器

从上面EmbeddedWebServerFactoryCustomizerAutoConfiguration 的分析可以看到,SpringBoot通过@ConditionalOnClass的方式来判断该应用是使用何种嵌入式容器,从而生成不同的WebServerFactoryCustomizerConfiguration进行配置。所以我们只需要修改pom.xml文件,就可以达到切换web嵌入容器的目的了。

  1. Tomcat

    1
    2
    3
    4
    5
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    引入web模块默认就是使用嵌入式的Tomcat作为Servlet容器;
    </dependency>
  2. Jetty

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <!-- 引入web模块 -->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
    <exclusion>
    <!-- 排除默认的Tomcat容器 -->
    <artifactId>spring-boot-starter-tomcat</artifactId>
    <groupId>org.springframework.boot</groupId>
    </exclusion>
    </exclusions>
    </dependency>

    <!--引入其他的Servlet容器-->
    <dependency>
    <artifactId>spring-boot-starter-jetty</artifactId>
    <groupId>org.springframework.boot</groupId>
    </dependency>
  3. Undertow

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <!-- 引入web模块 -->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
    <exclusion>
    <!-- 排除默认的Tomcat容器 -->
    <artifactId>spring-boot-starter-tomcat</artifactId>
    <groupId>org.springframework.boot</groupId>
    </exclusion>
    </exclusions>
    </dependency>

    <!--引入其他的Servlet容器-->
    <dependency>
    <artifactId>spring-boot-starter-undertow</artifactId>
    <groupId>org.springframework.boot</groupId>
    </dependency>

五、小结

  1. 如何修改SpringBoot中嵌入式容器的配置?

    答:有两种方法,其实本质上只有一种。两种方法为:

    • 修改yml配置文件 (推荐);
    • 自定义实现WebServerFactoryCustomizer的类;
  2. 如何注册三大组件(Servlet、Filter、Listener)?

    答:

    • 在配置类中创建ServletRegistrationBean 类,并加入到容器中,可向容器注册Servlet
    • 在配置类中创建FilterRegistrationBean 类,并加入到容器中,可向容器注册Filter
    • 在配置类中创建ServletListenerRegistrationBean 类,并加入到容器中,可向容器注册Listener
  3. 如何把默认的web容器替换成其他的内嵌的web容器?

    答:修改pom依赖即可。

此次分析到此结束,谢谢大家的观看,如若有任何错误,希望大家可以在评论处给出。我会虚心学习。谢谢!


评论