1 介绍
严格讲ServletContainerInitializer(javax.servlet.ServletContainerInitializer)不是Spring的扩展点,而是web容器(jetty、tomcat)的扩展点,Springboot是基于这个扩展点来实现Servlet启动的。
ServletContainerInitializer从Serlet3.0开始支持,它是web容器在启动阶段提供给组件(这里将脱离于web容器的第三方框架统称组件,如Spring框架)的一个Hook点,组件可以在这里做初始化的工作(注册servlet、filter、listener)。借助ServletContainerInitializer web应用可以无xml(当然还有别的机制实现无xml,例如注解@WebServlet也可以注册servlet)。
组件如何使用ServletContainerInitializer?Servlet3.0规范要求组件必须在其jar包的META-INF/services目录中创建一个叫做 javax.servlet.ServletContainerInitializer 的文件,文件中指定ServletContainerInitializer的实现类。那么web容器在启动时就会通过jar services API发现并加载,调用其onStartup方法(如何调用?可以赏析jetty源码)。
说到ServletContainerInitializer,不得不说注解@HandlesTypes(javax.servlet.annotation.HandlesTypes)。ServletContainerInitializer.onStartup方法的第一个参数“Set<Class<?>> c”就是来自于@HandlesTypes。@HandlesTypes指定一些类(可以是注解类),所有继承/实现或被注解的类拼成一个Set传给ServletContainerInitializer。
2 应用
因为ServletContainerInitializer的特点,可以基于它做一些组件。
2.1 在Spring中应用
目前Spring框架用到这个机制并进一步提供自己扩展点WebApplicationInitializer(org.springframework.web.WebApplicationInitializer)给web应用开发更多的自由。
package org.springframework.web;
import java.lang.reflect.Modifier;
import java.util.LinkedList;
import java.util.List;
import java.util.ServiceLoader;
import java.util.Set;
import javax.servlet.ServletContainerInitializer;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.HandlesTypes;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
@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...
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);
}
}
}
2.2 在 logback中应用
logback日志工具在高版本也是用ServletContainerInitializer避免在web.xml配置listener,使日志接入更清爽。
package ch.qos.logback.classic.servlet;
import static ch.qos.logback.core.CoreConstants.DISABLE_SERVLET_CONTAINER_INITIALIZER_KEY;
import java.util.Set;
import javax.servlet.ServletContainerInitializer;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import ch.qos.logback.classic.util.StatusViaSLF4JLoggerFactory;
import ch.qos.logback.core.util.OptionHelper;
public class LogbackServletContainerInitializer implements ServletContainerInitializer {
@Override
public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException {
if (isDisabledByConfiguration(ctx)) {
StatusViaSLF4JLoggerFactory.addInfo("Due to deployment instructions will NOT register an instance of " + LogbackServletContextListener.class
+ " to the current web-app", this);
return;
}
StatusViaSLF4JLoggerFactory.addInfo("Adding an instance of " + LogbackServletContextListener.class + " to the current web-app", this);
LogbackServletContextListener lscl = new LogbackServletContextListener();
ctx.addListener(lscl);
}
boolean isDisabledByConfiguration(ServletContext ctx) {
...
}
}
2.3 其它应用场景
-
实现一个轻量级web service框架
-
做一些独立的监控框架、诊断工具