Servlet 3.0+ ServletContainerInitializer旨在支持Servlet容器的基于代码的配置。
Servlet 3.0+ 规范在web容器启动时为提供给第三方组件机会做一些初始化的工作,例如注册Servlet或者Filtes等。哪它是如何实现的呢?
实现原理:
- 首先了解下Java SPI(Service Provider Interface),它是Java提供的一套用来被第三方实现或者扩展的API需要遵循如下约定:
- 当服务提供者提供了接口的一种具体实现后,在jar包的META-INF/services目录下创建一个以“接口全限定名”为命名的文件,内容为实现类的全限定名;
- 接口实现类所在的jar包放在主程序的classpath中;
- 主程序通过java.util.ServiceLoder.load(Class)动态装载实现模块,它通过扫描META-INF/services目录下的配置文件找到实现类的全限定名,把类加载到JVM;
- SPI的实现类必须携带一个不带参数的构造方法;
- 任何Servlet 3.0兼容的容器通过Java SPI机制查找到 javax.servlet.ServletContainerInitializer (这是servlet3.0规范中新增的一个接口)的实现类。
- @HandlesTypes注解用于注释ServletContainerInitializer的实现类,然后容器会将当前应用中所有这一类型(继承或者实现)的类放在ServletContainerInitializer接口的集合参数c中传递进来。如果不定义处理类型,或者应用中不存在相应的实现类,则集合参数c为空。
- 容器启动时分别调用所有ServletContainerInitializer实现的onStartup方法,并将对应实现类 @HandlesTypes注解指定类型(继承或者实现)的类和ServletContext当作参数传入,最后实现类拿到ServletContext就可以进行操作了,比如:注册Servlet或者Filtes
SpringMVC中的应用
- org.springframework.web.SpringServletContainerInitializer正是ServletContainerInitializer的实现类。
@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);
}
}
}
}
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);
for (WebApplicationInitializer initializer : initializers) {
initializer.onStartup(servletContext);
}
}
}
- 在spring-web-4.3.25.RELEASE.jar中也可以看到ServletContainerInitializer实现的声明
- Spring的WebApplicationInitializer SPI只包含一个方法:WebApplicationInitializer.onstartup (ServletContext)。这里又通过WebApplicationInitializer接口来让用户拿到ServletContext对象。
public interface WebApplicationInitializer {
void onStartup(ServletContext servletContext) throws ServletException;
}
- 这样SpringServletContainerInitializer负责将ServletContext实例化并委托给任何用户定义的WebApplicationInitializer实现。
- WebApplicationInitializer多个实例还可以通过@Order注解和实现Ordered接口来来确保servlet容器初始化的顺序。
- WEB-INF/web.xml和WebApplicationInitializer的使用并不互斥;例如,web.xml可以注册一个servlet,而WebApplicationInitializer可以注册另一个servlet;初始化器甚至可以通过。ServletContext.getServletRegistration(String)等方法修改在web.xml中执行的注册。
- 如果应用程序中存在WEB-INF/web.xml,则必须将其版本属性设置为“3.0”或更高,否则servlet容器将忽略ServletContainerInitializer的引导。
- 在Tomcat 7.0.14及以下版本,这个servlet映射不能以编程方式覆盖,7.0.15修复了这个问题。