【SpringBoot源码】嵌入式Servlet容器自动配置原理

嵌入式Servlet容器自动配置原理

带着问题看源码

  • 为什么可以根据配置的依赖自动使用对应的servlet容器?
  • 怎么根据配置文件中的 server.xx 以及 WebServletFactoryCustomizer 去设置servlet容器?
  • 嵌入式 servlet 容器是怎么启动的?

ServletWebServerFactoryAutoConfiguration 就是我们servlet容器自动配置类

@Configuration(proxyBeanMethods = false)
//自动配置顺序
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
//容器中必须有ServletRequest这个类,只要有使用 servlet容器都会有这个类,如果项目没有ServletRequest容器的话,那么这个类也不存在
@ConditionalOnClass(ServletRequest.class)
//类型必须是servlet
@ConditionalOnWebApplication(type = Type.SERVLET)
//配置类
@EnableConfigurationProperties(ServerProperties.class)
//导入类
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
      //阿雷,这咋一看,这下面这三个类好像有点眼熟欸!我来抓一下点
      //EmbeddedTomcat.class   EmbeddedJetty.class  EmbeddedUndertow.class
      //Embedded 是嵌入式的意思,组合起来就是嵌入式容器的意思,啧啧啧,机智如我啊
      ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
      ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
      ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration {
    //.....
}
  1. 为什么可以根据配置的依赖自动使用对应的servlet容器?

    通过 @Import 导入 Embeddedxxx.class

    //导入类
    @Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
          //阿雷,这咋一看,这下面这三个类好像有点眼熟欸!我来抓一下点
          //EmbeddedTomcat.class   EmbeddedJetty.class  EmbeddedUndertow.class
          //Embedded 是嵌入式的意思,组合起来就是嵌入式容器的意思,啧啧啧,机智如你啊
          ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
          ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
          ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
    public class ServletWebServerFactoryAutoConfiguration {
        //.....
    }
    

    每个 Embeddedxxx.class 中都配置了相应的 @ConditionalOnClass,会根据当前的servlet容器

    //=====================tomcat
    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
    @ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
    static class EmbeddedTomcat {}
        
    //=====================jetty
    @Configuration(proxyBeanMethods = false)
    	@ConditionalOnClass({ Servlet.class, Server.class, Loader.class, WebAppContext.class })
    	@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
    	static class EmbeddedJetty {}
    
    //=====================undertow
    	@Configuration(proxyBeanMethods = false)
    	@ConditionalOnClass({ Servlet.class, Undertow.class, SslClientAuthMode.class })
    	@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
    	static class EmbeddedUndertow {}
    

    如果没有对应的tomcat场景启动器,该注解就不会生效

  2. 怎么根据配置文件中的 server.xxx 以及 WebServletFactoryCustomizer 去设置servlet容器?

    1. ServletWebServerFactoryCustomizer 也实现了 WebServerFactoryCustomizer ,说明它也是定制 servlet 容器的
    2. servlet 容器配置文件通用定制器
    @Bean
    public ServletWebServerFactoryCustomizer servletWebServerFactoryCustomizer(ServerProperties serverProperties) {
       return new ServletWebServerFactoryCustomizer(serverProperties);
    }
    

    image-20210708001144274

    根据配置文件中的server.xxx来进行定制servlet容器

    //ServletWebServerFactiryCustomizer.class
    //既然继承了 WebServerFactoryCustomizer,那么他也同样的重写了customize方法
    @Override
    public void customize(ConfigurableServletWebServerFactory factory) {
       PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
       //这里这边涉及到jdk8的语法新特性,简单介绍一下下面这句代码的意思
       //获得this.serverProperties配置中的端口,设置端口
       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);
    }
    

    往下看,发现 …

    有一个叫做 TomcatServletWebServerFactoryCustomizer 的 bean 方法也实现继承了 WebServerFactoryCustomizer!

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

    image-20210708003156022

    TomcatServletWebServerFactoryCustomizer 这个类就是一个 tomcat配置文件定制器

    • 根据配置文件中server.tomcat.xx定制servler容器
    //TomcatServletWebServerFactoryCustomizer.class
    //既然继承了 WebServerFactoryCustomizer,那么他也同样的重写了customize方法
    //欸嘿,这里的设置语法跟上面那块有点不一样,但这不是重点,哈哈
    //这里也与上面同理:根据配置文件中server.tomcat.xxx 定制servlet容器
    @Override
    public void customize(TomcatServletWebServerFactory factory) {
       //获取配置中关于tomcat的配置
       ServerProperties.Tomcat tomcatProperties = this.serverProperties.getTomcat();
       if (!ObjectUtils.isEmpty(tomcatProperties.getAdditionalTldSkipPatterns())) {
          factory.getTldSkipPatterns().addAll(tomcatProperties.getAdditionalTldSkipPatterns());
       }
       if (tomcatProperties.getRedirectContextRoot() != null) {
          customizeRedirectContextRoot(factory, tomcatProperties.getRedirectContextRoot());
       }
       customizeUseRelativeRedirects(factory, tomcatProperties.isUseRelativeRedirects());
       factory.setDisableMBeanRegistry(!tomcatProperties.getMbeanregistry().isEnabled());
    }
    

    怎么让所有的 WebServerFactoryCustomizerBean 一一调用的

    • BeanPostProcessorsRegistrar

      先看看源码对这个类的解释:

      注册一个WebServerFactoryCustomizerBeanPostProcessor 。 通过ImportBeanDefinitionRegistrar注册ImportBeanDefinitionRegistrar进行早期注册

      实现 ImportBeanDefinitionRegistrar 会提供一个方法,并且提供 BeanDefinitonRegistrar 让我们去注册 bean

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

      注册了 WebServerFactoryCustormizerBeanPostProcessor

      //BeanPostProcessor 在bean创建时,在初始化的时候就会调用 postProcessBeforeInitialization方法(初始化前) postProcessAfterInitialization方法(初始化后)
      public class WebServerFactoryCustomizerBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware {
      
      //WebServerFactoryCustomizerBeanPostProcessor.class
      @Override
      public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
         //判断当前创建的bean是不是webServerFactory
         if (bean instanceof WebServerFactory) {
            //然后调用postProcessBeforeInitialization方法
            postProcessBeforeInitialization((WebServerFactory) bean);
         }
         return bean;
      }
      

      当对应Embeddedxxx 启动时,就会在里面配置 WebFactory 一个WebServerFactory 类型的一个Bean,负责创建对应的容器和启动

      image-20210708011800758

      我们看看它的类结构图

      1、调用getCustomizers()

      2、getWebServerFactoryCustomizerBeans() 就获取了所有实现了 WebServerFactoryCustomizer 接口的bean

      ​ 获取 自定义的,和 ServletWebServerFactoryCustomizer、TomcatServletWebServerFactoryCustomizer

      3、在 invoke 方法中循环调用所有实现了 WebServerFactoryCustomizer接口的Bean的customize方法进行一一定制

      @SuppressWarnings("unchecked")
      private void postProcessBeforeInitialization(WebServerFactory webServerFactory) {
         //1、调用getCustomizers()
         LambdaSafe.callbacks(WebServerFactoryCustomizer.class, getCustomizers(), webServerFactory)
      
      //3、在 invoke 方法中循环调用所有实现了 WebServerFactoryCustomizer接口的Bean的customize方法进行一一定制       .withLogger(WebServerFactoryCustomizerBeanPostProcessor.class)
               .invoke((customizer) -> customizer.customize(webServerFactory));
      }
      
      private Collection<WebServerFactoryCustomizer<?>> getCustomizers() {
         if (this.customizers == null) {
            // Look up does not include the parent context
            //2、getWebServerFactoryCustomizerBeans() 就获取了所有实现了 WebServerFactoryCustomizer 接口的bean
            this.customizers = new ArrayList<>(getWebServerFactoryCustomizerBeans());
            this.customizers.sort(AnnotationAwareOrderComparator.INSTANCE);
            this.customizers = Collections.unmodifiableList(this.customizers);
         }
         return this.customizers;
      }
      
      @SuppressWarnings({ "unchecked", "rawtypes" })
      private Collection<WebServerFactoryCustomizer<?>> getWebServerFactoryCustomizerBeans() {
         //beanFactory代表spring容器
         return (Collection) this.beanFactory.getBeansOfType(WebServerFactoryCustomizer.class, false, false).values();
      }
      
  3. 嵌入式 servlet 容器是怎么启动的?

    TomcatServletWebServerFactory

    • 自动配置中根据不同的以来,启动对应的一个Embeddedxxx,然后配置一个对应的 servlet 容器工厂类,比如 TomcatServletWebServerFactory
    • 在我们启动 springboot 应用启动的时候,就会调用refresh方法,在这个方法里面又会调用onRefresh方法,然后调用getWebServer方法,创建 servlet 并启动
    @Override
    public WebServer getWebServer(ServletContextInitializer... initializers) {
       if (this.disableMBeanRegistry) {
          Registry.disableRegistry();
       }
       Tomcat tomcat = new Tomcat();//创建一个tomcat容器
       File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
       tomcat.setBaseDir(baseDir.getAbsolutePath());
       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);
       //进入这个方法
       return getTomcatWebServer(tomcat);
    }
    
    protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
       //进入这个构造器
        return new TomcatWebServer(tomcat, getPort() >= 0, getShutdown());
    }
    
    //创建一个新的TomcatWebServer实例
    public TomcatWebServer(Tomcat tomcat, boolean autoStart, Shutdown shutdown) {
       Assert.notNull(tomcat, "Tomcat Server must not be null");
       this.tomcat = tomcat;
       this.autoStart = autoStart;
       this.gracefulShutdown = (shutdown == Shutdown.GRACEFUL) ? new GracefulShutdown(tomcat) : null;
       //进入这个方法里面
       initialize();
    }
    

    这块代码东西有点多,直接上截图看重点:

    image-20210708012806592

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值