SpringMVC学习 十四 SSM整合的原理

一、web环境中创建spring根应用上下文

在SSM整合时,会在web.xml配置监听器,配置代码如下:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <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>dispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring-mvc.xml</param-value>
        </init-param>
        <!--
            servlet默认是懒加载,也就是当servlet第一次被访问时,才会加载被访问的servlet,
            如果配置了<load-on-startup>,就是在tomcat容器启动时就加载当前servlet
        -->
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcherServlet</servlet-name>
        <!--
            / 匹配除jsp以外的所有的请求路径
            /* 匹配所有请求路径
            *.do 匹配所有以.do结尾的所有请求
        -->
        <url-pattern>/</url-pattern>
    </servlet-mapping>

</web-app>

配置的监听器就是ContextLoaderListener:

org.springframework.web.context.ContextLoaderListener

这个监听器的继承关系如下:

在Servlet容器启动时,会调用ServletContextListener的contextInitialized(ServletContextEvent even)方法,在此方法中会调用从ContextLoader继承来的initWebApplicationContext()

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!");
		}

		servletContext.log("Initializing Spring root WebApplicationContext");
		Log logger = LogFactory.getLog(ContextLoader.class);
		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);
				}
			}
			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.isInfoEnabled()) {
				long elapsedTime = System.currentTimeMillis() - startTime;
				logger.info("Root WebApplicationContext initialized in " + elapsedTime + " ms");
			}

			return this.context;
		}
		catch (RuntimeException | Error ex) {
			logger.error("Context initialization failed", ex);
			servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
			throw ex;
		}
	}

在上述代码中,我们可以看到,通过下面的语句创建了一个WebApplicationContext

if (this.context == null) {
   this.context = createWebApplicationContext(servletContext);
}

通过下面的语句,把创建的WebApplicationContext放到ServletContext域中

servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

二、web环境中创建SpringMVC的应用上下文

在web.xm中,可以看到在Servlet启动时,会创建DispatcherServlet。在Servlet初始化时,会调用Servlet的初始化方法init()方法。

下图时DispatcherServlet的继承关系图:

在HttpServletBean中的init()方法中,会读取配置在ServletConfig中的初始化参数,以及调用initServletBean()这个方法,而HttpServletBean中的方法是空方法,最后会调用FrameworkServlet类中的initServletBean()方法。代码HttpServletBean中init方法的代码 如下:

public final void init() throws ServletException {

   // Set bean properties from init parameters.
   PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
   if (!pvs.isEmpty()) {
      try {
         BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
         ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
         bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
         initBeanWrapper(bw);
         bw.setPropertyValues(pvs, true);
      }
      catch (BeansException ex) {
         if (logger.isErrorEnabled()) {
            logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
         }
         throw ex;
      }
   }

   // Let subclasses do whatever initialization they like.
   initServletBean();
}

HttpServletBean的initServletBean是空方法,因此会调用子类FrameworkServlet的initServletBean(),FrameworkServlet.initServletBean()代码如下:

protected final void initServletBean() throws ServletException {
   getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");
   if (logger.isInfoEnabled()) {
      logger.info("Initializing Servlet '" + getServletName() + "'");
   }
   long startTime = System.currentTimeMillis();

   try {
      this.webApplicationContext = initWebApplicationContext();
      initFrameworkServlet();
   }
   catch (ServletException | RuntimeException ex) {
      logger.error("Context initialization failed", ex);
      throw ex;
   }

   if (logger.isDebugEnabled()) {
      String value = this.enableLoggingRequestDetails ?
            "shown which may lead to unsafe logging of potentially sensitive data" :
            "masked to prevent unsafe logging of potentially sensitive data";
      logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails +
            "': request parameters and headers will be " + value);
   }

   if (logger.isInfoEnabled()) {
      logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");
   }
}

initServletBean方法调用initWebApplication()方法创建SpringMVC的应用上下文,然后把创建的应用上下文赋值给FrameworkServlet的webApplicationContext属性。至此springMVC的应用上下文就创建完成。

private WebApplicationContext webApplicationContext;

三、spring的根应用上下文与springMVC上下文的父子关系

在第一部分描述了ContextLoaderLister是如何创建Spring应用的根应用上下文,第二部分描述了DispatcherServlet是如何创建SpringMVC应用上下文,这两个上下文是什么关系呢?

其实在SSM整合时,这两个上下文是父子关系,这里的父子关系并不是继承关系,而是组合关系,与JVM中父类加载器的概念一样。在SpringMVC的应用上下文中有一个parent属性,这个属性指向Spring根应用上下文。

如下图:

那么这个SpringMVC创建过程是怎么样的呢?

在第二部分中指出,SpringMVC的应用上下文是FrameworkServlet的initWebApplicationContext()创建的,进入initWebApplicationContext()方法

protected WebApplicationContext initWebApplicationContext() {
		//从ServletContext中获取根应用上下文
		WebApplicationContext rootContext =
				WebApplicationContextUtils.getWebApplicationContext(getServletContext());
        
		WebApplicationContext wac = null;
		
		if (this.webApplicationContext != null) {
        	// 一个SpringMVC容器实例已经存在,则使用这个容器
			// 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) {
        	/**
            	在构造时如果没有SpringMVC的应用上下文被创建,则查找ServletContext,看看是否在ServletContext存在SpringMVC应用上下文
            
            */
			// 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) {
        	/**
            	如果在这个FrameworkServlet中没有找到任何SpringMVC的应用上下文,则创建一个。
                在createWebApplicationContext(rootContext)方法中创建一个SpringMVC应用上下文,并把根应用上下文rootContext赋值给
                被创建的SpringMVC应用上下文的parent属性。
            */
			// 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.
			synchronized (this.onRefreshMonitor) {
				onRefresh(wac);
			}
		}

		if (this.publishContext) {
			// Publish the context as a servlet context attribute.
			String attrName = getServletContextAttributeName();
			getServletContext().setAttribute(attrName, wac);
		}

		return wac;
	}

注意:在initWebApplicationContext方法的最后,创建的SpringMVC的应用上下文也是要发布到ServletContext的属性中,其中key值是org.springframework.web.servlet.FrameworkServlet.CONTEXT.dispatcherServlet ,value就是创建的SpringMVC的应用上下文。代码如下:

if (this.publishContext) {
   // Publish the context as a servlet context attribute.
   String attrName = getServletContextAttributeName();
   getServletContext().setAttribute(attrName, wac);
}

FrameworkServlet的createWebApplication(Application parent)代码如下:

protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
		Class<?> contextClass = getContextClass();
		if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
			throw new ApplicationContextException(
					"Fatal initialization error in servlet with name '" + getServletName() +
					"': custom WebApplicationContext class [" + contextClass.getName() +
					"] is not of type ConfigurableWebApplicationContext");
		}
		ConfigurableWebApplicationContext wac =
				(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

		wac.setEnvironment(getEnvironment());
        // 把根应用上下文赋值给SpringMVC的应用上下文
		wac.setParent(parent);
		String configLocation = getContextConfigLocation();
		if (configLocation != null) {
			wac.setConfigLocation(configLocation);
		}
		configureAndRefreshWebApplicationContext(wac);

		return wac;
	}

原文地址:https://www.cnblogs.com/cplinux/p/15363787.html