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}" + “
@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,不管你是小白还是大牛欢迎入驻,大家一起交流成长。