1、SpringMVC的整体结构图
组件说明:
1.1、DispatcherServlet前端控制器
Ⅰ、这个组件知名度应该是蛮高的,其实它就是一个Servlet,我们来看看DispatcherServlet类图:
Ⅱ、DispatcherServlet既然是一个标准的Servlet,那么它就有Servlet的特性,以及完整的生命周期,如下图:
Ⅲ、那么DispatcherServlet的生命周期是怎么开始的?什么时候创建?什么时候初始化?什么时候执行业务方法?什么时候销毁? 是不是有很多小问号?????
web项目我们都配置一个web.xml文件,我们会在里面配置DispatcherServlet,如下案例:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<!--配置的是SpringWEB项目的root应用上下文的容器配置参数,在Spring提供的ContextLoader里面载入
Spring的root应用上下文WebApplicationContext的时候会使用此参数-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<!--我们还可以配置Spring 的 root WebApplicationContex的实现类,不配置默认是XmlWebApplicationContext,这个默认的配置在Spring-web的jar中
org.springframework.web.context包下的ContextLoader.properties中。-->
<context-param>
<param-name>contextInitializerClasses</param-name>
<param-value>org.springframework.web.context.support.XmlWebApplicationContext</param-value>
</context-param>
<listener>
<!--ContextLoaderListener是由Spring提供的ServletContextListener-->
<!--监听Servlet上下文事件ServletContextEvent,也就是监听在web容器(如tomcat)启动阶段发布的启动等事件,
Spring主要使用此监听来构建Spring 的web应用上下文WebApplicationContext 作为整个SpringWEB项目的root应用上下文,比如装载@Service\@Componect等业务bean
此处要区分开SpringMVC应用上下文,SpringMVC应用上下文是此处装载的root应用上线文的子应用上下文-->
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<listener>
<!--RequestContextListener由spring提供的ServletRequestListener实现-->
<!--监听Servlet的请求事件ServletRequestEvent,也就是说只要有请求,此监听就会被执行-->
<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<!--当前Servlet的初始化参数,配置contextConfigLocation用于构建SpringMVC应用上下文的时候使用-->
<param-name>contextConfigLocation</param-name>
<param-value>WEB-INF/spring-mvc.xml</param-value>
</init-param>
<!--load-on-startup元素标记容器是否在启动的时候就加载这个servlet(实例化并调用其init()方法)。-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
Ⅳ、SpringWEB项目中root web应用上下文加载流程分析
在上面我们说了Spring使用org.springframework.web.context.ContextLoaderListener来加载其root web应用上下文,那么我们来分析一下是如何加载的。
我们先来看看ContextLoaderListener的类图:
ContextLoaderListener实现ServletContextListener使其拥有感知ServletContext生命周期的能力。
ContextLoaderListener继承了ContextLoader使其拥有加载Spring的root web应用上下文的能力。
那么 ContextLoaderListener就可以ServletContext生命周期中合适的机会来加载root web应用上下文的能力。
我们来看ContextLoaderListener的源码:
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
public ContextLoaderListener() {
}
public ContextLoaderListener(WebApplicationContext context) {
super(context);
}
ServletContext初始化完成后调用
@Override
public void contextInitialized(ServletContextEvent event) {
1、在ServletContext初始化完成后去载入Spring的root web应用上下文,
2、其中event.getServletContext()是获取到整个ServletContext上下文
3、ServletContext是个什么东西呢?其实就是在web容器中如tomcat中,
当前项目的一些元数据,如应用的路径ContextPath、ServerInfo等信息,
同时ServletContext也可以用来设置一些自定义数据,后面会有
4、使用父类ContextLoader的方法来载入Spring的root web应用上下文。
initWebApplicationContext(event.getServletContext());
}
ServletContext销毁的时候调用
@Override
public void contextDestroyed(ServletContextEvent event) {
closeWebApplicationContext(event.getServletContext());
ContextCleanupListener.cleanupAttributes(event.getServletContext());
}
}
接下来我们的重点就是父类ContextLoader的载入Spring的root web应用上下文的方法,源码如下:
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
先从ServletContext中获取名称为WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE的值,
如果获取到了那就报错,报一个"already a root application context" 已经有一个Spring 的root web应用上下文了,
这个就体现出了,在Spring的root web应用上下文载入完成后,会设置到ServletContext上下文中
key=WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
value=Spring root 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()) {
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) {
创建一个WebApplicationContex并赋值给ContextLoader类的成员变量context
this.context = createWebApplicationContext(servletContext);
}
if (this.context instanceof ConfigurableWebApplicationContext) {
将创建的WebApplicationContext转换为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,这边就会去装载例如@Service的BeanDefinition等。
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
将刷新完成后的WebApplicationContext设置到ServletContext上下文中,
key=WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,也就说说我们只要能够获取到Servlet的上下文,
就能够获取到Spring 的 root web应用上下文WebApplicationContext.
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;
}
}
好,这样Spring的root webApplicationContext就加入完成了,这个时候我们Servcie、dao层的bean什么的都已经实例化+初始化完成了,接下来由于我们设置的DispatcherServlet的<load-on-startup>1</load-on-startup>,那就是在启动的时候就会实例化+初始化(init)DispatcherServlet,那么我们来看看DispatcherServlet是如何初始化的:经过查找类继承关系,我们在GenericServlet类中找到了标准的Servlet的init方法的实现:
GenericServlet的init方法:
public void init(ServletConfig config) throws ServletException {
此处的ServletConfig是web容器构建的,里面主要是描述了当前初始化的Servlet的配置信息,
如Servlet的名称、类名、路径映射关系、初始参数(如我们配置的 <init-param>)
this.config = config;
this.init();
}
public void init() throws ServletException {
没有任何实现,是不是没有初始化啊,其实不是,是在HttpServletBean子类里面
}
我们找到了实际的实现子抽象类HttpServletBean中:
public final void init() throws ServletException {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Initializing servlet '" + this.getServletName() + "'");
}
使用当前的Servlet配置信息来构建一个PropertyValues实例来储存当前Servlet的配置属性。
PropertyValues pvs = new HttpServletBean.ServletConfigPropertyValues(this.getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(this.getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, this.getEnvironment()));
this.initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
} catch (BeansException var4) {
if (this.logger.isErrorEnabled()) {
this.logger.error("Failed to set bean properties on servlet '" + this.getServletName() + "'", var4);
}
throw var4;
}
}
重点在这里,初始化ServletBean,此处的实现在FrameworkServlet.initServletBean()中
this.initServletBean();
if (this.logger.isDebugEnabled()) {
this.logger.debug("Servlet '" + this.getServletName() + "' configured successfully");
}
}
我们找到了实际的实现子抽象类FrameworkServlet的initServletBean():
protected final void initServletBean() throws ServletException {
this.getServletContext().log("Initializing Spring FrameworkServlet '" + this.getServletName() + "'");
if (this.logger.isInfoEnabled()) {
this.logger.info("FrameworkServlet '" + this.getServletName() + "': initialization started");
}
long startTime = System.currentTimeMillis();
try {
重点在这里,初始化一个WebApplicationContex实例,这里就是我们说的SpringMVC的容器了,
一定要与Spring 的root webApplicationContext区分开来,
在ContextLoader中初始化的root webApplicationContext是这里要初始化的SpringMVC WebApplicationContex的parent容器。
this.webApplicationContext = this.initWebApplicationContext();
然后在执行初始化FrameworkServlet的逻辑,目前此处是没有实现的,也不需要实现,因为想做的事情都已经处理完成了。
this.initFrameworkServlet();
} catch (ServletException var5) {
this.logger.error("Context initialization failed", var5);
throw var5;
} catch (RuntimeException var6) {
this.logger.error("Context initialization failed", var6);
throw var6;
}
if (this.logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
this.logger.info("FrameworkServlet '" + this.getServletName() + "': initialization completed in " + elapsedTime + " ms");
}
}
接下来我们就来到了FrameworkServlet的initWebApplicationContext()方法:
protected WebApplicationContext initWebApplicationContext() {
从Sevlet上下文中获取到Spring 的 root webApplicationContext 上下文,用于设置为当前的SpringMVC容器的父容器。
WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
如果SpringMVC的应用上下文不为空,那就设置其parent为rootContext,并配置+刷新SpringMVC容器
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext)wac;
if (!cwac.isActive()) {
if (cwac.getParent() == null) {
cwac.setParent(rootContext);
}
this.configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
如果SpringMVC的应用上下是空,那就先去ServletContext中去寻找,
也就说SpringMVC的应用上下文在装载完成后也会添加到ServletContext中,
当然这个是空一控制是否添加到ServletContext中,其实就是使用一个boolean类型 的变量publishContext进行控制的。默认是true
wac = this.findWebApplicationContext();
}
if (wac == null) {
如果也没有在ServletContext中寻找到SpringMVC的应用上下文,那就创建一个,并且指定parent = rootContext。
在此步SpringMVC的应用上下文会配置好并且刷新,也就是说我们常用的Controller会在此处实例化+初始化。
wac = this.createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
默认会进入此处,意思就是SpringMVC容器刷新后是否触发此处事件,默认是需要触发。
!!!!重点:此处的onRefresh(wac);会初始化SpringMVC的9大组件如
HanderMapping、HnaderAdapter、LocaleResover等SpringMVC比较出名的组件
this.onRefresh(wac);
}
if (this.publishContext) {
如果需要发布SpringMVC应用上下文到ServletContext中,那就发布。默认是需要发布。
String attrName = this.getServletContextAttributeName();
this.getServletContext().setAttribute(attrName, wac);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Published WebApplicationContext of servlet '" + this.getServletName() + "' as ServletContext attribute with name [" + attrName + "]");
}
}
return wac;
}
接下来我们的重点就来到了FrameworkServlet的onRefresh(ApplicationContext context),我们发现这个方法是空的,此方法被DispatcherServlet复写了,那么我们来看看DispatcherServlet是如何复写的:
protected void onRefresh(ApplicationContext context) {
this.initStrategies(context);
}
protected void initStrategies(ApplicationContext context) {
1、初始化文件上传解析器
this.initMultipartResolver(context);
2、初始化本地信息解析器(国际化组件)
this.initLocaleResolver(context);
3、初始化主题解析器
this.initThemeResolver(context);
4、初始化处理器映射器
this.initHandlerMappings(context);
5、初始化处理器适配器
this.initHandlerAdapters(context);
6、初始化异常解析器
this.initHandlerExceptionResolvers(context);
7、初始化请求跟视图名称的翻译器
this.initRequestToViewNameTranslator(context);
8、初始化视图解析器
this.initViewResolvers(context);
9、初始化动画映射管理器
this.initFlashMapManager(context);
}
看吧9大组件一个不少,在下一篇文章中,我们将分析9大组件中,比较重要的组件初始化原理分析,然后在分析各组件的特性、原理。