SpringBoot是否内置了Servlet容器?如果内置了,它是如何工作的?

SpringBoot是否内置了Servlet容器?如果内置了,它是如何工作的?SpringBoot内置了Servlet容器,这样项目的发布、部署就不需要额外的Servlet容器,直接启动jar包即可。SpringBoot官方文档上有一个小章节内置servlet容器支持用于说明内置Servlet的相关问题。 在SpringBoot源码分析之SpringBoot的启动过程文章中我们了解到如果是Web程序,那么会构造AnnotationConfigEmbeddedWebApplicationContext类型的Spring容器,在SpringBoot源码分析之Spring容器的refresh过程文章中我们知道AnnotationConfigEmbeddedWebApplicationContext类型的Spring容器在refresh的过程中会在onRefresh方法中创建内置的Servlet容器。 接下来,我们分析一下内置的Servlet容器相关的知识点。 # 内置Servlet容器相关的接口和类 SpringBoot对内置的Servlet容器做了一层封装:public interface EmbeddedServletContainer {    // 启动内置的Servlet容器,如果容器已经启动,则不影响    void start() throws EmbeddedServletContainerException;    // 关闭内置的Servlet容器,如果容器已经关系,则不影响    void stop() throws EmbeddedServletContainerException;    // 内置的Servlet容器监听的端口    int getPort();} 它目前有3个实现类,分别是JettyEmbeddedServletContainer、TomcatEmbeddedServletContainer和UndertowEmbeddedServletContainer,分别对应Jetty、Tomcat和Undertow这3个Servlet容器。 EmbeddedServletContainerFactory接口是一个工厂接口,用于生产EmbeddedServletContainer:public interface EmbeddedServletContainerFactory {    // 获得一个已经配置好的内置Servlet容器,但是这个容器还没有监听端口。需要手动调用内置Servlet容器的start方法监听端口    // 参数是一群ServletContextInitializer,Servlet容器启动的时候会遍历这些ServletContextInitializer,并调用onStartup方法    EmbeddedServletContainer getEmbeddedServletContainer(            ServletContextInitializer… initializers);} ServletContextInitializer表示Servlet初始化器,用于设置ServletContext中的一些配置,在使用EmbeddedServletContainerFactory接口的getEmbeddedServletContainer方法获取Servlet内置容器并且容public interface ServletContextInitializer {    void onStartup(ServletContext servletContext) throws ServletException;} EmbeddedServletContainerFactory是在EmbeddedServletContainerAutoConfiguration这个自动化配置类中被注册到Spring容器中的(前期是Spring容器中不存在EmbeddedServletContainerFactory类型的bean,可以自己定义EmbeddedServletContainerFactory类型的bean)@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)@Configuration@ConditionalOnWebApplication // 在Web环境下才会起作用@Import(BeanPostProcessorsRegistrar.class) // 会Import一个内部类BeanPostProcessorsRegistrarpublic class EmbeddedServletContainerAutoConfiguration {
    @Configuration    // Tomcat类和Servlet类必须在classloader中存在    @ConditionalOnClass({ Servlet.class, Tomcat.class })    // 当前Spring容器中不存在EmbeddedServletContainerFactory类型的实例    @ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)    public static class EmbeddedTomcat {
        @Bean        public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() {          // 上述条件注解成立的话就会构造TomcatEmbeddedServletContainerFactory这个EmbeddedServletContainerFactory            return new TomcatEmbeddedServletContainerFactory();        }
    }
    @Configuration    // Server类、Servlet类、Loader类以及WebAppContext类必须在classloader中存在    @ConditionalOnClass({ Servlet.class, Server.class, Loader.class,            WebAppContext.class })    // 当前Spring容器中不存在EmbeddedServletContainerFactory类型的实例    @ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)    public static class EmbeddedJetty {
        @Bean        public JettyEmbeddedServletContainerFactory jettyEmbeddedServletContainerFactory() {            // 上述条件注解成立的话就会构造JettyEmbeddedServletContainerFactory这个EmbeddedServletContainerFactory            return new JettyEmbeddedServletContainerFactory();        }
    }
    @Configuration    // Undertow类、Servlet类、以及SslClientAuthMode类必须在classloader中存在    @ConditionalOnClass({ Servlet.class, Undertow.class, SslClientAuthMode.class })    // 当前Spring容器中不存在EmbeddedServletContainerFactory类型的实例    @ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)    public static class EmbeddedUndertow {
        @Bean        public UndertowEmbeddedServletContainerFactory undertowEmbeddedServletContainerFactory() {            // 上述条件注解成立的话就会构造JettyEmbeddedServletContainerFactory这个EmbeddedServletContainerFactory            return new UndertowEmbeddedServletContainerFactory();        }
    }    // 在EmbeddedServletContainerAutoConfiguration自动化配置类中被导入,实现了BeanFactoryAware接口(BeanFactory会被自动注入进来)和ImportBeanDefinitionRegistrar接口(会被ConfigurationClassBeanDefinitionReader解析并注册到Spring容器中)    public static class EmbeddedServletContainerCustomizerBeanPostProcessorRegistrar              implements ImportBeanDefinitionRegistrar, BeanFactoryAware {
          private ConfigurableListableBeanFactory beanFactory;
          @Override          public void setBeanFactory(BeanFactory beanFactory) throws BeansException {              if (beanFactory instanceof ConfigurableListableBeanFactory) {                  this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;              }          }
          @Override          public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,                  BeanDefinitionRegistry registry) {              if (this.beanFactory == null) {                  return;              }              // 如果Spring容器中不存在EmbeddedServletContainerCustomizerBeanPostProcessor类型的bean              if (ObjectUtils.isEmpty(this.beanFactory.getBeanNamesForType(                      EmbeddedServletContainerCustomizerBeanPostProcessor.class, true,                      false))) {                  // 注册一个EmbeddedServletContainerCustomizerBeanPostProcessor                  registry.registerBeanDefinition(                          “embeddedServletContainerCustomizerBeanPostProcessor”,                          new RootBeanDefinition(                                  EmbeddedServletContainerCustomizerBeanPostProcessor.class));
              }          }
      }
} EmbeddedServletContainerCustomizerBeanPostProcessor是一个BeanPostProcessor,它在postProcessBeforeInitialization过程中去寻找Spring容器中EmbeddedServletContainerCustomizer类型的bean,并依次调用EmbeddedServletContainerCustomizer接口的customize方法做一些定制化:@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName)    throws BeansException {  // 在Spring容器中寻找ConfigurableEmbeddedServletContainer类型的bean,SpringBoot内部的3种内置Servlet容器工厂都实现了这个接口,该接口的作用就是进行Servlet容器的配置  // 比如添加Servlet初始化器addInitializers、添加错误页addErrorPages、设置session超时时间setSessionTimeout、设置端口setPort等等  if (bean instanceof ConfigurableEmbeddedServletContainer) {    postProcessBeforeInitialization((ConfigurableEmbeddedServletContainer) bean);  }  return bean;}
private void postProcessBeforeInitialization(    ConfigurableEmbeddedServletContainer bean) {  for (EmbeddedServletContainerCustomizer customizer : getCustomizers()) {    // 遍历获取的每个定制化器,并调用customize方法进行一些定制    customizer.customize(bean);  }}
private Collection getCustomizers() {  if (this.customizers == null) {    this.customizers = new ArrayList(        // 找出Spring容器中EmbeddedServletContainerCustomizer类型的bean        this.applicationContext            .getBeansOfType(EmbeddedServletContainerCustomizer.class,                false, false)            .values());    // 定制化器做排序    Collections.sort(this.customizers, AnnotationAwareOrderComparator.INSTANCE);    // 设置定制化器到属性中    this.customizers = Collections.unmodifiableList(this.customizers);  }  return this.customizers;} SpringBoot内置了一些EmbeddedServletContainerCustomizer,比如ErrorPageCustomizer、ServerProperties、TomcatWebSocketContainerCustomizer等。 定制器比如ServerProperties表示服务端的一些配置,以server为前缀,比如有server.port、server.contextPath、server.displayName等,它同时也实现了EmbeddedServletContainerCustomizer接口,其中customize方法的一部分代码如下:@Overridepublic void customize(ConfigurableEmbeddedServletContainer container) {  // 3种ServletContainerFactory都实现了ConfigurableEmbeddedServletContainer接口,所以下面的这些设置相当于对ServletContainerFactory进行设置  // 如果配置了端口信息  if (getPort() != null) {    container.setPort(getPort());  }  …  // 如果配置了displayName  if (getDisplayName() != null) {    container.setDisplayName(getDisplayName());  }  // 如果配置了server.session.timeout,session超时时间。注意:这里的Session指的是ServerProperties的内部静态类Session  if (getSession().getTimeout() != null) {    container.setSessionTimeout(getSession().getTimeout());  }  …  // 如果使用的是Tomcat内置Servlet容器,设置对应的Tomcat配置  if (container instanceof TomcatEmbeddedServletContainerFactory) {    getTomcat().customizeTomcat(this,        (TomcatEmbeddedServletContainerFactory) container);  }  // 如果使用的是Jetty内置Servlet容器,设置对应的Tomcat配置  if (container instanceof JettyEmbeddedServletContainerFactory) {    getJetty().customizeJetty(this,        (JettyEmbeddedServletContainerFactory) container);  }  // 如果使用的是Undertow内置Servlet容器,设置对应的Tomcat配置  if (container instanceof UndertowEmbeddedServletContainerFactory) {    getUndertow().customizeUndertow(this,        (UndertowEmbeddedServletContainerFactory) container);  }  // 添加SessionConfiguringInitializer这个Servlet初始化器  // SessionConfiguringInitializer初始化器的作用是基于ServerProperties的内部静态类Session设置Servlet中session和cookie的配置  container.addInitializers(new SessionConfiguringInitializer(this.session));  // 添加InitParameterConfiguringServletContextInitializer初始化器  // InitParameterConfiguringServletContextInitializer初始化器的作用是基于ServerProperties的contextParameters配置设置到ServletContext的init param中  container.addInitializers(new InitParameterConfiguringServletContextInitializer(      getContextParameters()));} ErrorPageCustomizer在ErrorMvcAutoConfiguration自动化配置里定义,是个内部静态类: @Beanpublic ErrorPageCustomizer errorPageCustomizer() {    return new ErrorPageCustomizer(this.properties);}
private static class ErrorPageCustomizer          implements EmbeddedServletContainerCustomizer, Ordered {
        private final ServerProperties properties;
        protected ErrorPageCustomizer(ServerProperties properties) {            this.properties = properties;        }
        @Override        public void customize(ConfigurableEmbeddedServletContainer container) {            // 添加错误页ErrorPage,这个ErrorPage对应的路径是 /error            // 可以通过配置修改 ${servletPath} + KaTeX parse error: Expected 'EOF', got '}' at position 166: …th()));        }̲         @Overr…{timestamp}"          + “

There was an unexpected error (type= e r r o r , s t a t u s = {error}, status= {status}).
”          + “
${message}
”);
  @Bean(name = “error”) // bean的名字是error  @ConditionalOnMissingBean(name = “error”) // 名字为error的bean不存在才会构造  public View defaultErrorView() {    return this.defaultErrorView;  }
  @Bean  @ConditionalOnMissingBean(BeanNameViewResolver.class)  public BeanNameViewResolver beanNameViewResolver() {    // BeanNameViewResolver会去Spring容器找对应bean的视图    BeanNameViewResolver resolver = new BeanNameViewResolver();    resolver.setOrder(Ordered.LOWEST_PRECEDENCE - 10);    return resolver;  }
} 如果自定义了error页面,比如使用freemarker模板的话存在/templates/error.ftl页面,使用thymeleaf模板的话存在/templates/error.html页面。那么Whitelabel Error Page就不会生效了,而是会跳到这些error页面。这又是如何实现的呢? 这是因为ErrorMvcAutoConfiguration自动化配置类里的内部类 WhitelabelErrorViewConfiguration自动化配置类里有个条件类ErrorTemplateMissingCondition,它的getMatchOutcome方法:@Overridepublic ConditionOutcome getMatchOutcome(ConditionContext context,    AnnotatedTypeMetadata metadata) {  // 从spring.factories文件中找出key为TemplateAvailabilityProvider为类,TemplateAvailabilityProvider用来查询视图是否可用  List availabilityProviders = SpringFactoriesLoader      .loadFactories(TemplateAvailabilityProvider.class,          context.getClassLoader());  // 遍历各个TemplateAvailabilityProvider  for (TemplateAvailabilityProvider availabilityProvider : availabilityProviders)    // 如果error视图可用    if (availabilityProvider.isTemplateAvailable(“error”,        context.getEnvironment(), context.getClassLoader(),        context.getResourceLoader())) {      // 条件不生效。WhitelabelErrorViewConfiguration不会被构造      return ConditionOutcome.noMatch(“Template from "          + availabilityProvider + " found for error view”);    }  }  // 条件生效。WhitelabelErrorViewConfiguration被构造  return ConditionOutcome.match(“No error template view detected”);  } 比如FreeMarkerTemplateAvailabilityProvider这个TemplateAvailabilityProvider的逻辑如下:public class FreeMarkerTemplateAvailabilityProvider        implements TemplateAvailabilityProvider {
    @Override    public boolean isTemplateAvailable(String view, Environment environment,            ClassLoader classLoader, ResourceLoader resourceLoader) {        // 判断是否存在freemarker包中的Configuration类,存在的话才会继续        if (ClassUtils.isPresent(“freemarker.template.Configuration”, classLoader)) {            // 构造属性解析器            RelaxedPropertyResolver resolver = new RelaxedPropertyResolver(environment,                    “spring.freemarker.”);            // 设置一些配置            String loaderPath = resolver.getProperty(“template-loader-path”,                    FreeMarkerProperties.DEFAULT_TEMPLATE_LOADER_PATH);            String prefix = resolver.getProperty(“prefix”,                    FreeMarkerProperties.DEFAULT_PREFIX);            String suffix = resolver.getProperty(“suffix”,                    FreeMarkerProperties.DEFAULT_SUFFIX);            // 查找对应的资源文件是否存在            return resourceLoader.getResource(loaderPath + prefix + view + suffix)                    .exists();        }        return false;    }
}所以BeanNameViewResolver不会被构造,Whitelabel Error Page也不会构造,而是直接去找自定义的error视图。所谓技多不压身,我们所读过的每一本书,所学过的每一门语言,在未来指不定都能给我们意想不到的回馈呢。其实做为一个开发者,有一个学习的氛围跟一个交流圈子特别重要这里我推荐一个Java学习交流群342016322,不管你是小白还是大牛欢迎入驻,大家一起交流成长。

发布了58 篇原创文章 · 获赞 6 · 访问量 7130

猜你喜欢

转载自blog.csdn.net/weixin_44545088/article/details/105361888