看到一些Spring的项目,其web.xml是空的,但是Spring环境依然正确启动了,颇感好奇。原来这是在Servlet3推出后,Spring就跟进的功能。

Servlet3中,提供了新的注解,可以不依赖web.xml声明Servlet/过滤器/监听器。同时还提供了一个ServletContainerInitializer接口,这个接口能够让库代码加入到应用的启动环境中来。这个特性可以看看前一篇文章:Servlet3笔记-注解和可拔插特性。Spring就是利用了这个接口来实现零配置启动。

说明一下,这里的零配置指的是没有web.xml中的配置,而不是指Spring本身的配置。

Spring零配置如何使用

一个典型的web.xml配置可能像这样(参考):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">

<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:dispatcher-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

</web-app>

如果使用Spring零配置特性,以上的xml就变成如下的Java代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class MyWebAppInitializer implements WebApplicationInitializer {

/**
* Servlet容器启动时会自动运行该方法
*/
@Override
public void onStartup(ServletContext servletContext) throws ServletException {

servletContext.setInitParameter("contextConfigLocation", "classpath:applicationContext.xml");

ServletRegistration.Dynamic registration = servletContext.addServlet("dispatcher", new DispatcherServlet());
registration.setLoadOnStartup(1);
registration.addMapping("/");
registration.setInitParameter("contextConfigLocation", "classpath:dispatcher-servlet.xml");

servletContext.addListener(new ContextLoaderListener());
}
}

为什么应用中只要实现了Spring提供的WebApplicationInitializer接口,应用在启动的时候就会触发onStartup方法呢?

Spring零配置代码分析

首先可以看到spring-web中定义了javax.servlet.ServletContainerInitializer这个文件,其中指定了org.springframework.web.SpringServletContainerInitializer这个实现类:

实现类的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// 说明只处理WebApplicationInitializer的实现类
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {

// 应用启动时会触发该方法
@Override
public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
throws ServletException {

List<WebApplicationInitializer> initializers = new LinkedList<WebApplicationInitializer>();

if (webAppInitializerClasses != null) {
for (Class<?> waiClass : webAppInitializerClasses) {
// 过滤用户定义的WebApplicationInitializer的合法实现类
if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
try {
initializers.add((WebApplicationInitializer) waiClass.newInstance());
}
catch (Throwable ex) {
throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
}
}
}
}

// 如果用户没有定义WebApplicationInitializer的实现类,那么说明用户没有使用Spring零配置特性
if (initializers.isEmpty()) {
servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
return;
}

servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
AnnotationAwareOrderComparator.sort(initializers);

// 调用WebApplicationInitializer的实现类的初始化方法
for (WebApplicationInitializer initializer : initializers) {
initializer.onStartup(servletContext);
}
}
}

SpringServletContainerInitializer是一个很简单的包装,作为应用启动触发的入口。其中把启动的流程转发到了WebApplicationInitializer实现类中。

Spring零配置别的使用方法

除了直接实现WebApplicationInitializer接口外,Spring还提供了一些实现了部分功能的抽象类来方便我们使用,就行HttpServlet之于Servlet一样:

  • org.springframework.web.context.AbstractContextLoaderInitializer
  • org.springframework.web.servlet.support.AbstractDispatcherServletInitializer
  • org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer

一般比较常见的做法是基础AbstractAnnotationConfigDispatcherServletInitializer这个类,实现其getRootConfigClassesgetServletConfigClasses这两个方法,在这两个方法中指定Spring的配置类。

参考资料