本文基于:
SpringBoot-2.1.4
方式1. Servlet3.0 之前使用 web.xml 配置监听器,Servlet,Filter 就可以了,如下:
org.springframework.web.context.ContextLoaderListener
...
...
ContextLoaderListener
实现了 ServletContextListener,当 Servlet 容器启动时,contextInitialized(ServletContextEvent event)
方法会被回调,Spring 会做相应的 ApplicationContext 初始化。
方式2. Servlet3.0 之后,可以使用编程方式启动 Spring(不使用 Springboot)
该方式得益于 Servlet3.0 的 ServletContainerInitializer
接口,该接口使用 SPI 加载实现类,Spring 的SpringServletContainerInitializer
实现了该接口,源码如下:
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
@Override
public void onStartup(@Nullable Set> webAppInitializerClasses, ServletContext servletContext)
throws ServletException {
...
}
}
HandlesTypes: HandlesTypes的作用是其配置的类会被注入到
onStartup
方法的webAppInitializerClasses
参数。
我们也可以自己实现 ServletContainerInitializer
来做我们想做的事情。但是一般我们只要实现了 AbstractAnnotationConfigDispatcherServletInitializer
就可以使用 Spring(非 SpringBoog)。
方式3. SpringBoot war包启动
如果你是使用的 SpringBoot war包启动的话,你的启动类可能是这样的:
@SpringBootApplication
public class SpringTestApplication extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(SpringTestApplication.class);
}
}
SpringBootServletInitializer
实现了 WebApplicationInitializer
,就像上面提到的,我们的启动类会被注入到 SpringServletContainerInitializer
的 onStartup
方法。在 onStartup
方法中 Spring 会做相应的ApplicationContext 初始化。看 SpringBootServletInitializer
的源码你就会发现,其实 war 包启动的方式最后还是会使用到 SpringApplication#run。
方式4. SpringBoot jar方式启动
该方式,Spring 会通过 main 方法中的 SpringApplication.run(SpringTestApplication.class, args);
来做相应的 ApplicationContext 初始化,并且它使用嵌入式的 Tomcat 来加载 Servlet
,但是它没有完全遵守 servlet3.0 的规范,你可以尝试在 SpringServletContainerInitializer
中打个断点,你会发现它并没有被运行。它其实会进入到TomcatStarter
,TomcatStarter
和 SpringServletContainerInitializer
一样也实现了 ServletContainerInitializer
接口,监听器,Servlet,Filter 的注册依靠的是 ServletContextInitializer。具体请看 spring-boot中tomcat的启动过程
对于为什么不使用 SpringServletContainerInitializer
? SpringBoot 在 github 的 issues 中也做了回答。Springboot 考虑到了如下的问题,我们在使用 Springboot 时,开发阶段一般都是使用内嵌 tomcat 容器,但部署时却存在两种选择:一种是打成 jar 包,使用 java -jar 的方式运行;另一种是打成 war 包,交给外置容器去运行。前者就会导致容器搜索算法出现问题,因为这是 jar 包的运行策略,不会按照 servlet3.0 的策略去加载 ServletContainerInitializer
!最后作者还提供了一个替代选项:ServletContextInitializer
。该类被使用在了 TomcatServer
中。其实 war 包启动也会使用到 ServletContextInitializer
,只要是 SpringBoot 启动都会使用 ServletContextInitializer
而不是 ServletContainerInitializer
3.1 自定义 Servler,Filter,Listener 的一种注册方式
基于以上内容,我们可以自定义 ServletContextInitializer
的实现类来实现 Servler,Filter,Listener 的注册(无论你是war包启动还是jar包启动)。其实 Spirng 已经帮我们做好了,当你去看它的实现类时,你会看到:
- ServletRegistrationBean:用来注册 Servlet
- FilterRegistrationBean:用来注册 Filter
- ServletListenerRegistrationBean:用来注册 Listener
- ...
Spring 会通过以下方法为你注册:
ServletWebServerApplicationContext.java
private void createWebServer() {
WebServer webServer = this.webServer;
ServletContext servletContext = getServletContext();
if (webServer == null && servletContext == null) { //jar包启动入口
ServletWebServerFactory factory = getWebServerFactory();
// 这一步 getSelfInitializer() 方法返回的是一个 ServletContextInitializer,
// 它负责处理所有的 Servler,Filter,Listener 的注册
this.webServer = factory.getWebServer(getSelfInitializer());
}
else if (servletContext != null) {//war包启动入口
try {
getSelfInitializer().onStartup(servletContext);
}
catch (ServletException ex) {
throw new ApplicationContextException("Cannot initialize servlet context",
ex);
}
}
initPropertySources();
}
private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
return this::selfInitialize;
}
private void selfInitialize(ServletContext servletContext) throws ServletException {
prepareWebApplicationContext(servletContext);
registerApplicationScope(servletContext);
WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
// getServletContextInitializerBeans() 会获取所有有注册的 ServletContextInitializer 实例
for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
beans.onStartup(servletContext);
}
}
题外话
- 注解的方式注册Servlet。 3.0 以后,@WebServlet,@WebFilter,@WebListener + @ServletComponentScan
- Filter 的做种注册方式,详情
总结
- Servlet 3.0 后使整个应用更加轻量了,出去繁琐的 xml 配置,甚至可以动态配置 Servlet,Filter,Listener 等。
- 无论 SpringBoot 是 war 包还是 jar 启动,它最后的都是调用的 SpringApplication#run 来启动的。