Spring Boot下的Servlet、Filter、Listener加载流程分析
本文将详细分析Spring Boot中Servlet、Filter、Listener的加载流程,包括DispatcherServlet的注册。读者阅读之前,最好先阅读Spring MVC和Servlet规范相关的内容,参考Spring MVC。
一、Servlet、Filter、Listener的注册方式
下面先介绍Spring Boot中Servlet、Filter、Listener三大组件的注册方式,然后再详细分析这些注册方式的原理。
1.Servlet 3.0注解+@ServletComponentScan
Spring Boot兼容Servlet 3.0一系列以 @Web* 开头的注解:@WebServlet,@WebFilter,@WebListener。
@WebServlet("/hello")
public class HelloServlet extends HttpServlet {}
@WebFilter("/hello/*")
public class HelloFilter implements Filter {}
然后在启动类中添加@ServletComponentScan注解,以扫描Servlet 3.0 @Web* 注解的类。
@SpringBootApplication
@ServletComponentScan
public class SpringBootServletStartApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootServletStartApplication.class, args);
}
}
2.RegistrationBean
@Bean
public ServletRegistrationBean servletRegistrationBean() {
ServletRegistrationBean registrationBean = new ServletRegistrationBean();
registrationBean.addUrlMappings("/hello");
registrationBean.setServlet(new HelloServlet());
return registrationBean;
}
@Bean
public FilterRegistrationBean filterRegistrationBean() {
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(new HelloFilter());
registrationBean.addUrlPatterns("/hello/*");
return registrationBean;
}
ServletRegistrationBean和FilterRegistrationBean都继承自RegistrationBean,RegistrationBean是Spring Boot中广泛应用的一个注册类,负责把Servlet、Filter、Listener给容器化,使他们被Spring托管,并且完成自身对Web容器的注册。
从图中可以看出RegistrationBean的地位,它的几个实现类作用分别是:帮助容器注册Filter、Servlet、Listener。另外RegistrationBean实现了ServletContextInitializer接口,这个接口将会是下面分析的核心接口,先了解一下,RegistrationBean是它的抽象实现。
二、Servlet、Filter、Listener的加载流程
前提:分析的Spring Boot版本是1.5.3.RELEASE,典型的Spring Boot Web项目,使用Tomcat嵌入式容器。
在Spring Boot应用启动过程中,由于Spring Boot自动配置机制的存在,对于Web模块,以下自动配置类将会生效(按照被容器处理的顺序排序):EmbeddedServletContainerAutoConfiguration、DispatcherServletAutoConfiguration和WebMvcAutoConfiguration。
- EmbeddedServletContainerAutoConfiguration用于注册一个TomcatEmbeddedServletContainerFactory Bean:
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@ConditionalOnWebApplication
@Import(BeanPostProcessorsRegistrar.class)
public class EmbeddedServletContainerAutoConfiguration {
@Configuration
@ConditionalOnClass({ Servlet.class, Tomcat.class })
@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedTomcat {
@Bean
public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() {
return new TomcatEmbeddedServletContainerFactory(); // 注册Bean
}
}
}
- DispatcherServletAutoConfiguration内嵌两个配置类DispatcherServletConfiguration和DispatcherServletRegistrationConfiguration,前者用于配置DispatcherServlet Bean,后者用于注册DispatcherServlet。
// DispatcherServletConfiguration
@Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServlet dispatcherServlet() {
DispatcherServlet dispatcherServlet = new DispatcherServlet();
dispatcherServlet.setDispatchOptionsRequest(
this.webMvcProperties.isDispatchOptionsRequest());
dispatcherServlet.setDispatchTraceRequest(
this.webMvcProperties.isDispatchTraceRequest());
dispatcherServlet.setThrowExceptionIfNoHandlerFound(
this.webMvcProperties.isThrowExceptionIfNoHandlerFound());
return dispatcherServlet;
}
// DispatcherServletRegistrationConfiguration
@Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
@ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public ServletRegistrationBean dispatcherServletRegistration(
DispatcherServlet dispatcherServlet) {
ServletRegistrationBean registration = new ServletRegistrationBean(
dispatcherServlet, this.serverProperties.getServletMapping());
registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
registration.setLoadOnStartup(
this.webMvcProperties.getServlet().getLoadOnStartup()); // -1
if (this.multipartConfig != null) {
registration.setMultipartConfig(this.multipartConfig);
}
return registration;
}
可以看到DispatcherServlet的注册是通过ServletRegistrationBean的方式完成的。
- WebMvcAutoConfiguration内嵌EnableWebMvcConfiguration配置类,用于注册HandlerMapping、HandleAdapter等Web组件。
@Bean
public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
RequestMappingHandlerAdapter adapter = super.requestMappingHandlerAdapter();
adapter.setIgnoreDefaultModelOnRedirect(this.mvcProperties == null ? true
: this.mvcProperties.isIgnoreDefaultModelOnRedirect());
return adapter;
}
@Bean
@Primary
public RequestMappingHandlerMapping requestMappingHandlerMapping() {
// Must be @Primary for MvcUriComponentsBuilder to work
return super.requestMappingHandlerMapping();
}
Spring Boot应用启动过程中使用的是AnnotationConfigEmbeddedWebApplicationContext,继承自EmbeddedWebApplicationContext。在AnnotationConfigEmbeddedWebApplicationContext刷新过程中,会调用EmbeddedWebApplicationContext.onRefresh方法:
protected void onRefresh() {
super.onRefresh();
try {
createEmbeddedServletContainer();
}
catch (Throwable ex) {
throw new ApplicationContextException("Unable to start embedded container",
ex);
}
}
private void createEmbeddedServletContainer() {
EmbeddedServletContainer localContainer = this.embeddedServletContainer;
ServletContext localServletContext = getServletContext();
if (localContainer == null && localServletContext == null) {
EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();
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();
}
在createEmbeddedServletContainer方法中调用TomcatEmbeddedServletContainerFactory.getEmbeddedServletContainer方法创建EmbeddedServletContainer。在getEmbeddedServletContainer调用时传入了getSelfInitializer()的返回值。getSelfInitializer()方法如下:
private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
return new ServletContextInitializer() {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
selfInitialize(servletContext);
}
};
}
getSelfInitializer()方法返回了一个匿名的ServletContextInitializer实现。该ServletContextInitializer实现是注册Servlet、Filter、Listener的基础。
TomcatEmbeddedServletContainerFactory.getEmbeddedServletContainer方法创建EmbeddedServletContainer的过程如下:
public EmbeddedServletContainer getEmbeddedServletContainer(
ServletContextInitializer... initializers) {
// 前面代码省略
prepareContext(tomcat.getHost(), initializers); // 1
return getTomcatEmbeddedServletContainer(tomcat); // 2
}
在getEmbeddedServletContainer方法中先看1处的代码实现:
protected void prepareContext(Host host, ServletContextInitializer[] initializers) {
// 前面省略
// 合并ServletContextInitializer
ServletContextInitializer[] initializersToUse = mergeInitializers(initializers);
configureContext(context, initializersToUse);
// ...
}
protected void configureContext(Context context,
ServletContextInitializer[] initializers) {
TomcatStarter starter = new TomcatStarter(initializers); // 创建TomcatStarter
context.addServletContainerInitializer(starter, NO_CLASSES); // 添加到Context
// ...
}
在prepareContext中调用了configureContext方法,在configureContext方法中创建了TomcatStarter实例,并在构造器中传入了上面匿名的ServletContextInitializer实现,并最终添加到了Tomcat Context,因此Web容器启动过程中会回调其onStartup方法。下面看TomcatStarter的实现(实现了ServletContainerInitializer接口):
class TomcatStarter implements ServletContainerInitializer {
private final ServletContextInitializer[] initializers;
TomcatStarter(ServletContextInitializer[] initializers) {
this.initializers = initializers;
}
@Override
public void onStartup(Set<Class<?>> classes, ServletContext servletContext)
throws ServletException {
try {
for (ServletContextInitializer initializer : this.initializers) {
initializer.onStartup(servletContext);
}
}
catch (Exception ex) {
// ...
}
}
可以看到TomcatStarter所做的事情是在它的onStartup方法被回调时,依次调用传入的ServletContextInitializer的onStartup方法。那TomcatStarter的onStartup方法是什么时候被回调的呢?回到getEmbeddedServletContainer方法处:
public EmbeddedServletContainer getEmbeddedServletContainer(
ServletContextInitializer... initializers) {
// 前面代码省略
prepareContext(tomcat.getHost(), initializers); // 1
return getTomcatEmbeddedServletContainer(tomcat); // 2
}
下面分析getEmbeddedServletContainer方法中代码2处的逻辑:
protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(
Tomcat tomcat) {
return new TomcatEmbeddedServletContainer(tomcat, getPort() >= 0);
}
public TomcatEmbeddedServletContainer(Tomcat tomcat, boolean autoStart) {
Assert.notNull(tomcat, "Tomcat Server must not be null");
this.tomcat = tomcat;
this.autoStart = autoStart;
initialize(); // 启动Tomcat Web容器
}
private void initialize() throws EmbeddedServletContainerException {
TomcatEmbeddedServletContainer.logger
.info("Tomcat initialized with port(s): " + getPortsDescription(false));
synchronized (this.monitor) {
try {
addInstanceIdToEngineName();
try {
// ...
// Start the server to trigger initialization listeners
this.tomcat.start(); // 启动Tomcat Server
// ...
}
catch (Exception ex) {
containerCounter.decrementAndGet();
throw ex;
}
}
catch (Exception ex) {
throw new EmbeddedServletContainerException(
"Unable to start embedded Tomcat", ex);
}
}
}
可以看到TomcatEmbeddedServletContainer在初始化时调用了initialize()方法,并启动了Tomcat Server,来回调触发ServletContainerInitializer的onStartup方法。于是TomcatStarter中传入的ServletContextInitializer的onStartup方法得以依次回调(Tomcat线程,非主线程)。下面就来看一下上文中getSelfInitializer()返回的ServletContextInitializer的onStartup方法的逻辑。
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 {
prepareEmbeddedWebApplicationContext(servletContext);
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
ExistingWebApplicationScopes existingScopes = new ExistingWebApplicationScopes(
beanFactory);
WebApplicationContextUtils.registerWebApplicationScopes(beanFactory,
getServletContext());
existingScopes.restore();
WebApplicationContextUtils.registerEnvironmentBeans(beanFactory,
getServletContext());
for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
beans.onStartup(servletContext); // 重点
}
}
getSelfInitializer方法调用了selfInitialize方法,selfInitialize方法首先调用prepareEmbeddedWebApplicationContext方法,用于向ServletContext保存ROOT应用上下文的引用,即当前的AnnotationConfigEmbeddedWebApplicationContext实例。然后注册了web应用的Scope和环境Bean。下面的for循环是重点:
for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
beans.onStartup(servletContext);
}
Servlet、Filter、Listener的注册就发生在这里。首先看下getServletContextInitializerBeans()获取ServletContextInitializer Beans的逻辑:
// Returns {@link ServletContextInitializer}s that should be used with the embedded
// Servlet context. By default this method will first attempt to find
// {@link ServletContextInitializer}, {@link Servlet}, {@link Filter} and certain
// {@link EventListener} beans.
// @return the servlet initializer beans
protected Collection<ServletContextInitializer> getServletContextInitializerBeans() {
return new ServletContextInitializerBeans(getBeanFactory());
}
public ServletContextInitializerBeans(ListableBeanFactory beanFactory) {
this.initializers = new LinkedMultiValueMap<Class<?>, ServletContextInitializer>();
addServletContextInitializerBeans(beanFactory); // 从容器获取ServletContextInitializer Beans
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) {
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);
}
}
getServletContextInitializerBeans()方法主要从Spring容器中获取ServletContextInitializer Bean并排序。因此,第二部分开头介绍的DispatcherServlet ServletRegistrationBean也是在此时从Spring容器中获取的。getServletContextInitializerBeans()方法得到的为ServletRegistrationBean、FilterRegistrationBean、ServletListenerRegistrationBean等实例,均实现了ServletContextInitializer接口。因此,在上面的for循环中完成了Servlet、Filter、Listener的注册。
for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
beans.onStartup(servletContext); // Servlet、Filter、Listener注册
}
可以看到,Spring Boot中只要依赖于ServletContextInitializer接口,就可以实现Servlet、Filter、Listener的注册。
1.Servlet 3.0注解+@ServletComponentScan注册方式的解释
@Import(ServletComponentScanRegistrar.class)
public @interface ServletComponentScan {}
当@ServletComponentScan注解配置在@Configuration类上时,导入了ServletComponentScanRegistrar类。该类的左右是向Spring容器中注册ServletComponentRegisteringPostProcessor BeanDefinition。ServletComponentRegisteringPostProcessor是一个BeanFactoryPostProcessor,主要用于扫描类路径下注解了Servlet 3.0 @Web*注解的类。下面看下ServletComponentRegisteringPostProcessor的postProcessBeanFactory方法的逻辑:
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
throws BeansException {
if (isRunningInEmbeddedContainer()) {
ClassPathScanningCandidateComponentProvider componentProvider = createComponentProvider(); // 组件扫描
for (String packageToScan : this.packagesToScan) {
scanPackage(componentProvider, packageToScan);
}
}
}
private void scanPackage(
ClassPathScanningCandidateComponentProvider componentProvider,
String packageToScan) {
for (BeanDefinition candidate : componentProvider
.findCandidateComponents(packageToScan)) {
if (candidate instanceof ScannedGenericBeanDefinition) {
for (ServletComponentHandler handler : HANDLERS) {
handler.handle(((ScannedGenericBeanDefinition) candidate),
(BeanDefinitionRegistry) this.applicationContext);
}
}
}
}
scanPackage方法中的HANDLERS变量包含WebServletHandler、WebFilterHandler、WebListenerHandler实例,分别处理@WebServlet、@WebFilter、@WebListener注解,将对应注解的类创建成ServletRegistrationBean、FilterRegistrationBean、ServletListenerRegistrationBean实例,注册到Spring容器。因此,应用启动过程中能够注册将Servlet、Filter、Listener注册到Tomcat Server Web容器中并生效,由于ServletRegistrationBean、FilterRegistrationBean、ServletListenerRegistrationBean都实现了ServletContextInitializer接口,因此其核心还是ServletContextInitializer接口。
2.RegistrationBean的解释
根据上面的分析,由于ServletRegistrationBean、FilterRegistrationBean、ServletListenerRegistrationBean都实现了ServletContextInitializer接口,因此直接定义ServletRegistrationBean、FilterRegistrationBean、ServletListenerRegistrationBean的Spring Bean配置,也能将Servlet、Filter、Listener注册到Tomcat Server Web容器中并生效。
3.第三种注册方式
根据上面的分析,其实可以直接定义ServletContextInitializer接口的Spring Bean,也能将Servlet、Filter、Listener注册到Tomcat Server Web容器中并生效。如下所示:
@Component
public class MyServletContextInitializer implements ServletContextInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
System.out.println("创建 HelloServlet...");
ServletRegistration.Dynamic servletRegistration =
servletContext.addServlet(HelloServlet.class.getSimpleName(), HelloServlet.class);
servletRegistration.addMapping("/hello");
System.out.println("创建 HelloFilter...");
FilterRegistration.Dynamic filterRegistration =
servletContext.addFilter(HelloFilter.class.getSimpleName(), HelloFilter.class);
EnumSet<DispatcherType> set = EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD);
filterRegistration.addMappingForUrlPatterns(set, true, "/hello/*");
}
}
上述代码的作用与前两种注册方式原理一致,不过使用上还是前两种注册方式比较常用。
三、DispatcherServlet的细节(重要)
在Spring MVC中,DispatcherServlet默认情况下会在容器启动时直接初始化,调用其init方法。而在Spring Boot中,DispatcherServlet在其注册到ServletContext时初始化,默认情况下会在第一次HTTP请求到来时调用其init方法(loadOnStartup = -1)。init方法调用时的不同点如下:
// FrameworkServlet
protected WebApplicationContext initWebApplicationContext() {
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
if (cwac.getParent() == null) {
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
wac = findWebApplicationContext();
}
if (wac == null) {
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
onRefresh(wac);
}
if (this.publishContext) {
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
"' as ServletContext attribute with name [" + attrName + "]");
}
}
return wac;
}
在Spring Boot中,DispatcherServlet.init方法调用时应用已启动完成,DispatcherServlet实例已由Spring容器初始化(自动配置机制)并设置ApplicationContext,即AnnotationConfigEmbeddedWebApplicationContext。
// FrameworkServlet
public void setApplicationContext(ApplicationContext applicationContext) {
if (this.webApplicationContext == null && applicationContext instanceof WebApplicationContext) {
this.webApplicationContext = (WebApplicationContext) applicationContext;
this.webApplicationContextInjected = true;
}
}
因此在DispatcherServlet.init方法调用时,接着调用父类FrameworkServlet的initWebApplicationContext()方法,其this.webApplicationContext != null(wac != null)
。而Spring MVC中,initWebApplicationContext()调用时this.webApplicationContext == null
,因此Spring MVC会创建两个Web应用上下文;而Spring Boot中只创建了一个Web应用上下文,即AnnotationConfigEmbeddedWebApplicationContext。
四、总结
根据前面的介绍,在Spring Boot中,通过定义一个TomcatStarter(ServletContainerInitializer实现,Servlet 3.0 API),将Tomcat Web容器与Spring容器(AnnotationConfigEmbeddedWebApplicationContext)挂钩。Spring Boot提供ServletContextInitializer接口,开发者间接或者直接定义ServletContextInitializer接口的实现BeanDefinition,由Spring容器管理,然后由TomcatStarter触发这些ServletContextInitializer的onStartup方法,实现Servlet、Filter、Listener到ServletContext的注册。
下面是总结的Spring MVC与Spring Boot用到的Servlet API与自定义API的区别:
Spring MVC:
ServletContextListener Servlet API --> 用户
ServletContainerInitializer Servlet API --> 框架: SpringServletContainerInitializer(SPI)
WebApplicationInitializer Spring API --> 用户
Spring Boot:
ServletContainerInitializer Servlet API --> TomcatStarter
ServletContextInitializer Spring Boot API -> 用户