概述
之所以对这个接口感兴趣, 主要是因为最近在研究Spring-Session. 在网上查找了相关的配置方式之后, 发现基本都是对Servlet3.0环境下
ServletContainerInitializer
(简称SCI)接口的使用.
1. 定义
// 完整命名: javax.servlet.ServletContainerInitializer
public interface ServletContainerInitializer {
public void onStartup(Set<Class<?>> c, ServletContext ctx)
throws ServletException;
}
2. 概述
ServletContainerInitializer
是 Servlet 3.0 新增的一个接口,主要用于在容器启动阶段通过编程风格注册Filter
,Servlet
以及Listener
,以取代通过web.xml
配置注册。这样就利于开发内聚的web应用框架.- 我们以SpringMVC举例, servlet3.0之前我们需要在
web.xml
中依据Spring的规范新建一堆配置。这样就相当于将框架和容器紧耦合了。而在3.x后注册的功能内聚到Spring里,Spring-web就变成一个纯粹的即插即用的组件,不用依据应用环境定义一套新的配置。
3. 原理
ServletContainerInitializer
接口的实现类通过java SPI声明自己是ServletContainerInitializer
的provider.- 容器启动阶段依据java spi获取到所有
ServletContainerInitializer
的实现类,然后执行其onStartup
方法. - 另外在实现
ServletContainerInitializer
时还可以通过@HandlesTypes
注解定义本实现类希望处理的类型,容器会将当前应用中所有这一类型(继承或者实现)的类放在ServletContainerInitializer
接口的集合参数c
中传递进来。如果不定义处理类型,或者应用中不存在相应的实现类,则集合参数c
为空. - 这一类实现了 SCI 的接口,如果做为独立的包发布,在打包时,会在JAR 文件的
META-INF/services/javax.servlet.ServletContainerInitializer
文件中进行注册。 容器在启动时,就会扫描所有带有这些注册信息的类(@HandlesTypes(WebApplicationInitializer.class)
这里就是加载WebApplicationInitializer.class
类)进行解析,启动时会调用其onStartup
方法——也就是说servlet容器负责加载这些指定类, 而ServletContainerInitializer
的实现者(例如Spring-web中的SpringServletContainerInitializer
对接口ServletContainerInitializer
的实现中,是可以直接获取到这些类的)
4. 与类加载的区别
- 类加载是根据限定的名称去加载,并没有相关的标准去加载未知的内容.
- 而SCI(全称
ServletContainerInitializer
)则是根据约定的标准,扫描META-INF中包含注册信息的 class 并在启动阶段调用其onStartup
.
5. SpringMVC中的应用
通过查看ServletContainerInitializer
继承层级, 可以发现spring-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>();
// webAppInitializerClasses 就是servlet3.0规范中为我们收集的 WebApplicationInitializer 接口的实现类的class
// 从webAppInitializerClasses中筛选并实例化出合格的相应的类
if (webAppInitializerClasses != null) {
for (Class<?> waiClass : webAppInitializerClasses) {
// Be defensive: Some servlet containers provide us with invalid classes,
// no matter what @HandlesTypes says...
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;
}
// 这行代码说明我们在实现WebApplicationInitializer可以通过继承Ordered, PriorityOrdered来自定义执行顺序
AnnotationAwareOrderComparator.sort(initializers);
servletContext.log("Spring WebApplicationInitializers detected on classpath: " + initializers);
// 迭代每个initializer实现的方法
for (WebApplicationInitializer initializer : initializers) {
initializer.onStartup(servletContext);
}
}
}
SpringServletContainerInitializer
由支持Servlet3.0+的Servlet容器实例化并调用.- Servlet容器还会查询classpath下
SpringServletContainerInitializer
类上修饰的@HandlesTypes
注解所标注的WebApplicationInitializer
接口的实现类. 这一步也是容器帮我们完成的. SpringServletContainerInitializer
通过实现ServletContainerInitializer
将自身并入到Servlet容器的生命周期中, 并通过自身定义的WebApplicationInitializer
将依赖于Spring框架的系统初始化需求与Servlet容器解耦. 即依赖于spring的系统可以通过实现WebApplicationInitializer
来实现自定义的初始化逻辑. 而不需要去实现ServletContainerInitializer
6. Tomcat调用SCI的时机
现在还有一个疑问, 就是 ServletContainerInitializer
的调用时机?, 因为servlet容器除了会回调SCI之外, 还有回调诸如servlet, listener等. 搞清楚这些先后顺序可以帮助我们快速定位和理解某些奇怪的问题.
这里我们就以Tomcat举例, 以下逻辑总结于Tomcat7.x, 有兴趣的读者可以去StandardContext
类中对startInternal
的实现中(第5608行 —— 第5618行, 这也是Tomcat中唯一的调用ServletContainerInitializers
接口的onStartup
方法的位置)求证下:
- 解析
web.xml
- 往
ServletContext
实例中注入<context-param>
参数 - 回调Servlet3.0的
ServletContainerInitializers
接口实现类 - 触发
Listener
事件(beforeContextInitialized, afterContextInitialized); 这里只会触发ServletContextListener
类型的 - 初始化
Filter
, 调用其init
方法 - 加载 启动时即加载的servlet
7. Links
- Tomcat 中 的可插拔以及 SCI 的实现原理 — 强烈推荐!
最后
以上就是安静毛衣为你收集整理的Servlet3.0研究之ServletContainerInitializer接口的全部内容,希望文章能够帮你解决Servlet3.0研究之ServletContainerInitializer接口所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复