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框架
-
做一些獨立的監控框架、診斷工具