【SpringBoot】Servlet容器


title: 【SpringBoot】Servlet容器
date: 2017-08-31 21:25:12
tags:

  • Java
  • Spring
    categories: Spring

记得自己看 Spring Boot 源码的初衷是对部署时不需要额外的 Servlet 容器的好奇,好像看着看着关注到了其他细节。挖了几个坑就跳过了,今天把之前关于 Spring Boot 与 Servlet 容器的坑填一下。

先回(rang)顾(wo)一(xiang)下(xiang)之前与 Web 环境相关的内容:

Servlet 容器启动

【SpringBoot】容器启动 中提到的创建处理 web 环境对应的 Spring 容器实际创建的就是 AnnotationConfigEmbeddedWebApplicationContext,这个类是 ApplicationContext 的子类。其分别在 onRefresh 和 finishRefresh 方法中创建和启动 Servlet 容器:

@Override
protected void onRefresh() {
   super.onRefresh();
   try {
      createEmbeddedServletContainer();
   }
   catch (Throwable ex) {
      throw new ApplicationContextException("Unable to start embedded container", ex);
   }
}

@Override
protected void finishRefresh() {
  super.finishRefresh();
  EmbeddedServletContainer localContainer = startEmbeddedServletContainer();
  if (localContainer != null) {
    publishEvent(
    new EmbeddedServletContainerInitializedEvent(this, localContainer));
  }
}

在 Spring 容器生命周期里与 Servlet 容器相关的逻辑封装在以上两个方法,分别用以创建和启动 Servlet 容器。

Servlet 容器创建细节

上面说到 AnnotationConfigEmbeddedWebApplicationContext 中创建 Servlet 容器,具体的细节如下:

// AnnotationConfigEmbeddedWebApplicationContext
private void createEmbeddedServletContainer() {
   EmbeddedServletContainer localContainer = this.embeddedServletContainer;
   ServletContext localServletContext = getServletContext();
   if (localContainer == null && localServletContext == null) {
      // 获取 Servlet 容器工厂
      EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();
      // 获取 Servlet 容器初始化器
      // 使用 Servlet 容器初始化器创建 Servlet 容器
      this.embeddedServletContainer = 
        containerFactory.getEmbeddedServletContainer(getSelfInitializer());
   }
   else if (localServletContext != null) {
      try {
         getSelfInitializer().onStartup(localServletContext);
      }
      catch (ServletException ex) {
         throw new ApplicationContextException("Cannot initialize servlet context",
               ex);
      }
   }
   initPropertySources();
}

细节可以分为:

  1. 获取 Servlet 容器工厂
  2. 获取 Servlet 容器初始化器
  3. 创建 Servlet 容器

获取 Servlet 容器工厂

// AnnotationConfigEmbeddedWebApplicationContext

protected EmbeddedServletContainerFactory getEmbeddedServletContainerFactory() {
   // Use bean names so that we don't consider the hierarchy
   String[] beanNames = getBeanFactory()
         .getBeanNamesForType(EmbeddedServletContainerFactory.class);
   // 省略异常
   return getBeanFactory().getBean(beanNames[0],
         EmbeddedServletContainerFactory.class);
}

直接从 Spring 容器里获取 Servlet 容器工厂。一时之间卡住了不知道 EmbeddedServletContainerFactory 这个类时怎么注册到 Spring 容器中的。在网上查了一下发现是 META-INF/spring.factories 里指定了 EmbeddedServletContainerAutoConfiguration 这个配置类。而 EmbeddedServletContainerFactory 是 EmbeddedServletContainerAutoConfiguration 这个自动化配置类中被注册到 Spring 容器中的,关于配置类的注册我又要挖坑了。EmbeddedServletContainerAutoConfiguration:

@AutoConfigureOrder(-2147483648)
@Configuration
@ConditionalOnWebApplication  // 在Web环境下起作用
@Import({EmbeddedServletContainerAutoConfiguration.BeanPostProcessorsRegistrar.class})
public class EmbeddedServletContainerAutoConfiguration {
    public EmbeddedServletContainerAutoConfiguration() {
    }
    
