ServletContainerInitializer
即Servlet容器初始化组件,用来初始化Servlet容器。如注册Servlet、注册过滤器等。
public interface ServletContainerInitializer {
/**
* Receives notification during startup of a web application of the classes
* within the web application that matched the criteria defined via the
* {@link javax.servlet.annotation.HandlesTypes} annotation.
*
* @param c The (possibly null) set of classes that met the specified
* criteria
* @param ctx The ServletContext of the web application in which the
* classes were discovered
*
* @throws ServletException If an error occurs
*/
void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException;
}
为了解耦,JAVA提供了一种约定,服务提供方实现了ServletContainerInitializer
后,在jar包的META-INF/services
目录创建一个名为javax.servlet.ServletContainerInitializer
的文件,文件内容为对应的实现类。
如Spring实现的SpringServletContainerInitializer
,见下图
同时对于ServletContainerInitializer
组件可以添加注解@HandlesTypes
,可以指定具体的类名,被指定的类的实现将被扫描并注入到ServletContainerInitializer.onStartup
方法的参数Set<Class<?>>
中。
@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);
}
}
}
如上的SpringServletContainerInitializer
,会将所有的WebApplicationInitializer
实现注入其参数Set<Class<?>> webAppInitializerClasses
中,并进行实例化及遍历调用,用于初始化Web环境上下文。
WebApplicationInitializer
的实现替代了web.xml
的配置方式,如我们在使用传统SpringMVC时需要配置在web.xml
中配置DispatcherServlet
,如
* <servlet>
* <servlet-name>dispatcher</servlet-name>
* <servlet-class>
* org.springframework.web.servlet.DispatcherServlet
* </servlet-class>
* <init-param>
* <param-name>contextConfigLocation</param-name>
* <param-value>/WEB-INF/spring/dispatcher-config.xml</param-value>
* </init-param>
* <load-on-startup>1</load-on-startup>
* </servlet>
*
* <servlet-mapping>
* <servlet-name>dispatcher</servlet-name>
* <url-pattern>/</url-pattern>
* </servlet-mapping>
可以改为
public class MyWebAppInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext container) {
XmlWebApplicationContext appContext = new XmlWebApplicationContext();
appContext.setConfigLocation("/WEB-INF/spring/dispatcher-config.xml");
ServletRegistration.Dynamic dispatcher =
container.addServlet("dispatcher", new DispatcherServlet(appContext));
dispatcher.setLoadOnStartup(1);
dispatcher.addMapping("/");
}
}
SpringBoot提供了一个抽象类SpringBootServletInitializer implements WebApplicationInitializer
提供默认实现,如果要自定义可以继承它,并在configure
中指定应用启动类。注意只有打包为war时才会用到它,嵌入式启动不需要。
public class Bootstrap extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(Application.class);
}
}
Tomcat
通过Context
和ContextConfig
来实现ServletContainerInitializer
组件的调用和加载。
ContextConfig
会遍历所有jar包的javax.servlet.ServletContainerInitializer
的文件内容,通过反射实例化对象,并将其注入到Context
中,并由Context
调用其onStartup
方法。
在SpringBoot的嵌入式Tomcat
中,实现了class TomcatStarter implements ServletContainerInitializer
,并将其硬编码直接注入到了WebApplicationContext
中,TomcatStarter
中会遍历调用所有ServletContextInitializer
来实现Servlet容器上下文的初始化
其他参考资料: