一、Spring MVC的初始化
Spring MVC的初始化主要有两部分组成:
1、初始化Spring IoC:通过配置ContextLoderListener完成。
在spring Web中,需要初始化IOC容器,用于存放我们注入的各种对象。当tomcat启动时首先会初始化一个web对应的IOC容器,用于初始化和注入各种我们在web运行过程中需要的对象。当tomcat启动的时候是如何初始化IOC容器的,我们先看一下在web.xml中经常看到的配置:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:applicationContext.xml <!--修改路径-->
</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
监听Servlet,当tomcat启动时会初始化一个Servlet容器,这样ContextLoaderListener会监听到Servlet的初始化,这样在Servlet初始化之后我们就可以在ContextLoaderListener中也进行一些初始化操作。ServletContextListener实现了ServletContextListener接口,所以会有两个方法contextInitialized和contextDestroyed。web容器初始化时会调用方法contextInitialized,web容器销毁时会调用方法contextDestroyed。
看看ContextLoaderListener的源码
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
public ContextLoaderListener() {
}
public ContextLoaderListener(WebApplicationContext context) {
super(context);
}
/**
* Initialize the root web application context.
*/
@Override
public void contextInitialized(ServletContextEvent event) {
//在父类ContextLoader中实现
initWebApplicationContext(event.getServletContext());
}
/**
* Close the root web application context.
*/
@Override
public void contextDestroyed(ServletContextEvent event) {
//关闭Web IoC容器
closeWebApplicationContext(event.getServletContext());
//消除相关参数
ContextCleanupListener.cleanupAttributes(event.getServletContext());
}
}
ContextLoaderListener的初始化Web容器方法contextInitialized()的默认实现是在他的父类ContextLoader的initWebApplicationContext方法中实现的。意思就是初始化web应用上下文。他的主要流程就是创建一个IOC容器,并将创建的IOC容器存到servletContext中,ContextLoader的核心实现如下:
//初始化WebApplicationContext,IOC容器
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
//判断web容器中是否已经有WebApplicationContext
if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
//存在就抛出异常
throw new IllegalStateException(
"Cannot initialize context because there is already a root application context present - " +
"check whether you have multiple ContextLoader* definitions in your web.xml!");
}
Log logger = LogFactory.getLog(ContextLoader.class);
servletContext.log("Initializing Spring root WebApplicationContext");
if (logger.isInfoEnabled()) {
("Root WebApplicationContext: initialization started");
}
long startTime = System.currentTimeMillis();
try {
// Store context in local instance variable, to guarantee that
// it is available on ServletContext shutdown.
//创建WebApplicationContext
if (this.context == null) {
//最终得到的是XmlWebApplicationContext
this.context = createWebApplicationContext(servletContext);
}
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
//判断应用上下文是否激活
if (!cwac.isActive()) {
// The context has not yet been refreshed -> provide services such as
// setting the parent context, setting the application context id, etc
//判断是否已经有父容器
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent ->
// determine parent for root web application context, if any.
//获得父容器
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
//设置并刷新WebApplicationContext容器
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
//将初始化的WebApplicationContext设置到servletContext中
servletContext.setAttribute(
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
ClassLoader ccl = Thread.currentThread().getContextClassLoader();
if (ccl == ContextLoader.class.getClassLoader()) {
currentContext = this.context;
}
else if (ccl != null) {
currentContextPerThread.put(ccl, this.context);
}
if (logger.isDebugEnabled()) {
logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
}
if (logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
}
//初始化 WebApplicationContext完成并返回
return this.context;
}
catch (RuntimeException ex) {
logger.error("Context initialization failed", ex);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
throw ex;
}
catch (Error err) {
logger.error("Context initialization failed", err);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
throw err;
}
}
2、初始化映射请求上下文:通过配置DispatcherServlet完成。---加载springMVC的配置文件
从上图可以看出,DispatcherServlet最终是继承由WEB容器提供的HttpServlet实现的。它里面有以下几个组件,这些组件就是Spring MVC的核心部分:
- MultipartResolver:文件解析器,用于支持服务器的文件上传;
- LocaleResolver:国际化解析器,用来提供国际化功能;
- ThemeResolver:主题解析器,用来提供皮肤主题功能;
- HandlerMapping:映射URI和处理器,使控制器得以运行;
- HandlerAdapter:处理器适配器,为不同的处理器提供上下文运行环境;
- HandlerExceptionResolver:处理器异常解析器,用来解析处理器产生的异常;
- RequestToViewNameTranslator:视图逻辑名称转换器,根据逻辑视图的名称找到具体的视图。注意:当处理器没有返回逻辑视图名时,将请求的URL自动映射为逻辑视图名;
- ViewResolver:视图解析器,当控制器返回后,通过试图解析器会把逻辑视图名进行,从而定位实际视图;
在WEB容器启动的时候,Spring MVC就会初始化上面的这些组件,所以我们不需要对他们进行配置就可以用了。下面从源码分析这个初始化过程。
3、HttpServletBean继承HttpServlet,因此在Web容器启动时将调用它的init方法,该初始化方法的主要作用
- 1)将Servlet初始化参数(init-param)设置到该组件上(如contextAttribute、contextClass、namespace、contextConfigLocation),通过BeanWrapper简化设值过程,方便后续使用;
- 2)提供给子类初始化扩展点,initServletBean(),该方法由FrameworkServlet覆盖。
public abstract class HttpServletBean extends HttpServlet implements EnvironmentAware{
@Override
public final void init() throws ServletException {
//省略部分代码
//1、如下代码的作用是将Servlet初始化参数设置到该组件上
//如contextAttribute、contextClass、namespace、contextConfigLocation;
try {
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, this.environment));
initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
//…………省略其他代码
}
//2、提供给子类初始化的扩展点,该方法由FrameworkServlet覆盖
initServletBean();
if (logger.isDebugEnabled()) {
logger.debug("Servlet '" + getServletName() + "' configured successfully");
}
}
//…………省略其他代码
}
在类HttpServletBean中看到initServletBean(),但是子类FrameworkServlet中覆盖了这个方法:
public abstract class FrameworkServlet extends HttpServletBean {
@Override
protected final void initServletBean() throws ServletException {
//省略部分代码
try {
//1、初始化Web上下文
this.webApplicationContext = initWebApplicationContext();
//2、提供给子类初始化的扩展点
initFrameworkServlet();
}
//省略部分代码
}
}
protected WebApplicationContext initWebApplicationContext() {
//ROOT上下文(ContextLoaderListener加载的)
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
// 1、在创建该Servlet注入的上下文
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
if (cwac.getParent() == null) {
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
//2、查找已经绑定的上下文
wac = findWebApplicationContext();
}
if (wac == null) {
//3、如果没有找到相应的上下文,并指定父亲为ContextLoaderListener
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
//4、刷新上下文(执行一些初始化)
onRefresh(wac);
}
if (this.publishContext) {
// Publish the context as a servlet context attribute.
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
//省略部分代码
}
return wac;
}
从initWebApplicationContext()方法可以看出,基本上如果ContextLoaderListener加载了上下文将作为根上下文(DispatcherServlet的父容器)。最后调用了onRefresh()方法执行容器的一些初始化,这个方法由子类实现,来进行扩展。
DispatcherServlet继承FrameworkServlet,并实现了onRefresh()方法提供一些前端控制器相关的配置:
public class DispatcherServlet extends FrameworkServlet {
//实现子类的onRefresh()方法,该方法委托为initStrategies()方法。
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
//初始化默认的Spring Web MVC框架使用的策略(如HandlerMapping)
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
}
最后调用了onRefresh()方法执行容器的一些初始化,这个方法由子类实现,来进行扩展。
注意:如果没有配置Spring IOC的初始化类ContextLoderListener,那么DispatcherServlet在它初始化的时候会对Spring IoC进行初始化,一般来说最好在DispatcherServlet初始化之前就完成Spring IoC初始化。
二、使用注解方式配置初始化
使用注解方式很简单,首先继承一个类:AbstractAnnotationConfigDIspatcherServletInitializer,然后实现它所定义的方法就可以了。
package spittr.config;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
public class SpittrWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[] {RootConfig.class};
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[] {WebConfig.class};
}
@Override
protected String[] getServletMappings() {
return new String[] {"/"};
}
}
原理:
Servlet 容器会在 classpath 下搜索实现了 javax.servlet .ServletContainerInitializer 接口的任何类,找到之后用它来初始化 Servlet 容器。 Spring 实现了以上接口,实现类叫做 SpringServletContainerInitializer, 它会依次搜寻实现了 WebApplicationInitializer的任何类,并委派这个类实现配置。之后,Spring 3.2 开始引入一个简易的 WebApplicationInitializer 实现类,这就是AbstractAnnotationConfigDispatcherServletInitializer。
所以 SpittrWebAppInitializer 继承 AbstractAnnotationConfigDispatcherServletInitializer之后,也就是间接实现了WebApplicationInitializer,在 Servlet 3.0 容器中,它会被自动搜索到,被用来配置 servlet 上下文。
我们需要重写上面的三个方法:
第一个,getServletMappings(),为 DispatcherServlet 提供一个或更多的Servlet 映射;这里是被映射到 /,指示它为默认的 servlet,用来操作所有来到程序的 Request。
通过getServletConfigClasses() 方法,设置 DispatcherServlet 通过 WebConfig 配置类来完成 Spring 上下文和 bean 的加载。
通过调用 getRootConfigClasses()方法返回的类就是用来配置 ContextLoaderListener 产生的上下文。
其中,DispatcherServlet 是用来加载涉及 web 功能的 beans,例如 controllers, view resolvers, 和 handler mappings;而 ContextLoaderListener 则是用来载入程序中其余的 beans,例如一些中间层和数据层组件,完成的是程序后端功能。