写在前面:阅读需要会使用SpringMVC。
Web环境中的SpringMVC:概述
在Web环境中,SpringMVC是建立在IoC容器上的。了解SpringMVC,首先要了解SpringIoC容器是如何在Web环境中被载入的。Spring IoC是一个独立的模块,它并不是在Web容器中发挥作用的,如果要在Web容器中使用IoC容器,需要Spring为IoC设计一个启动过程,吧IoC容器导入,并在Web容器中建立起来。这个启动过程需要结合Web容器的启动过程。一方面处理Web容器的启动,另一方面通过设计特定的Web容器拦截器,将IoC容器载入到Web环境中,并将其初始化。在这个过程完成之后,IoC容器才能够正茬工作,才能建立起MVC框架的运行机制,从而响应容器传递的HTTP请求。
在Web.xml的设置中,有两个设置,一个是DispatcherServlet,另一个是ContextLoaderListener。DispatcherServlet负责响应处理HTTP请求,ContextLoaderListener负责构建SringMVC建立一个IoC容器在Web环境中的启动工作。
ContextLoaderListener和IoC容器的构建
ContexLoaderListener
web.xml中部署ContextLoaderListener:
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
监听器:ContextLoaderListener这个监听器是和Web服务器的生命周期相关联的,由ContextLoaderListener负责完成IoC容器在Web环境中的启动工作,如将DispatchServlet作为SpringMVC处理Web请求的转发器的建立过程等。
它实现了ServletContextListener接口。该定义在Servlet API中,提供了如contextInitialized和ContextDestroyed等方法。
ContextLoaderListener的组成
在Web容器中,建立WebApplicationContext的过程,是在 contextInitialized的接口中实现的,而具体的载入IoC容器的过程是由ContextLoaderListener交由ContextLoader来完成的。ContextLoader是ContextLoaderListener的基类。类图如下:
IoC容器启动的基本过程 :
Web容器的上下文设计
先看上下文(Context)设计,再从ContextLoaderListener中去了解整个容器启动的过程。在Web环境中,Spring提供了WebApplicationContext来满足启动过程的需要,WebApplicationContext是我们观察的重点。
WebApplication的类层次图如下所示:
在这个接口设计中,有ApplicationContext和BeanFactory进行对接。在这里,XmlWebApplicationContext是我们比较熟悉的对象,它在ApplicationContext的继承功能上,增加了对Web环境和XML配置定义的处理。在启动过程中,Spring会使用一个默认的WebApplicationContext实现作为IoC容器,这个默认实现是XmlWebApplicationContext,其默认读取的配置文件是:/WEB-INF/applicationCointext.xml
。
ContextLoader的设计和实现
对于Spring承载的Web应用而言,IoC容器的载入和初始化是由ContextLoaderListener这样的类来完成的。ContextLoaderListener实现了ServletContextListener接口,这个接口里的函数会结合Web的容器声明周期被调用,如初始化和销毁。具体的初始化,有ContextLoader来完成
ContextLoader的初始化工作
ContextLoader主要完成根上下文(root context,作为Web容器中唯一的实例而存在,单例的意思吧)在Web容器的创建,并将其保存到Web容器的ServletContext中,供需要时使用。
初始化代码:
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
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()) {
logger.info("Root WebApplicationContext: initialization started");
}
long startTime = System.currentTimeMillis();
try {
// Store context in local instance variable, to guarantee that
// it is available on ServletContext shutdown.
if (this.context == null) {
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);
}
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
//设置root_context
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;
logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
}
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;
}
}
创建Web上下文
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
//这里判断使用什么样的类作为IoC容器
Class<?> contextClass = determineContextClass(sc);
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
"] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
}
return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}
确定使用的IoC容器
protected Class determineContextClass(ServletContext servletContext) thorws ApplicationContextException{
//读取ServletContext中对CONTEXT_CLASS_PARAM参数的配置
String contextClassName = servletContext.
getInitParmamter(CONTEXT_CLASS_PARAM);
//如果设置了需要使用的上下文类,就用这个类
if(contextClassName != null){
try{
return ClassUtils.forName(contextClassName,ClassUtils.getCDefaultClassLoader());
catch(ClasssNotFountException ex){
//抛出异常信息
}
else{//没有设置,使用默认的contextClass类
contextClassName = defaultStategies.getProperty(WebApplicationContext.class.getName());
try{
return ClassUtils.forName(contextClassName,ClassUtils.getCDefaultClassLoader());
catch(ClasssNotFountException ex){
//抛出异常信息
}
}
}