spring基本使用(17)-springMVC1-SpringMVC环境中容器加载原理分析以及DispatcherServlet的初始化原理分析

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大组件中,比较重要的组件初始化原理分析,然后在分析各组件的特性、原理。

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值