分享

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

 WindySky 2017-12-27

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

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

         

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

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

   

  1. <servlet>  
  2.         <servlet-name>SpringMVCDispatcher</servlet-name>  
  3.         <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>  
  4.         <init-param>  
  5.             <param-name>contextConfigLocation</param-name>  
  6.             <param-value>  
  7.                 classpath:spring-mvc.xml  
  8.             </param-value>  
  9.         </init-param>  
  10.         <load-on-startup>1</load-on-startup>  
  11.     </servlet>  
  12.     <servlet-mapping>  
  13.         <servlet-name>SpringMVCDispatcher</servlet-name>  
  14.         <url-pattern>*.jhtml</url-pattern>  
  15.     </servlet-mapping>  
  1. <servlet>  
  2.         <servlet-name>HessianDispatcher</servlet-name>  
  3.         <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>  
  4.         <init-param>  
  5.             <param-name>contextConfigLocation</param-name>  
  6.             <param-value>  
  7.                 classpath:hessian-service.xml  
  8.             </param-value>  
  9.         </init-param>  
  10.         <load-on-startup>1</load-on-startup>  
  11.     </servlet>  
  12.     <servlet-mapping>  
  13.         <servlet-name>HessianDispatcher</servlet-name>  
  14.         <url-pattern>/service/*</url-pattern>  
  15.     </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)

            

  1. /** 
  2.  * Create new ServletConfigPropertyValues. 
  3.  * @param config ServletConfig we'll use to take PropertyValues from 
  4.  * @param requiredProperties set of property names we need, where 
  5.  * we can't accept default values 
  6.  * @throws ServletException if any required properties are missing 
  7.  */  
  8. public ServletConfigPropertyValues(ServletConfig config, Set<String> requiredProperties)  
  9.     throws ServletException {  
  10.     Enumeration en = config.getInitParameterNames();  
  11.     while (en.hasMoreElements()) {  
  12.         String property = (String) en.nextElement();  
  13.         Object value = config.getInitParameter(property);  
  14.         addPropertyValue(new PropertyValue(property, value));  
  15.     }  
  16. }  
   

说明:

   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();进行跟踪,查看这个方法做了什么事情?

 
  1. protected WebApplicationContext initWebApplicationContext() {  
  2.        //根节点上下文,是通过ContextLoaderListener加载的,服务器启动时,最先加载的  
  3.         WebApplicationContext rootContext =  
  4.                 WebApplicationContextUtils.getWebApplicationContext(getServletContext());  
  5.         if (this.webApplicationContext != null) {  
  6.             wac = this.webApplicationContext;  
  7.             if (wac instanceof ConfigurableWebApplicationContext) {  
  8.                 ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;  
  9.                  //要对上下文设置父上下文和ID等  
  10.                 if (!cwac.isActive()) {  
  11.                     if (cwac.getParent() == null) {  
  12.                         cwac.setParent(rootContext);  
  13.                     }  
  14.                     configureAndRefreshWebApplicationContext(cwac);  
  15.                 }  
  16.             }  
  17.         }  
  18.          //Servlet不是由编程式注册到容器中,查找servletContext中已经注册的WebApplicationContext作为上下文  
  19.         if (wac == null) {  
  20.             wac = findWebApplicationContext();  
  21.         }  
  22.           //如果都没找到时,就用根上下文就创建一个上下文有ID  
  23.         if (wac == null) {  
  24.                wac = createWebApplicationContext(rootContext);  
  25.         }  
  26.         //在上下文关闭的情况下调用refesh可启动应用上下文,在已经启动的状态下,调用refresh则清除缓存并重新装载配置信息  
  27.         if (!this.refreshEventReceived) {  
  28.             onRefresh(wac);  
  29.         }  
  30.        //对不同的请求对应的DispatherServlet有不同的WebApplicationContext、并且都存放在ServletContext中  
  31.         if (this.publishContext) {  
  32.             String attrName = getServletContextAttributeName();  
  33.             getServletContext().setAttribute(attrName, wac);  
  34.             if (this.logger.isDebugEnabled()) {  
  35.                 this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +  
  36.                         "' as ServletContext attribute with name [" + attrName + "]");  
  37.             }  
  38.         }  
  39.   
  40.         return wac;  
  41.     }  
   

  总结:

       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初始化映射处理器

  1. private void initHandlerMappings(ApplicationContext context) {  
  2.     this.handlerMappings = null;  
  3.     if (this.detectAllHandlerMappings) {  
  4.         Map<String, HandlerMapping> matchingBeans =  
  5.                 BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);  
  6.         if (!matchingBeans.isEmpty()) {  
  7.             this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());  
  8.             // We keep HandlerMappings in sorted order.  
  9.             OrderComparator.sort(this.handlerMappings);  
  10.         }  
  11.     }  
  12.     else {  
  13.         try {  
  14.             HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);  
  15.             this.handlerMappings = Collections.singletonList(hm);  
  16.         }  
  17.         catch (NoSuchBeanDefinitionException ex) {  
  18.             }  
  19.     }  
  20.     if (this.handlerMappings == null) {  
  21.         this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);  
  22.         if (logger.isDebugEnabled()) {  
  23.             logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");  
  24.         }  
  25.     }  
  26. }  

说明: 

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

  2detectAllHandlerMappings如果是false时,查找keyhandlerMappingHandlerMapping类型的bean为该类组件,而且 Collections.singletonList只有一个元素的集合。

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

第二:initHandlerAdapters适配器处理器

  
  1. private void initHandlerAdapters(ApplicationContext context) {  
  2.         this.handlerAdapters = null;  
  3.   
  4.         if (this.detectAllHandlerAdapters) {  
  5.             Map<String, HandlerAdapter> matchingBeans =  
  6.                     BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);  
  7.             if (!matchingBeans.isEmpty()) {  
  8.                 this.handlerAdapters = new ArrayList<HandlerAdapter>(matchingBeans.values());  
  9.                 // We keep HandlerAdapters in sorted order.  
  10.                 OrderComparator.sort(this.handlerAdapters);  
  11.             }  
  12.         }  
  13.         else {  
  14.             try {  
  15.                 HandlerAdapter ha = context.getBean(HANDLER_ADAPTER_BEAN_NAME, HandlerAdapter.class);  
  16.                 this.handlerAdapters = Collections.singletonList(ha);  
  17.             }  
  18.             catch (NoSuchBeanDefinitionException ex) {  
  19.                             }  
  20.         }  
  21.         if (this.handlerAdapters == null) {  
  22.             this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class);  
  23.             if (logger.isDebugEnabled()) {  
  24.                 logger.debug("No HandlerAdapters found in servlet '" + getServletName() + "': using default");  
  25.             }  
  26.         }  
  27.     }  


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

 

这里就不在说明了。

总结:

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

 


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

 

总结:

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

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



    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多