深入了解WebApplicationInitializer是如何消除web.xml和springMVC的配置文件
https://www.nonelonely.com/article/1552475062917
servlet3.0 8.2.4 Shared libraries / runtimes pluggability 章节中规定:为了支持可以不使用web.xml,提供了ServletContainerInitializer,它可以通过SPI机制,当启动web容器的时候,会自动到添加的相应jar包下找到META-INF/services下以ServletContainerInitializer的全路径名称命名的文件,它的内容为ServletContainerInitializer实现类的全路径,将它们实例化。
ServletContainerInitializer
package javax.servlet;
import java.util.Set;
public interface ServletContainerInitializer {
void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException;
}
SpringServletContainerInitializer实现类
由于官方提供了接口,所以spring那边写了SpringServletContainerInitializer类实现了ServletContainerInitializer接口,为了就是支持可以不使用web.xml
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;
import org.springframework.lang.Nullable;
import org.springframework.util.ReflectionUtils;
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
@Override
public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses,
ServletContext servletContext) throws ServletException {
List<WebApplicationInitializer> initializers = new LinkedList<>();
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)
ReflectionUtils.accessibleConstructor(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);
}
}
}
这个先判断webAppInitializerClasses这个Set是否为空。如果不为空的话,找到这个set中不是接口,不是抽象类,并且是WebApplicationInitializer接口实现类的类,将它们保存到list中。当这个list为空的时候,抛出异常。不为空的话就按照一定的顺序排序,并将它们按照一定的顺序实例化,调用其onStartup方法执行。
WebApplicationInitializer
经过上面的几步,最终到了这个接口,所以我们只要实现在这个接口,就可以不用写web.xml文件了
public class MyWebAppInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext container) {
//实用xml的方式配置SpringMVC
XmlWebApplicationContext appContext = new XmlWebApplicationContext();
//设置配置文件的位置
appContext.setConfigLocation("/WEB-INF/spring/springmvc-config.xml");
//将DispatcherServlet实例添加到容器
ServletRegistration.Dynamic dispatcher =
container.addServlet("dispatcher", new DispatcherServlet(appContext));
//设置启动顺序
dispatcher.setLoadOnStartup(1);
//设置映射路径
dispatcher.addMapping("/");
}
}
这里去除了web.xml文件,但是SpringMVC的配置文件还在,因为我们用XmlWebApplicationContext来加载配置,但是spring提供了AnnotationConfigWebApplicationContext注解的方式加载配置文件,所以可以这样:
public class MyWebAppInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext container) {
// 创建Spring的root配置环境
AnnotationConfigWebApplicationContext rootContext =
new AnnotationConfigWebApplicationContext();
rootContext.register(MyMvcConfig.class);
// 将Spring的配置添加为listener
container.addListener(new ContextLoaderListener(rootContext));
// 创建SpringMVC的分发器
AnnotationConfigWebApplicationContext dispatcherContext =
new AnnotationConfigWebApplicationContext();
dispatcherContext.register(DispatcherConfig.class);
// 注册请求分发器
ServletRegistration.Dynamic dispatcher =
container.addServlet("dispatcher", new DispatcherServlet(dispatcherContext));
dispatcher.setLoadOnStartup(1);
dispatcher.addMapping("/");
}
}
在这里的MyMvcConfig.class是一个JavaConfig的方式:
@Configuration
@EnableWebMvc
@ComponentScan
public class MyMvcConfig{
@Bean
public InternalResourceViewResolver viewResolver {
InternalResourceViewResolver viewResolver=new InternalResourceViewResolver();
viewResolver.setPrefix("/WEB-INF/classes/views");
viewResolver.setSuffix(".jsp");
return viewResolver;
}
}
SpringBootServletInitializer
public abstract class SpringBootServletInitializer implements WebApplicationInitializer {
protected Log logger; // Don't initialize early
private boolean registerErrorPageFilter = true;
protected final void setRegisterErrorPageFilter(boolean registerErrorPageFilter) {
this.registerErrorPageFilter = registerErrorPageFilter;
}
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
// Logger initialization is deferred in case an ordered
// LogServletContextInitializer is being used
this.logger = LogFactory.getLog(getClass());
WebApplicationContext rootAppContext =
createRootApplicationContext(servletContext);
if (rootAppContext != null) {
servletContext.addListener(new ContextLoaderListener(rootAppContext) {
@Override
public void contextInitialized(ServletContextEvent event) {
// no-op because the application context is already initialized
}
});
}
else {
this.logger.debug("No ContextLoaderListener registered, as " +
"createRootApplicationContext() did not "
+ "return an application context");
}
}
protected WebApplicationContext createRootApplicationContext(
ServletContext servletContext) {
SpringApplicationBuilder builder = createSpringApplicationBuilder();
builder.main(getClass());
ApplicationContext parent = getExistingRootWebApplicationContext(servletContext);
if (parent != null) {
this.logger.info("Root context already created (using as parent).");
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, null);
builder.initializers(new ParentContextApplicationContextInitializer(parent));
}
builder.initializers(new ServletContextApplicationContextInitializer(servletContext));
builder.contextClass(AnnotationConfigServletWebServerApplicationContext.class);
builder = configure(builder);
builder.listeners(new WebEnvironmentPropertySourceInitializer(servletContext));
SpringApplication application = builder.build();
if (application.getAllSources().isEmpty()
&& AnnotationUtils.findAnnotation(getClass(), Configuration.class) != null) {
application.addPrimarySources(Collections.singleton(getClass()));
}
Assert.state(!application.getAllSources().isEmpty(),
"No SpringApplication sources have been defined. Either override the "
+ "configure method or add an @Configuration annotation");
// Ensure error pages are registered
if (this.registerErrorPageFilter) {
application.addPrimarySources(Collections.singleton(ErrorPageFilterConfiguration.class));
}
return run(application);
}
protected SpringApplicationBuilder createSpringApplicationBuilder() {
return new SpringApplicationBuilder();
}
protected WebApplicationContext run(SpringApplication application) {
return (WebApplicationContext) application.run();
}
private ApplicationContext getExistingRootWebApplicationContext(
ServletContext servletContext) {
Object context =
servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
if (context instanceof ApplicationContext) {
return (ApplicationContext) context;
}
return null;
}
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder;
}
private static final class WebEnvironmentPropertySourceInitializer implements
ApplicationListener<ApplicationEnvironmentPreparedEvent>, Ordered {
private final ServletContext servletContext;
private WebEnvironmentPropertySourceInitializer(ServletContext servletContext) {
this.servletContext = servletContext;
}
@Override
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
ConfigurableEnvironment environment = event.getEnvironment();
if (environment instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment)
environment).initPropertySources(this.servletContext, null);
}
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
}
}
1.Servlet3.0+版本的容器会检测到ServletContainerInitializer的实现类并用他的onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)方法来初始化容器,Spring MVC提供了这个容器的实现类SpringServletContainerInitializer,并且通过@TypeHandler(WebApplicationInitializer.class)注解将WebApplicationInitializer接口的实现类作为参数传入,即将ServletContainerInitializer的初始化任务交由WebApplicationinitializer实现类的onStartup(ServletContext servletContext)方法处理
2.Spring MVC同时提供了实现这个接口的抽象类,名为AbstractDispatcherServletInitializer,还有干脆实现了这个接口的类,名为AbatractAnnotationConfigDispatcherServletInitializer,这次主要讲从WebApplicationInitializer接口实现的方式