原文链接:http://fangjian0423.github.io/2017/05/22/springboot-embedded-servlet-container/

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内置容器并且容器启动的时候调用onStartup方法:

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一个内部类BeanPostProcessorsRegistrar
public class EmbeddedServletContainerAutoConfiguration {@Configuration// Tomcat类和Servlet类必须在classloader中存在@ConditionalOnClass({ Servlet.class, Tomcat.class })// 当前Spring容器中不存在EmbeddedServletContainerFactory类型的实例@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)public static class EmbeddedTomcat {@Beanpublic TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() {// 上述条件注解成立的话就会构造TomcatEmbeddedServletContainerFactory这个EmbeddedServletContainerFactoryreturn 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 {@Beanpublic JettyEmbeddedServletContainerFactory jettyEmbeddedServletContainerFactory() {// 上述条件注解成立的话就会构造JettyEmbeddedServletContainerFactory这个EmbeddedServletContainerFactoryreturn 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 {@Beanpublic UndertowEmbeddedServletContainerFactory undertowEmbeddedServletContainerFactory() {// 上述条件注解成立的话就会构造JettyEmbeddedServletContainerFactory这个EmbeddedServletContainerFactoryreturn new UndertowEmbeddedServletContainerFactory();}}// 在EmbeddedServletContainerAutoConfiguration自动化配置类中被导入,实现了BeanFactoryAware接口(BeanFactory会被自动注入进来)和ImportBeanDefinitionRegistrar接口(会被ConfigurationClassBeanDefinitionReader解析并注册到Spring容器中)public static class EmbeddedServletContainerCustomizerBeanPostProcessorRegistrarimplements ImportBeanDefinitionRegistrar, BeanFactoryAware {private ConfigurableListableBeanFactory beanFactory;@Overridepublic void setBeanFactory(BeanFactory beanFactory) throws BeansException {if (beanFactory instanceof ConfigurableListableBeanFactory) {this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;}}@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,BeanDefinitionRegistry registry) {if (this.beanFactory == null) {return;}// 如果Spring容器中不存在EmbeddedServletContainerCustomizerBeanPostProcessor类型的beanif (ObjectUtils.isEmpty(this.beanFactory.getBeanNamesForType(EmbeddedServletContainerCustomizerBeanPostProcessor.class, true,false))) {// 注册一个EmbeddedServletContainerCustomizerBeanPostProcessorregistry.registerBeanDefinition("embeddedServletContainerCustomizerBeanPostProcessor",new RootBeanDefinition(EmbeddedServletContainerCustomizerBeanPostProcessor.class));}}}}

EmbeddedServletContainerCustomizerBeanPostProcessor是一个BeanPostProcessor,它在postProcessBeforeInitialization过程中去寻找Spring容器中EmbeddedServletContainerCustomizer类型的bean,并依次调用EmbeddedServletContainerCustomizer接口的customize方法做一些定制化:

@Override
public 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<EmbeddedServletContainerCustomizer> getCustomizers() {if (this.customizers == null) {this.customizers = new ArrayList<EmbeddedServletContainerCustomizer>(// 找出Spring容器中EmbeddedServletContainerCustomizer类型的beanthis.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方法的一部分代码如下:

@Override
public void customize(ConfigurableEmbeddedServletContainer container) {// 3种ServletContainerFactory都实现了ConfigurableEmbeddedServletContainer接口,所以下面的这些设置相当于对ServletContainerFactory进行设置// 如果配置了端口信息if (getPort() != null) {container.setPort(getPort());}...// 如果配置了displayNameif (getDisplayName() != null) {container.setDisplayName(getDisplayName());}// 如果配置了server.session.timeout,session超时时间。注意:这里的Session指的是ServerProperties的内部静态类Sessionif (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自动化配置里定义,是个内部静态类:

@Bean
public ErrorPageCustomizer errorPageCustomizer() {return new ErrorPageCustomizer(this.properties);
}private static class ErrorPageCustomizerimplements EmbeddedServletContainerCustomizer, Ordered {private final ServerProperties properties;protected ErrorPageCustomizer(ServerProperties properties) {this.properties = properties;}@Overridepublic void customize(ConfigurableEmbeddedServletContainer container) {// 添加错误页ErrorPage,这个ErrorPage对应的路径是 /error// 可以通过配置修改 ${servletPath} + ${error.path}container.addErrorPages(new ErrorPage(this.properties.getServletPrefix()+ this.properties.getError().getPath()));}@Overridepublic int getOrder() {return 0;}}

DispatcherServlet的构造

DispatcherServlet是SpringMVC中的核心分发器。它是在DispatcherServletAutoConfiguration这个自动化配置类里构造的(如果Spring容器内没有自定义的DispatcherServlet),并且还会被加到Servlet容器中(通过ServletRegistrationBean完成)。

DispatcherServletAutoConfiguration这个自动化配置类存在2个条件注解@ConditionalOnWebApplication和@ConditionalOnClass(DispatcherServlet.class)都满足条件,所以会被构造(存在@AutoConfigureAfter(EmbeddedServletContainerAutoConfiguration.class)注解,会在EmbeddedServletContainerAutoConfiguration自动化配置类构造后构造):

@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@ConditionalOnWebApplication
@ConditionalOnClass(DispatcherServlet.class)
@AutoConfigureAfter(EmbeddedServletContainerAutoConfiguration.class)
public class DispatcherServletAutoConfiguration ...

DispatcherServletAutoConfiguration有个内部类DispatcherServletConfiguration,它会构造DispatcherServlet(使用了条件类DefaultDispatcherServletCondition,如果Spring容器已经存在自定义的DispatcherServlet类型的bean,该类就不会被构造,会直接使用自定义的DispatcherServlet):

@Configuration
// 条件类DefaultDispatcherServletCondition,是EmbeddedServletContainerAutoConfiguration的内部类
// DefaultDispatcherServletCondition条件类会去Spring容器中找DispatcherServlet类型的实例,如果找到了不会构造DispatcherServletConfiguration,否则就是构造DispatcherServletConfiguration,该类内部会构造DispatcherServlet
// 所以如果我们要自定义DispatcherServlet的话只需要自定义DispatcherServlet即可,这样DispatcherServletConfiguration内部就不会构造DispatcherServlet
@Conditional(DefaultDispatcherServletCondition.class)
// Servlet3.0开始才有的类,支持以编码的形式注册Servlet
@ConditionalOnClass(ServletRegistration.class)
// spring.mvc 为前缀的配置
@EnableConfigurationProperties(WebMvcProperties.class)
protected static class DispatcherServletConfiguration {@Autowiredprivate ServerProperties server;@Autowiredprivate WebMvcProperties webMvcProperties;@Autowired(required = false)private MultipartConfigElement multipartConfig;// Spring容器注册DispatcherServlet@Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)public DispatcherServlet dispatcherServlet() {// 直接构造DispatcherServlet,并设置WebMvcProperties中的一些配置DispatcherServlet dispatcherServlet = new DispatcherServlet();dispatcherServlet.setDispatchOptionsRequest(this.webMvcProperties.isDispatchOptionsRequest());dispatcherServlet.setDispatchTraceRequest(this.webMvcProperties.isDispatchTraceRequest());dispatcherServlet.setThrowExceptionIfNoHandlerFound(this.webMvcProperties.isThrowExceptionIfNoHandlerFound());return dispatcherServlet;}@Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)public ServletRegistrationBean dispatcherServletRegistration() {// 直接使用DispatcherServlet和server配置中的servletPath路径构造ServletRegistrationBean// ServletRegistrationBean实现了ServletContextInitializer接口,在onStartup方法中对应的Servlet注册到Servlet容器中// 所以这里DispatcherServlet会被注册到Servlet容器中,对应的urlMapping为server.servletPath配置ServletRegistrationBean registration = new ServletRegistrationBean(dispatcherServlet(), this.server.getServletMapping());registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);if (this.multipartConfig != null) {registration.setMultipartConfig(this.multipartConfig);}return registration;}@Bean // 构造文件上传相关的bean@ConditionalOnBean(MultipartResolver.class)@ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)public MultipartResolver multipartResolver(MultipartResolver resolver) {return resolver;}}

ServletRegistrationBean实现了ServletContextInitializer接口,是个Servlet初始化器,onStartup方法代码:

@Override
public void onStartup(ServletContext servletContext) throws ServletException {Assert.notNull(this.servlet, "Servlet must not be null");String name = getServletName();if (!isEnabled()) {logger.info("Servlet " + name + " was not registered (disabled)");return;}logger.info("Mapping servlet: '" + name + "' to " + this.urlMappings);// 把servlet添加到Servlet容器中,Servlet容器启动的时候会加载这个ServletDynamic added = servletContext.addServlet(name, this.servlet);if (added == null) {logger.info("Servlet " + name + " was not registered "+ "(possibly already registered?)");return;}// 进行Servlet的一些配置,比如urlMapping,loadOnStartup等configure(added);
}

类似ServletRegistrationBean的还有ServletListenerRegistrationBean和FilterRegistrationBean,它们都是Servlet初始化器,分别都是在Servlet容器中添加Listener和Filter。

1个小漏洞:如果定义了一个名字为dispatcherServlet的bean,但是它不是DispatcherServlet类型,那么DispatcherServlet就不会被构造,@RestController和@Controller注解的控制器就没办法生效:

@Bean(name = "dispatcherServlet")
public Object test() {return new Object();
}

内置Servlet容器的创建和启动

web程序对应的Spring容器是AnnotationConfigEmbeddedWebApplicationContext,继承自EmbeddedWebApplicationContext。在onRefresh方法中会去创建内置Servlet容器:

@Override
protected void onRefresh() {super.onRefresh();try {// 创建内置Servlet容器createEmbeddedServletContainer();}catch (Throwable ex) {throw new ApplicationContextException("Unable to start embedded container",ex);}
}private void createEmbeddedServletContainer() {EmbeddedServletContainer localContainer = this.embeddedServletContainer;ServletContext localServletContext = getServletContext();// 内置Servlet容器和ServletContext都还没初始化的时候执行if (localContainer == null && localServletContext == null) {// 从Spring容器中获取EmbeddedServletContainerFactory,如果EmbeddedServletContainerFactory不存在或者有多个的话会抛出异常中止程序EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();// 获取Servlet初始化器并创建Servlet容器,依次调用Servlet初始化器中的onStartup方法this.embeddedServletContainer = containerFactory.getEmbeddedServletContainer(getSelfInitializer());}// 内置Servlet容器已经初始化但是ServletContext还没初始化的时候执行else if (localServletContext != null) {try {// 对已经存在的Servlet容器依次调用Servlet初始化器中的onStartup方法getSelfInitializer().onStartup(localServletContext);}catch (ServletException ex) {throw new ApplicationContextException("Cannot initialize servlet context",ex);}}initPropertySources();}

getSelfInitializer方法获得的Servlet初始化器内部会去构造一个ServletContextInitializerBeans(Servlet初始化器的集合),ServletContextInitializerBeans构造的时候会去Spring容器中查找ServletContextInitializer类型的bean,其中ServletRegistrationBean、FilterRegistrationBean、ServletListenerRegistrationBean会被找出(如果有定义),这3种ServletContextInitializer会在onStartup方法中将Servlet、Filter、Listener添加到Servlet容器中(如果我们只定义了Servlet、Filter或者Listener,ServletContextInitializerBeans内部会调用addAdaptableBeans方法把它们包装成RegistrationBean):

// selfInitialize方法内部调用的getServletContextInitializerBeans方法获得ServletContextInitializerBeans
protected Collection<ServletContextInitializer> getServletContextInitializerBeans() {return new ServletContextInitializerBeans(getBeanFactory());
}private void addServletContextInitializerBean(String beanName,ServletContextInitializer initializer, ListableBeanFactory beanFactory) {if (initializer instanceof ServletRegistrationBean) {Servlet source = ((ServletRegistrationBean) initializer).getServlet();addServletContextInitializerBean(Servlet.class, beanName, initializer,beanFactory, source);}else if (initializer instanceof FilterRegistrationBean) {Filter source = ((FilterRegistrationBean) initializer).getFilter();addServletContextInitializerBean(Filter.class, beanName, initializer,beanFactory, source);}else if (initializer instanceof DelegatingFilterProxyRegistrationBean) {String source = ((DelegatingFilterProxyRegistrationBean) initializer).getTargetBeanName();addServletContextInitializerBean(Filter.class, beanName, initializer,beanFactory, source);}else if (initializer instanceof ServletListenerRegistrationBean) {EventListener source = ((ServletListenerRegistrationBean<?>) initializer).getListener();addServletContextInitializerBean(EventListener.class, beanName, initializer,beanFactory, source);}else {addServletContextInitializerBean(ServletContextInitializer.class, beanName,initializer, beanFactory, null);}}

Servlet容器创建完毕之后在finishRefresh方法中会去启动:

@Override
protected void finishRefresh() {super.finishRefresh();// 调用startEmbeddedServletContainer方法EmbeddedServletContainer localContainer = startEmbeddedServletContainer();if (localContainer != null) {// 发布EmbeddedServletContainerInitializedEvent事件publishEvent(new EmbeddedServletContainerInitializedEvent(this, localContainer));}
}private EmbeddedServletContainer startEmbeddedServletContainer() {// 先得到在onRefresh方法中构造的Servlet容器embeddedServletContainerEmbeddedServletContainer localContainer = this.embeddedServletContainer;if (localContainer != null) {// 启动localContainer.start();}return localContainer;}

自定义Servlet、Filter、Listener

SpringBoot默认只会添加一个Servlet,也就是DispatcherServlet,如果我们想添加自定义的Servlet或者是Filter还是Listener,有以下几种方法。

1.在Spring容器中声明ServletRegistrationBean、FilterRegistrationBean或者ServletListenerRegistrationBean。原理在DispatcherServlet的构造章节中已经说明

@Bean
public ServletRegistrationBean customServlet() {return new ServletRegistrationBean(new CustomServlet(), "/custom");
}private static class CustomServlet extends HttpServlet {@Overrideprotected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {resp.getWriter().write("receive by custom servlet");}
}

2.@ServletComponentScan注解和@WebServlet、@WebFilter以及@WebListener注解配合使用。@ServletComponentScan注解启用ImportServletComponentScanRegistrar类,是个ImportBeanDefinitionRegistrar接口的实现类,会被Spring容器所解析。ServletComponentScanRegistrar内部会解析@ServletComponentScan注解,然后会在Spring容器中注册ServletComponentRegisteringPostProcessor,是个BeanFactoryPostProcessor,会去解析扫描出来的类是不是有@WebServlet、@WebListener、@WebFilter这3种注解,有的话把这3种类型的类转换成ServletRegistrationBean、FilterRegistrationBean或者ServletListenerRegistrationBean,然后让Spring容器去解析:

@SpringBootApplication
@ServletComponentScan
public class EmbeddedServletApplication { ... }@WebServlet(urlPatterns = "/simple")
public class SimpleServlet extends HttpServlet {@Overrideprotected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {resp.getWriter().write("receive by SimpleServlet");}}

3.在Spring容器中声明Servlet、Filter或者Listener。因为在ServletContextInitializerBeans内部会去调用addAdaptableBeans方法把它们包装成ServletRegistrationBean:

@Bean(name = "dispatcherServlet")
public DispatcherServlet myDispatcherServlet() {return new DispatcherServlet();
}

Whitelabel Error Page原理

为什么SpringBoot的程序里Controller发生了错误,我们没有进行异常的捕捉,会跳转到Whitelabel Error Page页面,这是如何实现的?

SpringBoot內部提供了一个ErrorController叫做BasicErrorController,对应的@RequestMapping地址为 “server.error.path” 配置 或者 “error.path” 配置,这2个配置没配的话默认是/error,之前分析过ErrorPageCustomizer这个定制化器会把ErrorPage添加到Servlet容器中(这个ErrorPage的path就是上面说的那2个配置),这样Servlet容器发生错误的时候就会访问ErrorPage配置的path,所以程序发生异常且没有被catch的话,就会走Servlet容器配置的ErrorPage。下面这段代码是BasicErrorController对应的处理请求方法:

@RequestMapping(produces = "text/html")
public ModelAndView errorHtml(HttpServletRequest request,HttpServletResponse response) {// 设置响应码response.setStatus(getStatus(request).value());// 设置一些信息,比如timestamp、statusCode、错误message等Map<String, Object> model = getErrorAttributes(request,isIncludeStackTrace(request, MediaType.TEXT_HTML));// 返回error视图return new ModelAndView("error", model);
}

这里名字为error视图会被BeanNameViewResolver这个视图解析器解析,它会去Spring容器中找出name为error的View,error这个bean在ErrorMvcAutoConfiguration自动化配置类里定义,它返回了一个SpelView视图,也就是刚才见到的Whitelabel Error Page(error.whitelabel.enabled配置需要是true,否则WhitelabelErrorViewConfiguration自动化配置类不会被注册):

@Configuration
@ConditionalOnProperty(prefix = "server.error.whitelabel", name = "enabled", matchIfMissing = true)
@Conditional(ErrorTemplateMissingCondition.class)
protected static class WhitelabelErrorViewConfiguration {// Whitelabel Error Pageprivate final SpelView defaultErrorView = new SpelView("<html><body><h1>Whitelabel Error Page</h1>"+ "<p>This application has no explicit mapping for /error, so you are seeing this as a fallback.</p>"+ "<div id='created'>${timestamp}</div>"+ "<div>There was an unexpected error (type=${error}, status=${status}).</div>"+ "<div>${message}</div></body></html>");@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方法:

@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,AnnotatedTypeMetadata metadata) {// 从spring.factories文件中找出key为TemplateAvailabilityProvider为类,TemplateAvailabilityProvider用来查询视图是否可用List<TemplateAvailabilityProvider> availabilityProviders = SpringFactoriesLoader.loadFactories(TemplateAvailabilityProvider.class,context.getClassLoader());// 遍历各个TemplateAvailabilityProviderfor (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 FreeMarkerTemplateAvailabilityProviderimplements TemplateAvailabilityProvider {@Overridepublic 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视图。

一些测试代码: https://github.com/fangjian0423/springboot-analysis/tree/master/springboot-embedded-servlet-conatiner

备用:法国队4-2克罗地亚 2018-07-16

ConfigurationClassPostProcessor这个processor是优先级最高的被执行的processor(实现了PriorityOrdered接口)。
这个ConfigurationClassPostProcessor会去BeanFactory中找出所有有@Configuration注解的bean
【找出所有有@Configuration注解的bean,EmbeddedServletContainerAutoConfiguration会被找到  --
http://fangjian0423.github.io/2017/05/10/springboot-context-refresh/ - 
http://7x2wh6.com1.z0.glb.clouddn.com/configuration-annotation-process.png 图】

EmbeddedServletContainerAutoConfiguration  @Import(BeanPostProcessorsRegistrar.class)

SpringApplication refreshContext(context);  -AbstractApplicationContext refresh()  invokeBeanFactoryPostProcessors(beanFactory);
PostProcessorRegistrationDelegate  invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry); postProcessor.postProcessBeanDefinitionRegistry(registry);
-- ConfigurationClassPostProcessor类 -- (分叉1 parser.parse(candidates);-- 
 BeanPostProcessorsRegistrar -setBeanFactory)-this.reader.loadBeanDefinitions(configClasses); 
--ConfigurationClassBeanDefinitionReader loadBeanDefinitions   loadBeanDefinitionsForConfigurationClass --loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
--BeanPostProcessorsRegistrar registerBeanDefinitions EmbeddedServletContainerCustomizerBeanPostProcessor 
--postProcessBeforeInitialization (自动执行,因为BeanPostProcessor, BeanFactoryAware) customizer.customize(bean); ServerProperties

spring onfresh时  EmbeddedServletContainerAutoConfiguration里的 EmbeddedTomcat new TomcatEmbeddedServletContainerFactory();

SpringBoot源码分析之内置Servlet容器相关推荐

  1. Springboot源码分析之内嵌tomcat源码分析

    Springboot源码是内嵌tomcat的,这个和完整的tomcat还是不同. 内嵌tomcat的源码在tomcat-embed-core等3个jar包里 展开tomcat-embed-core的c ...

  2. MyBatis 源码分析 - 内置数据源

    1.简介 本篇文章将向大家介绍 MyBatis 内置数据源的实现逻辑.搞懂这些数据源的实现,可使大家对数据源有更深入的认识.同时在配置这些数据源时,也会更清楚每种属性的意义和用途.因此,如果大家想知其 ...

  3. beanfactorypostprocessor_Spring源码分析(六)容器的扩展点(BeanFactoryPostProcessor)

    之前的文章我写了BeanDefinition的基本概念和合并,其中很对次提到了容器的扩展点,这篇文章就写这方面的知识.这部分的内容主要涉及到官网的1.8小节.按照官网介绍来说,容器的扩展点可以分为三类 ...

  4. SpringBoot源码分析(二)之自动装配demo

    SpringBoot源码分析(二)之自动装配demo 文章目录 SpringBoot源码分析(二)之自动装配demo 前言 一.创建RedissonTemplate的Maven服务 二.创建测试服务 ...

  5. Springboot源码分析第一弹 - 自动装配实现

    Springboot就不用多了吧,解放Java开发双手的神器. 最显著的特点就是,去配置化,自动装配,自动配置.让开发人员只需要注重业务的开发 今天就来了解一下自动装配的源码是怎么实现的 预先准备 直 ...

  6. springboot源码分析

    快速开发底层原理 SpringBoot核心理念 能够实现帮助开发者快速的整合第三方框架(Spring.Mybatis.hibernate) 原理:Maven依赖封装整合和自定义starter. 完全去 ...

  7. 精尽Spring Boot源码分析 - 内嵌Tomcat容器的实现

    概述 我们知道 Spring Boot 能够创建独立的 Spring 应用,内部嵌入 Tomcat 容器(Jetty.Undertow),让我们的 jar 无需放入 Servlet 容器就能直接运行. ...

  8. SpringBoot源码分析之SpringBoot可执行文件解析

    SpringBoot提供了一个插件spring-boot-maven-plugin用于把程序打包成一个可执行的jar包.在pom文件里加入这个插件即可: 1 2 3 4 5 6 7 8 <bui ...

  9. Spring Ioc 源码分析(一)--Spring Ioc容器的加载

    1.目标:熟练使用spring,并分析其源码,了解其中的思想.这篇主要介绍spring ioc 容器的加载 2.前提条件:会使用debug 3.源码分析方法:Intellj idea debug 模式 ...

最新文章

  1. 我离开Uber,开始自主创业后......
  2. 抽象类在ASP.NET的学习与应用
  3. Metasploit漏洞利用基础教程要出版了
  4. 【算法+图像处理】2D卷积与快速卷积算法C语言实现
  5. 非公平锁和公平锁在reetrantlock里的实现过程是怎样的
  6. Linux 下 离线下载服务部署 CCAA的安装使用
  7. Citrix Provisioning Services:Windows 10 VDA计算机的登录性
  8. lin通讯从节点同步间隔场_汽车行业必须知识--CAN FD通讯
  9. MyBatis学习总结(24)——Mybatis常见问题汇总
  10. 【Matlab学习笔记】【函数学习】max()和max(max())的区别
  11. C语言编程QQ管理系统,c语言制作学生管理系统srrpqq67.doc
  12. Keli 编译遇到 *** FATAL ERROR L250: CODE SIZE LIMIT IN RESTRICTED VERSION EXCEEDED且已经破解
  13. wps表格在拟合曲线找点_excel拟合曲线函数表达式,excel散点拟合出来的公式看不懂。请问怎么写入表格使用?...
  14. Xshell6、Xftp6【官方免费版】下载
  15. android3d画廊自动切换,Android ViewPager打造3D画廊
  16. 常见音频格式开源解码库汇总(持续更新)
  17. Java教程:如何使用Jib插件容器化SpringBoot应用?
  18. 小米Max详细刷成开发版开启Root超级权限的经验
  19. Maven项目进行打包出现 The POM for xxxx is missing, no dependency information available
  20. 解决Perl TK 中文输入,操作问题

热门文章

  1. fms 连 mysql_FMS服务器
  2. 红米4X-ROOT-解锁
  3. linux查看wifi信号命令_如何用wifi-linux检测AP信号强度
  4. 【Unity3d】随机生成地图(Map)
  5. 宇视IPC9312I-FW通道2无录像
  6. 如何用计算机录麦克风的声音,怎么让电脑录音只录麦克风里的声音?
  7. 数据挖掘竞赛——糖尿病遗传风险检测挑战赛Baseline
  8. 工业相机SDK使用Python打开无法显示图像
  9. 图片转换成pdf格式如何转换?
  10. MCU、Linux实现OTA固件升级要点