Spring MVC属于SpringFrameWork的后续产品,已经融合在Spring Web Flow里面。Spring 框架提供了构建 Web 应用程序的全功能 MVC 模块。使用 Spring 可插入的 MVC 架构,可以选择是使用内置的 Spring Web 框架还可以是 Struts 这样的 Web 框架。
springmvc 的信息配置在web.xml中,与struts2不同的是struts2 的入口是一个filter,而springmvc的入口是一个servlet。先看下web容器是怎么加载web.xml的
1、启动一个WEB项目的时候,WEB容器会去读取它的配置文件web.xml,读取listener和context-param两个结点。
2、紧急着,容创建一个ServletContext(servlet上下文),这个web项目的所有部分都将共享这个上下文。
3、容器将context-param转换为键值对,并交给servletContext。
4、容器创建listener中的类实例,创建监听器。
web.xml 的加载顺序是:ServletContext -> context-param -> listener -> filter -> servlet ,而同个类型之间的实际程序调用的时候的顺序是根据对应的 mapping 的顺序进行调用的。
一个springMVC项目的web.xml简单配置如下:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
<!--统一字符编码处理乱码问题 -->
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 在容器启动时加载spring配置文件 -->
<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>
<servlet-name>Spring MVC Dispatcher Servlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:springmvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Spring MVC Dispatcher Servlet</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
<!-- 设置session过期时间--》
<session-config>
<session-timeout>15</session-timeout>
</session-config>
</web-app>
容器创建
ContextLoaderListener基于web上下文级别的监听器在web容器加载的时候就创建了applicationContext对象,并且将spring配置文件中的bean加载在ioc容器当中
DispatcherServlet是一个请求分发控制器,和其他servlet一样拦截来自url-pattern中定义的请求。其中load-on-startup 标签的含义是指在web容器启动时就制定了servlet被加载的顺序,他的值必须是一个整数,当是一个大于等于0的整数的时候,容器在配置的时候就加载并且初始化这个servler,数值越小,优先被加载。当是一个负整数,或者没有制定的时候,在该servlet被调用的时候才加载
在web.xml中我们可以看到contextLoaderListener和DispatcherServlet都会去加载spring的xml文件,值得说明的是这两种方式加载的spring生成的application是两个独立的application 具体看这里
个人建议基于mvc的pring配置相关由DispatcherServlet加载,而其他的javaBean有contextLoaderListener加载
一:contextLoaderListener加载spring的配置文件applicationContext.xml
contextLoaderListener 继承了contextLoader并且实现了ServletContextListener
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
private ContextLoader contextLoader;
}
其中有两个主要的方法 在项目启动时会执行contextInitialized()方法,该方法主要是用来创建application对象。在容器关闭时会调用contextDestroyed()方法,该方法会执行applicationContext的清理操作
public void contextInitialized(ServletContextEvent event) {
/* @Deprecated
public ContextLoader getContextLoader() {
return this.contextLoader;
}*/
this.contextLoader = createContextLoader();
if (this.contextLoader == null) {
this.contextLoader = this;
}
//初始化context的webApplicationContext对象 this.contextLoader.initWebApplicationContext(event.getServletContext());
}
public void contextDestroyed(ServletContextEvent event) {
if (this.contextLoader != null) {
this.contextLoader.closeWebApplicationContext(event.getServletContext());
}
ContextCleanupListener.cleanupAttributes(event.getServletContext());
}
在ContextLoaderListen中的contextInitialized()方法中会调用initWebApplicationContext(event.getServletContext());
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) {
//创建webapplicationcontext容器对象
this.context = createWebApplicationContext(servletContext);
}
if (this.context instanceof ConfigurableWebApplicationContext) {
//加载spring配置文件并且创建相关bean对象到容器中去
configureAndRefreshWebApplicationContext((ConfigurableWebApplicationContext)this.context, 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;
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;
}
}
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
..
..
// Determine parent for root web application context, if any.
ApplicationContext parent = loadParentContext(sc);
wac.setParent(parent);
wac.setServletContext(sc);
//CONFIG_LOCATION_PARAM就是在web.xml中配置的context-param中的contextConfigLocation
String initParameter = sc.getInitParameter(CONFIG_LOCATION_PARAM);
if (initParameter != null) {
//加载spring的配置文件applicationContext.xml
wac.setConfigLocation(initParameter);
}
customizeContext(sc, wac);
//执行所有Java对象的创建
wac.refresh();
}
//创建ConfigurableWebApplicationContext 对象的方法
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
//读取上下文对象
Class<?> contextClass = determineContextClass(sc);
.
.
//创建Configurable的上下文对象
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
return wac;
}
简单描述下上面的调用关系
1:web容器启动时,调用ContextLoaderLinster的contextInitialized方法初始化WebApplicationContext容器
2:contextInitialized()调用ContextLoaderLinster的父类ContextLoader的initWebApplicationContxt(ServletContext servletContext)方法,该方法主要做了3件事:
- createWebApplicationContext(servletContext);创建webApplicationContext对象
- configureAndRefreshWebApplicationContext((ConfigurableWebApplicationContext)this.context, servletContext);从servletContext中获取spring的配置文件路径加载配置文件,并且refresh(),创建里面的Bean实例
- 将webapplicationcontext对象放到servletContext中去 servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);//常量
public static final String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + “.ROOT”;
至此创建出了一个ioc容器并且将spring配置的bean加载了到了其中
我们可以这样获取这个IOC容器
WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext());
二:DispatcherServlet
DispatcherServlet是前端控制器设计模式的实现,提供spring web mvc 集中访问点,并且负责职责的分派,与spring ioc容器可以达到无缝集成,可以继承spring的所有优势
我们在web.xml中设置了DispatcherServlet 启动顺序load-on-startup
则DispatcherServlet会随着项目一起启动,而servlet初始化话是调用他的init方法
先看一下servlet的继承关系
我们可以在他的父类HttpServletBean中找到init()方法
public final void init() throws ServletException {
.
.
initServletBean();
.
.
}
initServletBean() 在HttpservletBean的子类FrameworkServlet中得到的实现
try {
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
}
protected WebApplicationContext initWebApplicationContext() {
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
// A context instance was injected at construction time -> use it
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
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 -> set
// the root application context (if any; may be null) as the parent
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
// No context instance was injected at construction time -> see if one
// has been registered in the servlet context. If one exists, it is assumed
// that the parent context (if any) has already been set and that the
// user has performed any initialization such as setting the context id
wac = findWebApplicationContext();
}
if (wac == null) {
// No context instance is defined for this servlet -> create a local one
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
// Either the context is not a ConfigurableApplicationContext with refresh
// support or the context injected at construction time had already been
// refreshed -> trigger initial onRefresh manually here.
onRefresh(wac);
}
if (this.publishContext) {
// Publish the context as a servlet context attribute.
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;
}
大概流程
①HttpServletBean.init方法中执行initServletBean方法进行初始化操作,当然该方法在HttpServletBean是空方法,所以需要子类重写。
②FrameworkServlet.initServletBean子类不负众望,重写了initServletBean方法,该方法最核心的操作就是调用initWebApplicationContext()执行上下文Bean初始化。
③FrameworkServlet.initWebApplicationContext方法首先获取自己的双亲上下文(也就是ContextLoaderListener初始化成功的WebApplicationContext);然后创建或者获取当前Servelet的WebApplicationContext。
④无论是自己创建还是获取现有的WebApplicationContext,最终都会让Servelt级别的WebApplicationContext执行configureAndRefreshWebApplicationContext()方法进行上下文容器初始化。
通过以上几步即可创建一个完整的IOC容器,而完成容器创建之后,DispatcherServlet还做了一件事:初始化Servelt控制器必备对象,这个是在initWebApplicationContext()方法中通过调用onRefresh(wac)方法实现的。而onRefresh也被重写过,如果要了解怎么初始化Servlet控制器必备对象可以查看DispatcherServlet的onRefresh方法了解。
if (!this.refreshEventReceived) {
// Either the context is not a ConfigurableApplicationContext with refresh
// support or the context injected at construction time had already been
// refreshed -> trigger initial onRefresh manually here.
onRefresh(wac);
}
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
//将uri和hanlder进行了映射
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}