记得刚参加工作的时候,搭建springMVC框架还是需要手动配置web.xml文件,现在已经被渐渐淘汰了,在项目中几乎看不到有这种配置了,取而代之的是springMVC的零配置,原理就是tomcat的SPI的技术。
通过spi机制,tomcat会扫描所有jar下的
/META-INF/services/javax.servlet.ServletContainerInitializer文件。
在SpringServletContainerInitializer中通过@HandlesTypes注解,将实现了所有WebApplicationInitializer接口的类作为参数传入onstartup方法中。
然后遍历所有实现了WebApplicationInitializer接口的类,调用他们的onstartup方法。
接下来定义一个WebApplicationInitializer接口的类:
代码片段一:
public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
//父容器
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[]{SpringContainer.class};
}
//SpringMVC配置子容器
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[]{MvcContainer.class};
}
//获取DispatcherServlet的映射信息
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
代码片段二:
//不扫描有@Controller注解的类
@ComponentScan(value = "com.dongsq",excludeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class})
})
public class SpringContainer {
}
代码片段三:
//扫描有@Controller注解的类
@ComponentScan(value = "com.dongsq",includeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class})
})
public class MvcContainer {
}
定义的类继承了AbstractAnnotationConfigDispatcherServletInitializer,而被继承类 又实现了WebApplicationInitializer接口。
所以由继承关系可知:会调用到AbstractDispatcherServletInitializer的onStartup方法。
看super.onStartup方法:
super.onStartup方法做了两件事:
一、创建spring上下文,createRootApplicationContext方法会调用复写的getRootConfigClasses方法,会将自定义的SpringConfainer类加入spring容器。
二、servlet容器注册监听。
AbstractDispatcherServletInitializer的onStartup方法中的super.onStartup方法调用完成,接下来会调用registerDispatcherServlet方法,创建mvc上下文,注册DispatcherServlet。
代码片段四:
protected void registerDispatcherServlet(ServletContext servletContext) {
String servletName = getServletName();
Assert.hasLength(servletName, "getServletName() must not return null or empty");
//创建mvc的上下文,这里会调代码片段一中的getServletConfigClasses
WebApplicationContext servletAppContext = createServletApplicationContext();
Assert.notNull(servletAppContext, "createServletApplicationContext() must not return null");
//创建DispatcherServlet对象,把springmvc上下文设置到DispatcherServlet中
FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
Assert.notNull(dispatcherServlet, "createDispatcherServlet(WebApplicationContext) must not return null");
dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());
//把DispatcherServlet放到servlet上下文中
ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
if (registration == null) {
throw new IllegalStateException("Failed to register servlet with name '" + servletName + "'. " +
"Check if there is another servlet registered under the same name.");
}
registration.setLoadOnStartup(1);
//钩子方法 会调用代码片段一中的getServletMappings方法
registration.addMapping(getServletMappings());
registration.setAsyncSupported(isAsyncSupported());
//定义拦截器
Filter[] filters = getServletFilters();
if (!ObjectUtils.isEmpty(filters)) {
for (Filter filter : filters) {
registerServletFilter(servletContext, filter);
}
}
customizeRegistration(registration);
}
接下来在servlet容器启动时,会调到监听类中的contextInitialized方法。
看initWebApplicationContext方法:
代码片段五:
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
.....
try {
.....
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
if (!cwac.isActive()) {
if (cwac.getParent() == null) {
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
//这里会调用refresh方法,启动spring上下文
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
//将spring上下文设置到servlet容器中
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
.....
}
....
}
接下来servlet容器在启动的时候,会调到DispatcherServlet中的init方法,最终会调到initWebApplicationContext方法。
initWebApplicationContext方法代码:
protected WebApplicationContext initWebApplicationContext() {
//从servlet下午文中获取spring的上下文对象
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
if (cwac.getParent() == null) {
//设置spring上下文为springMVC的父上下文
cwac.setParent(rootContext);
}
//在这启动springMVC上下文
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
wac = findWebApplicationContext();
}
if (wac == null) {
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
onRefresh(wac);
}
if (this.publishContext) {
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
"' as ServletContext attribute with name [" + attrName + "]");
}
}
return wac;
}