zoukankan      html  css  js  c++  java
  • 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;
    	}

  • 相关阅读:
    网络流24题之圆桌问题
    BZOJ 4276: [ONTAK2015]Bajtman i Okrągły Robin
    网络流24题航空路线问题
    BZOJ1038 瞭望塔
    BZOJ4029 HEOI2015定价
    BZOJ1226 SDOI2009学校食堂
    网络流24题之魔术球问题
    网络流24题之最小路径覆盖问题
    【BZOJ1098】[POI2007]办公楼biu
    BZOJ3065 带插入区间K小值
  • 原文地址:https://www.cnblogs.com/cplinux/p/15363787.html
Copyright © 2011-2022 走看看