• SpringMVC源代码深度分析DispatcherServlet核心的控制器(初始化)


          SpringMVC是非常优秀的MVC框架,每一个框架都是为了我们提高开发效率,我们试图通过对SpringMVC的源码去了解这个框架,了解整个设计思想,框架要有扩展性,这里用的比較多是接口和抽象,是框架的主力,我们通过了解源码能对SpringMVC框架更了解,也能对我们开发思想有非常大的启示。

        SpringMVC由几个核心类和接口组成的。我们今天要的一个是DispatcherServlet核心的前置控制器。配置在Web.xml中。所以请求都经过它来统一分发的。SpringMVC几个核心类和接口都会出如今DispatcherServlet的源代码中,我这里大概介绍一个。今天重点是介绍DispatcherServlet核心的前置控制器。后面我们在具体分析其他的几个核心类和接口分析。

             

       DispatcherServlet的继承关系图,能清晰的了解整个层次。

      当Web项目启动时。做初始化工作。所以我们大部分是配置在Web.xml里面,这样项目一启动,就会运行相关的初始化工作,以下是Web.xml代码:

       

    <servlet>
    		<servlet-name>SpringMVCDispatcher</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>
    		<load-on-startup>1</load-on-startup>
    	</servlet>
    	<servlet-mapping>
            <servlet-name>SpringMVCDispatcher</servlet-name>
            <url-pattern>*.jhtml</url-pattern>
        </servlet-mapping>
    
    <servlet>
    		<servlet-name>HessianDispatcher</servlet-name>
    		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    		<init-param>
    			<param-name>contextConfigLocation</param-name>
    			<param-value>
    				classpath:hessian-service.xml
    			</param-value>
    		</init-param>
    		<load-on-startup>1</load-on-startup>
    	</servlet>
    	<servlet-mapping>
            <servlet-name>HessianDispatcher</servlet-name>
            <url-pattern>/service/*</url-pattern>
        </servlet-mapping>
    这里配置了两个DispatcherServlet,后面会介绍到。怎么各自处理,有各自的上下文容器。


    load-on-startup是启动的优先级,spring-mvc.xml是我们配置bean的一些信息

       最早我们開始学习MVC结构时。就是学servlet,都是继 承了HttpServlet 类,也是又一次了initdoGetdoPostdestroy方法,我这边就不介绍HttpServlet 类,DispatcherServlet也是间接最高继承了HttpServlet,如图所看到的:

        


      我们先了解项目启动,DispatcherServlet和父类都做了什么事情呢?这是我们今天的重点。

      第一步:DispatcherServlet继承了FrameworkServlet,FrameworkServlet继承了HttpServletBeanHttpServletBean继承了HttpServlet 类,而HttpServletBean类有一个入口点就是重写了init方法。如图所看到的:

      

       

        init方法做了什么事情呢?接下来我们来详细分析:

        init方法里有涉及到了BeanWrapperPropertyValuesResourceLoader。我这里大概介绍一下。

         1PropertyValues:获取Web.xml里面的servletinit-param(web.xml)

                

      /**
    		 * Create new ServletConfigPropertyValues.
    		 * @param config ServletConfig we'll use to take PropertyValues from
    		 * @param requiredProperties set of property names we need, where
    		 * we can't accept default values
    		 * @throws ServletException if any required properties are missing
    		 */
    		public ServletConfigPropertyValues(ServletConfig config, Set<String> requiredProperties)
    			throws ServletException {
    			Enumeration en = config.getInitParameterNames();
    			while (en.hasMoreElements()) {
    				String property = (String) en.nextElement();
    				Object value = config.getInitParameter(property);
    				addPropertyValue(new PropertyValue(property, value));
    			}
    		}
       

    说明:

       Enumeration en = config.getInitParameterNames();

    获取了init-paramparam-nameparam-value值,并设置配置參数到PropertyValue,如图所看到的:

      

       

      2BeanWrapper:封装了bean的行为,提供了设置和获取属性值。它有相应的BeanWrapperImpl如图所看到的:

             

    3)ResourceLoader:接口仅有一个getResource(String location)的方法。能够依据一个资源地址载入文件资源。classpath:这样的方式指定SpringMVC框架bean配置文件的来源。

         ResourcePatternResolver扩展了ResourceLoader接口。获取资源

             ResourcePatternResolver resolver =new PathMatchingResourcePatternResolver();

             resolver.getResources("classpath:spring-mvc.xml");


      总结:

         先通过PropertyValues获取web.xml文件init-param的參数值,然后通过ResourceLoader读取.xml配置信息,BeanWrapper对配置的标签进行解析和将系统默认的bean的各种属性设置到相应的bean属性。


       在init方法里还调用了initServletBean();这里面又实现了什么。HttpServletBean在为子类提供模版、让子类依据自己的需求实现不同的ServletBean的初始化工作。这边是由HttpServletBean的子类FrameworkServlet来实现的,如图所看到的:

         

      this.webApplicationContext = initWebApplicationContext();初始化SpringMVC 上下文容器。servlet的上下文容器是ServletContext。对initWebApplicationContext()。进行跟踪,查看这种方法做了什么事情?

     
    protected WebApplicationContext initWebApplicationContext() {
           //根节点上下文,是通过ContextLoaderListener载入的,server启动时,最先载入的
    		WebApplicationContext rootContext =
    				WebApplicationContextUtils.getWebApplicationContext(getServletContext());
    		if (this.webApplicationContext != null) {
    			wac = this.webApplicationContext;
    			if (wac instanceof ConfigurableWebApplicationContext) {
    				ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
                     //要对上下文设置父上下文和ID等
    				if (!cwac.isActive()) {
      					if (cwac.getParent() == null) {
    						cwac.setParent(rootContext);
    					}
    					configureAndRefreshWebApplicationContext(cwac);
    				}
    			}
    		}
             //Servlet不是由编程式注冊到容器中,查找servletContext中已经注冊的WebApplicationContext作为上下文
    		if (wac == null) {
    			wac = findWebApplicationContext();
    		}
              //假设都没找到时,就用根上下文就创建一个上下文有ID
    		if (wac == null) {
      			   wac = createWebApplicationContext(rootContext);
    		}
            //在上下文关闭的情况下调用refesh可启动应用上下文,在已经启动的状态下。调用refresh则清除缓存并又一次装载配置信息
    		if (!this.refreshEventReceived) {
    			onRefresh(wac);
    		}
           //对不同的请求相应的DispatherServlet有不同的WebApplicationContext、而且都存放在ServletContext中
    		if (this.publishContext) {
    			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;
    	}
       

      总结:

           initWebApplicationContext初始化上下文。并作为值放到了ServletContext里,由于不同的DispatherServlet有相应的各自的上下文,并且上下文有设置父上下文和id属性等。上下文项目启动时会调用createWebApplicationContext()方法,如图所看到的:

        

       然后会初始化,设置设置父上下文和id属性等。如图所看到的:

        



     1)获取ContextLoaderListener载入的上下文并标示为跟上下文,假设是编程式传入。没初始化,以根节点为父上文。并设置ID等信息。然后初始化。

     2)假设上下文是为空的,Servlet不是由编程式注冊到容器中,查找servletContext中已经注冊的WebApplicationContext作为上下文,假设都没找到时,就用根上下文就创建一个上下文有ID。在上下文关闭的情况下调用refesh可启动应用上下文,在已经启动的状态下,调用refresh则清除缓存并又一次装载配置信息

     3)对不同的请求相应的DispatherServlet有不同的WebApplicationContext、而且都存放在ServletContext中。以servlet-name为key保存在severtContext。前面有配置了两个DispatherServlet,都有各自的上下文容器。如图所看到的:

        


    回调函数onRefresh还做了一些提供了SpringMVC各种编程元素的初始化工作。 onRefresh在为子类提供模版、让子类依据自己的需求实现不同的onRefresh的初始化工作。这边是由FrameworkServlet的子类DispatcherServlet来实现的,如图所看到的:

       

    我们如今来分析SpringMVC组件进行初始化。并封装到DispatcherServlet

            //初始化上传文件解析器

            initMultipartResolver(context);

            //初始化本地解析器

           initLocaleResolver(context);

            //初始化主题解析器

            initThemeResolver(context);

            //初始化映射处理器

            initHandlerMappings(context);

             //初始化适配器处理器

            initHandlerAdapters(context);

            //初始化异常处理器

            initHandlerExceptionResolvers(context);

            //初始化请求到视图名翻译器

            initRequestToViewNameTranslator(context);

            //初始化视图解析器

            initViewResolvers(context);

     我们这边拿几个比較基本的分析一下详细实现了什么。

     第一:initHandlerMappings初始化映射处理器

     private void initHandlerMappings(ApplicationContext context) {
    		this.handlerMappings = null;
    		if (this.detectAllHandlerMappings) {
    			Map<String, HandlerMapping> matchingBeans =
    					BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
    			if (!matchingBeans.isEmpty()) {
    				this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
    				// We keep HandlerMappings in sorted order.
    				OrderComparator.sort(this.handlerMappings);
    			}
    		}
    		else {
    			try {
    				HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
    				this.handlerMappings = Collections.singletonList(hm);
    			}
    			catch (NoSuchBeanDefinitionException ex) {
    				}
    		}
    		if (this.handlerMappings == null) {
    			this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
    			if (logger.isDebugEnabled()) {
    				logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
    			}
    		}
    	}

    说明: 

      1detectAllHandlerMappings默认是true,依据类型匹配机制查找上下文及父容器上下文中全部类型为HandlerMappingbean,将它们作为该类型组件。并放到ArrayList<HandlerMapping>中。

      2detectAllHandlerMappings假设是false时。查找keyhandlerMappingHandlerMapping类型的bean为该类组件,并且 Collections.singletonList仅仅有一个元素的集合。

      3List<HandlerMapping> 是为空的话,使用BeanNameUrlHandleMapping实现类创建该类的组件。

    第二:initHandlerAdapters适配器处理器

      
    private void initHandlerAdapters(ApplicationContext context) {
    		this.handlerAdapters = null;
    
    		if (this.detectAllHandlerAdapters) {
    			Map<String, HandlerAdapter> matchingBeans =
    					BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);
    			if (!matchingBeans.isEmpty()) {
    				this.handlerAdapters = new ArrayList<HandlerAdapter>(matchingBeans.values());
    				// We keep HandlerAdapters in sorted order.
    				OrderComparator.sort(this.handlerAdapters);
    			}
    		}
    		else {
    			try {
    				HandlerAdapter ha = context.getBean(HANDLER_ADAPTER_BEAN_NAME, HandlerAdapter.class);
    				this.handlerAdapters = Collections.singletonList(ha);
    			}
    			catch (NoSuchBeanDefinitionException ex) {
    							}
    		}
    		if (this.handlerAdapters == null) {
    			this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class);
    			if (logger.isDebugEnabled()) {
    				logger.debug("No HandlerAdapters found in servlet '" + getServletName() + "': using default");
    			}
    		}
    	}


    initHandlerAdapters适配器处理器初始化原理跟initHandlerMappings初始化映射处理器一样

    这里就不在说明了。

    总结:

    1initHandlerMapping会初始化了handlerMethods请求方法的映射。HandlerMapping处理请求的映射的如图所看到的:

     


       今天先讲SpringMVC的初始化。当DispatcherServlet初始化后,就会自己主动扫描上下文的bean,依据名称或者类型匹配的机制查找自己定义的组件,找不到则使用DispatcherServlet。Properties定义默认的组件

    总结:

         HttpServletBeanFrameworkServletDispatcherServlet三个不同的类层次,SpringMVC对三个以抽象和继承来实现不用的功能。分工合作。实现了解耦的设计原则。

    我们在回想一下。各自做了什么事情。HttpServletBean是实现了获取web.xml中的<init-param>配置元素的值。FrameworkServlet实现了SpringMVC上下文并依据不同的DispatcherServlet放在以servlet-namekeysevertContext中。DispatcherServlet主要实现了初始化SpringMVC组件元素。



  • 相关阅读:
    git忽略已提交过的文件方法
    去除git版本控制
    写博客的初衷
    Substring with Concatenation of All Words
    Course Schedule
    Reverse Words in a String--not finished yet
    Repeated DNA Sequences
    Maximum Product of Word
    Odd Even Linked List
    Reorder List
  • 原文地址:https://www.cnblogs.com/mengfanrong/p/5077526.html
Copyright © 2020-2023  润新知