Servlet 3.0 新特性

servlet3.0 首先提供了 @WebServlet @WebFilter 等注解,这样便有了抛弃 web.xml 的第一个途径,凭借注解声明 servlet 和 filter 来做到这一点。

除了这种方式,servlet3.0 规范还提供了更强大的功能,可以在运行时动态注册 servlet ,filter,listener。以 servlet 为例,过滤器与监听器与之类似。ServletContext 为动态配置 Servlet 增加了如下方法:

  • ServletRegistration.Dynamic addServlet(String servletName,Class<? extends Servlet> servletClass)
  • ServletRegistration.Dynamic addServlet(String servletName, Servlet servlet)
  • ServletRegistration.Dynamic addServlet(String servletName, String className)
  • T createServlet(Class clazz)
  • ServletRegistration getServletRegistration(String servletName)
  • Map<String,? extends ServletRegistration> getServletRegistrations()

其中前三个方法的作用是相同的,只是参数类型不同而已;

通过 #createServlet(Class clazz) 方法创建的 Servlet,通常需要做一些自定义的配置,然后使用 #addServlet(…) 方法来将其动态注册为一个可以用于服务的 Servlet。

两个 #getServletRegistration() 方法主要用于动态为 Servlet 增加映射信息,这等价于在 web.xml 中使用 标签为存在的 Servlet 增加映射信息。

ServletContainerInitializer

ServletContainerInitializer也是 Servlet 3.0 新增的一个接口,容器在启动时使用 JAR 服务 API(JAR Service API) 来发现 ServletContainerInitializer 的实现类,并且容器将== WEB-INF/lib ==目录下 JAR 包中的类都交给该类的 #onStartup(…) 方法处理,我们通常需要在该实现类上使用 @HandlesTypes 注解来指定希望被处理的类,过滤掉不希望给 #onStartup(…) 处理的类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
.
├── pom.xml
└── src
├── main
│ ├── java
│ │ └── moe
│ │ └── cnkirito
│ │ ├── CustomServletContainerInitializer.java
│ │ ├── filter
│ │ │ └── HelloWorldFilter.java
│ │ └── servlet
│ │ └── HelloWorldServlet.java
│ └── resources
│ └── META-INF
│ └── services
│ └── javax.servlet.ServletContainerInitializer
└── test
└── java

DEMO

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
public class CustomServletContainerInitializer implements ServletContainerInitializer {

private final static String JAR_HELLO_URL = "/hello";

@Override
public void onStartup(Set<Class<?>> c, ServletContext servletContext) {

System.out.println("创建 helloWorldServlet...");

ServletRegistration.Dynamic servlet = servletContext.addServlet(
HelloWorldServlet.class.getSimpleName(),
HelloWorldServlet.class);
servlet.addMapping(JAR_HELLO_URL);

System.out.println("创建 helloWorldFilter...");

FilterRegistration.Dynamic filter = servletContext.addFilter(
HelloWorldFilter.class.getSimpleName(), HelloWorldFilter.class);
EnumSet<DispatcherType> dispatcherTypes = EnumSet.allOf(DispatcherType.class);
dispatcherTypes.add(DispatcherType.REQUEST);
dispatcherTypes.add(DispatcherType.FORWARD);
filter.addMappingForUrlPatterns(dispatcherTypes, true, JAR_HELLO_URL);
}

}

ServletContext 我们称之为 servlet 上下文,它维护了整个 web 容器中注册的 servlet,filter,listener,以 servlet 为例,可以使用 servletContext.addServlet 等方法来添加 servlet。

这么声明一个 ServletContainerInitializer的实现类,web容器并不会识别它,所以,需要借助 SPI 机制来指定该初始化类,这一步骤是通过在项目路径下创建 META-INF/services/javax.servlet.ServletContainerInitializer来做到的,它只包含一行内容:

1
moe.cnkirito.CustomServletContainerInitializer

Spring 是如何支持 Servlet 3.0 的?

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
@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) {
// Be defensive: Some servlet containers provide us with invalid classes,
// no matter what @HandlesTypes says...
// <1>
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);
}
}
}
}

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);
// <2>
for (WebApplicationInitializer initializer : initializers) {
initializer.onStartup(servletContext);
}
}
}

Spring并没有在 SpringServletContainerInitializer 中直接对 servlet 和 filter 进行注册,而是委托给了一个陌生的类 org.springframework.web.WebApplicationInitializer 。WebApplicationInitializer 类便是 spring 用来初始化 web 环境的委托者类,它通常有三个实现类:

AbstractDispatcherServletInitializer#registerDispatcherServlet 便是无 web.xml 前提下创建 DispatcherServlet 的关键代码。

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
42
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
// 调用父类启动的逻辑
super.onStartup(servletContext);
// 注册 DispacherServlt
registerDispatcherServlet(servletContext);
}

protected void registerDispatcherServlet(ServletContext servletContext) {
// 获得 Servlet 名
String servletName = getServletName();
Assert.hasLength(servletName, "getServletName() must not return null or empty");

// <1> 创建 WebApplicationContext 对象
WebApplicationContext servletAppContext = createServletApplicationContext();
Assert.notNull(servletAppContext, "createServletApplicationContext() must not return null");

// <2> 创建 FrameworkServlet 对象
FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
Assert.notNull(dispatcherServlet, "createDispatcherServlet(WebApplicationContext) must not return null");
dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());

ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
if (registration == null) {
throw new IllegalStateException("Failed to register servlet with name '" + servletName + "'. " +
"Check if there is another servlet registered under the same name.");
}

registration.setLoadOnStartup(1);
registration.addMapping(getServletMappings());
registration.setAsyncSupported(isAsyncSupported());

// <3> 注册过滤器
Filter[] filters = getServletFilters();
if (!ObjectUtils.isEmpty(filters)) {
for (Filter filter : filters) {
registerServletFilter(servletContext, filter);
}
}

customizeRegistration(registration);
}

createServletApplicationContext

1
2
3
4
5
6
7
8
9
@Override
protected WebApplicationContext createServletApplicationContext() {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
Class<?>[] configClasses = getServletConfigClasses();
if (!ObjectUtils.isEmpty(configClasses)) {
context.register(configClasses);
}
return context;
}

该方法由子类 AbstractAnnotationConfigDispatcherServletInitializer 重写,并且创建的 WebApplicationContext 的子类 AnnotationConfigWebApplicationContext 对象

createDispatcherServlet

1
2
3
protected FrameworkServlet createDispatcherServlet(WebApplicationContext servletAppContext) {
return new DispatcherServlet(servletAppContext);
}

比较有趣的是传入的 servletAppContext 方法参数,这就是该 DispatcherServlet 的 Servlet WebApplicationContext 容器