    // 在 import 中导入该类
    // 主要作用是注册 EmbeddedServletContainerCustomizerBeanPostProcessor,
    // ErrorPageRegistrarBeanPostProcessor
    public static class BeanPostProcessorsRegistrar 
      implements ImportBeanDefinitionRegistrar, BeanFactoryAware {
        // 省略
        public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
                                            BeanDefinitionRegistry registry) {
            if(this.beanFactory != null) {
                // 注册 EmbeddedServletContainerCustomizerBeanPostProcessor
                this.registerSyntheticBeanIfMissing(
                  registry, "embeddedServletContainerCustomizerBeanPostProcessor", 
                  EmbeddedServletContainerCustomizerBeanPostProcessor.class);
                // 注册 ErrorPageRegistrarBeanPostProcessor
                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);
            }

        }
    }

    // 省略 Jetty,Undertow 配置

    @Configuration
    @ConditionalOnClass({Servlet.class, Tomcat.class})
    @ConditionalOnMissingBean(
        value = {EmbeddedServletContainerFactory.class},
        search = SearchStrategy.CURRENT
    )
    public static class EmbeddedTomcat {
        public EmbeddedTomcat() {
        }

        @Bean
        public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() {
            // 注册 EmbeddedServletContainerFactory 的实现类
            return new TomcatEmbeddedServletContainerFactory();
        }
    }
}

当满足特定条件时会注册具体的 EmbeddedServletContainerFactory 实现类,例如 TomcatEmbeddedServletContainerFactory。

这里还看到 注册了 EmbeddedServletContainerCustomizerBeanPostProcessor 和 ErrorPageRegistrarBeanPostProcessor 。简单看了一下 EmbeddedServletContainerCustomizerBeanPostProcessor,这是一个基本的 BeanPostProcessor,具体作用是对 EmbeddedServletContainerCustomizer 的实例进行定制,具体的实现包括:ErrorPageCustomizer,TomcatWebSocketContainerCustomizer,ServerProperties 等。

// EmbeddedServletContainerCustomizerBeanPostProcessor 
public class EmbeddedServletContainerCustomizerBeanPostProcessor
      implements BeanPostProcessor, BeanFactoryAware {

   private ListableBeanFactory beanFactory;

   private List<EmbeddedServletContainerCustomizer> customizers;

   @Override
   public Object postProcessBeforeInitialization(Object bean, String beanName)
         throws BeansException {
      if (bean instanceof ConfigurableEmbeddedServletContainer) {
         // 处理ConfigurableEmbeddedServletContainer类型的bean
         postProcessBeforeInitialization((ConfigurableEmbeddedServletContainer) bean);
      }
      return bean;
   }

   private void postProcessBeforeInitialization(
         ConfigurableEmbeddedServletContainer bean) {
      // 对ConfigurableEmbeddedServletContainer类型的bean定制化处理
      for (EmbeddedServletContainerCustomizer customizer : getCustomizers()) {
         customizer.customize(bean);
      }
   }

   private Collection<EmbeddedServletContainerCustomizer> getCustomizers() {
      if (this.customizers == null) {
         // 从容器中找出所有EmbeddedServletContainerCustomizer类型的bean
         this.customizers = new ArrayList<EmbeddedServletContainerCustomizer>(
               this.beanFactory.getBeansOfType(EmbeddedServletContainerCustomizer.class,
                                               false, false).values());
         Collections.sort(this.customizers, AnnotationAwareOrderComparator.INSTANCE);
         this.customizers = Collections.unmodifiableList(this.customizers);
      }
      return this.customizers;
   }

}

Servlet 容器初始化器

再回顾一下 Servlet 容器的创建核心逻辑:

containerFactory.getEmbeddedServletContainer(getSelfInitializer());

先获取 Servlet 初始化器,然后创建 Servlet 容器

private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
   return new ServletContextInitializer() {
      @Override
      public void onStartup(ServletContext servletContext) throws ServletException {
         selfInitialize(servletContext);
      }
   };
}

private void selfInitialize(ServletContext servletContext) throws ServletException {
   // Servlet 容器准备
   prepareEmbeddedWebApplicationContext(servletContext);
   // 初始 scopes
   ConfigurableListableBeanFactory beanFactory = getBeanFactory();
   ExistingWebApplicationScopes existingScopes = new ExistingWebApplicationScopes(
         beanFactory);
   WebApplicationContextUtils.registerWebApplicationScopes(beanFactory,
         getServletContext());
   existingScopes.restore();
   // 注册 web 相关 bean
   WebApplicationContextUtils.registerEnvironmentBeans(beanFactory,
         getServletContext());
   // 初始化 servlet, filter, Listener 并注册
   for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
      beans.onStartup(servletContext);
   }
}

// 获取 ServletContext 初始化 bean
protected Collection<ServletContextInitializer> getServletContextInitializerBeans() {
    new ServletContextInitializerBeans(getBeanFactory());
}
public ServletContextInitializerBeans(ListableBeanFactory beanFactory) {
   this.initializers = new LinkedMultiValueMap<Class<?>, ServletContextInitializer>();
   // 添加 ServletContextInitializer 类型的 bean
   addServletContextInitializerBeans(beanFactory);
   addAdaptableBeans(beanFactory);
   List<ServletContextInitializer> sortedInitializers = 
     new ArrayList<ServletContextInitializer>();
   for (Map.Entry<?, List<ServletContextInitializer>> entry : this.initializers.entrySet()) {
      AnnotationAwareOrderComparator.sort(entry.getValue());
      sortedInitializers.addAll(entry.getValue());
   }
   this.sortedList = Collections.unmodifiableList(sortedInitializers);
}

private void addServletContextInitializerBeans(ListableBeanFactory beanFactory) {
   for (Entry<String, ServletContextInitializer> initializerBean : getOrderedBeansOfType(
         beanFactory, ServletContextInitializer.class)) {
      addServletContextInitializerBean(initializerBean.getKey(),
            initializerBean.getValue(), beanFactory);
   }
}

private void addServletContextInitializerBean(String beanName,
      ServletContextInitializer initializer, ListableBeanFactory beanFactory) {
   if (initializer instanceof ServletRegistrationBean) {
      // servlet
      Servlet source = ((ServletRegistrationBean) initializer).getServlet();
      addServletContextInitializerBean(Servlet.class, beanName, initializer,
            beanFactory, source);
   }
   else if (initializer instanceof FilterRegistrationBean) {
      // filter
      Filter source = ((FilterRegistrationBean) initializer).getFilter();
      addServletContextInitializerBean(Filter.class, beanName, initializer,
            beanFactory, source);
   }
   else if (initializer instanceof DelegatingFilterProxyRegistrationBean) {
      // filter
      String source = ((DelegatingFilterProxyRegistrationBean) initializer)
            .getTargetBeanName();
      addServletContextInitializerBean(Filter.class, beanName, initializer,
            beanFactory, source);
   }
   else if (initializer instanceof ServletListenerRegistrationBean) {
      // listener
      EventListener source = ((ServletListenerRegistrationBean<?>) initializer)
            .getListener();
      addServletContextInitializerBean(EventListener.class, beanName, initializer,
            beanFactory, source);
   }
   else {
      addServletContextInitializerBean(ServletContextInitializer.class, beanName,
            initializer, beanFactory, initializer);
   }
}

创建 Servlet 容器

以 TomcatEmbeddedServletContainerFactory 的创建容器方法为例:

@Override
public EmbeddedServletContainer getEmbeddedServletContainer(
      ServletContextInitializer... initializers) {
   Tomcat tomcat = new Tomcat();
   File baseDir = (this.baseDirectory != null ? this.baseDirectory
         : createTempDir("tomcat"));
   tomcat.setBaseDir(baseDir.getAbsolutePath());
   Connector connector = new Connector(this.protocol);
   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 getTomcatEmbeddedServletContainer(tomcat);
}

上面的代码完成了 Servlet 容器启动前所有的创建,配置动作。

Servlet 容器启动

private EmbeddedServletContainer startEmbeddedServletContainer() {
   EmbeddedServletContainer localContainer = this.embeddedServletContainer;
   if (localContainer != null) {
      localContainer.start();
   }
   return localContainer;
}

启动的逻辑并不复杂,直接调用 Servlet 容器的 start 方法。

总结

Spring Boot 使用的内嵌 Servlet 容器启动过程:

  1. 从 spring.factories 中读取并注册了 EmbeddedServletContainerAutoConfiguration 类
  2. EmbeddedServletContainerAutoConfiguration 注册了 Servlet 容器工厂类
  3. 在 Spring 容器生命周期的 onRefresh 方法中开始创建 Servlet 容器
    1. 获取 Servlet 容器工厂
    2. 获取 Servlet 相关初始化 bean
    3. 配置并创建 Servlet 容器
  4. 在 Spring 容器生命周期的 finishRefresh 方法中调用 Servlet 容器的 start 方法启动容器
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容