源码分析系列之Spring MVC路由机制

1、SpringMVC运行原理

在这里插入图片描述
流程说明:
(1)客户端(浏览器)发送请求,直接请求到DispatcherServlet。
(2)DispatcherServlet根据请求信息调用HandlerMapping,解析请求对应的Handler
(3)解析到对应的Handler后,开始由HandlerAdapter适配器处理
(4)HandlerAdapter会根据Handler来调用真正的处理器开处理请求,并处理相应的业务逻辑。
(5)处理器处理完业务后,会返回一个ModelAndView对象,Model是返回的数据对象,View是个逻辑上的View。
(6)ViewResolver会根据逻辑View查找实际的View。
(7)DispaterServlet把返回的Model传给View。
(8)通过View返回给请求者(浏览器)

2、Spring MVC路由机制

在这里插入图片描述
原理: 可以理解成有一个map存着key是url,value是controller的map,然后来一个url,找到对应的controller就好了。

所有请求经过DispatcherServlet再经过HandlerMapping寻找指定的Controller

根据上图,dispatcherservlet作为核心的控制中心,在URL到框架的映射的这一步中需要获得一个HandlerMapping。

HandlerMapping 继承关系图:
在这里插入图片描述

2.1 HandlerMapping接口源码

public interface HandlerMapping {
     String PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE = HandlerMapping.class.getName() + ".pathWithinHandlerMapping" ;

     String BEST_MATCHING_PATTERN_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingPattern";

     String INTROSPECT_TYPE_LEVEL_MAPPING = HandlerMapping.class.getName() + ".introspectTypeLevelMapping" ;

     String URI_TEMPLATE_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".uriTemplateVariables";

     String MATRIX_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".matrixVariables";

     String PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE = HandlerMapping.class.getName() + ".producibleMediaTypes";

     /**
      * Return a handler and any interceptors for this request. The choice may be made
      * on request URL, session state, or any factor the implementing class chooses.
      * <p>The returned HandlerExecutionChain contains a handler Object, rather than
      * even a tag interface, so that handlers are not constrained in any way.
      * For example, a HandlerAdapter could be written to allow another framework's
      * handler objects to be used.
      * <p>Returns <code> null</code> if no match was found. This is not an error.
      * The DispatcherServlet will query all registered HandlerMapping beans to find
      * a match, and only decide there is an error if none can find a handler.
      * @param request current HTTP request
      * @return a HandlerExecutionChain instance containing handler object and
      * any interceptors, or <code>null</code> if no mapping found
      * @throws Exception if there is an internal error
      */
     HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;

}

HandlerMapping实现其实维护了一个HashMap<String, Object>,其中key是http请求的path信息,value可以是一个字符串,或者是一个处理请求的HandlerExecutionChain(包含:handler 和 HandlerInterceptor[ ] 两部分)。

AbstractUrlHandlerMapping源码:

private final Map <String, Object> handlerMap = new LinkedHashMap< String, Object >();

在基于Spring MVC的Web应用程序中,我们可以为DispatcherServlet提供多个HandlerMapping供其使用

DispatcherServlet在选用HandlerMapping的过程中,将根据我们所指定的一系列 HandlerMapping的优先级进行排序,然后优先使用优先级在前的HandlerMapping

如果当前的HandlerMapping能够返回可用的Handler,DispatcherServlet则使用当前返回的Handler进行Web请求的处理,而不再继续询问其他的HandlerMapping。否则,DispatcherServlet将继续按照各个HandlerMapping的优先级进行询问,直到获取一个可用的Handler为止。

实现上述机制,平时在配置的时候类似如下:

	<bean class= "org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping" >
           <property name= "order" value= "1" />
     </bean >


     <bean id= "urlMapping" class= "org.springframework.web.servlet.handler.SimpleUrlHandlerMapping" >
           <property name= "mappings">
               <value>
                   dealer/login.html=DealerController
                   customer/login.html=CustomerController                  
               </value>
           </property>
           <property name= "order" value= "0" />
     </bean >

ControllerClassNameHandlerMapping的order为1,SimpleUrlHandlerMapping的order为0,如果各自配置了相同url对应的Controller,那么就优先使用SimpleUrlHandlerMapping的。其实这里应该理解为优先使用SimpleUrlHandlerMapping中的HandlerExecutionChain(包含:handler 和 HandlerInterceptor[ ])更为准确。

其实下面这个图就能解释List<HandlerMapping>的数据结构了,理解它基本也理解了本文了:
在这里插入图片描述
DispatcherServlet 中对多个HandlerMapping遍历取得handler的源码实现如下:

/** List of HandlerMappings used by this servlet */
     private List<HandlerMapping> handlerMappings;

     protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
           for (HandlerMapping hm : this.handlerMappings) {
               if (logger .isTraceEnabled()) {
                    logger.trace(
                              "Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
              }
              HandlerExecutionChain handler = hm.getHandler(request);
               if (handler != null) {
                    return handler;
              }
          }
           return null ;
     }

遍历一遍所有的HandlerMapping,每次检查有没有需要的handler,第一个找到的就返回。如此看来这个List<HandlerMapping>应该是有优先级的,我们可以看HandlerMapping的第一个实现类(AbstractHandlerMapping)它就继承了Ordered来获得一个优先级的功能。

初始化这个List<HandlerMapping>的源码

private void initHandlerMappings(ApplicationContext context) {
           this.handlerMappings = null;

           if (this.detectAllHandlerMappings ) {
               // Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
              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) {
                    // Ignore, we'll add a default HandlerMapping later.
              }
          }

           // Ensure we have at least one HandlerMapping, by registering
           // a default HandlerMapping if no other mappings are found.
           if (this .handlerMappings == null) {
               this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
               if (logger .isDebugEnabled()) {
                    logger.debug( "No HandlerMappings found in servlet '" + getServletName() + "': using default");
              }
          }
     }

2.2 OrderComparator. sort( this. handlerMappings);的实现

// 继承Comparator接口
public class OrderComparator implements Comparator<Object> {

     /**
      * Shared default instance of OrderComparator.
      */
     public static final OrderComparator INSTANCE = new OrderComparator();

     // 实现compare方法
     public int compare(Object o1, Object o2) {
           boolean p1 = (o1 instanceof PriorityOrdered);
           boolean p2 = (o2 instanceof PriorityOrdered);
           if (p1 && !p2) {
               return -1;
          }
           else if (p2 && !p1) {
               return 1;
          }

           // Direct evaluation instead of Integer.compareTo to avoid unnecessary object creation.
           int i1 = getOrder(o1);
           int i2 = getOrder(o2);
          // 比较的就是各自的order
           return (i1 < i2) ? -1 : (i1 > i2) ? 1 : 0;
     }
     // 结合Order
     protected int getOrder(Object obj) {
           return (obj instanceof Ordered ? ((Ordered) obj).getOrder() : Ordered.LOWEST_PRECEDENCE );
     }

     public static void sort(List<?> list) {
           if (list.size() > 1) {
              // 最后还是用着方法比较
              Collections. sort(list, INSTANCE);
          }
     }

     public static void sort(Object[] array) {
           if (array.length > 1) {
              Arrays. sort(array, INSTANCE);
          }
     }
}

前面提到过,HandlerMapping 中的 getHandler返回的类型是:HandlerExecutionChain

HandlerExecutionChain 其实就是下面两块:

private final Object handler ;
private HandlerInterceptor [] interceptors ;

HandlerInterceptor 就是拦截器,而HandlerExecutionChain 又是HandlerMapping 封装,所以在配置spring 拦截器的时候,我们只要把拦截器定义好,然后注入到HandlerMapping 中所有属于该map的handler都会使用到这些拦截器了,又根据上面多个mapping的机制,我们可以理解在多个mapping,而且url重复的时候,有可能一些优先级比较低的mapping中的拦截器就不会被执行到,这就是所谓spring没有完全全局的拦截器的由来

2.3 HandlerInterceptor 源码

public interface HandlerInterceptor {  
  
//  Called before the handler execution, returns a boolean value, “true” : continue the handler execution chain; “false”, stop the execution chain and return it.
  boolean preHandle(HttpServletRequest request, HttpServletResponse response,   
      Object handler) throws Exception;  
        
  void postHandle(  
      HttpServletRequest request, HttpServletResponse response, Object handler,   
          ModelAndView modelAndView) throws Exception;  
        
  void afterCompletion(  
      HttpServletRequest request, HttpServletResponse response, Object handler,   
      Exception ex) throws Exception;  
        
}

(1)preHandle (HttpServletRequest request, HttpServletResponse response, Object handle) 方法,顾名思义,该方法将在请求处理之前进行调用。SpringMVC 中的Interceptor 是链式调用的,在一个应用中或者说是在一个请求中可以同时存在多个Interceptor 。每个Interceptor 的调用会依据它的声明顺序依次执行,而且最先执行的都是Interceptor 中的preHandle 方法,所以可以在这个方法中进行一些前置初始化操作或者是对当前请求的一个预处理,也可以在这个方法中进行一些判断来决定请求是否要继续进行下去。该方法的返回值是布尔值Boolean类型的,当它返回为false 时,表示请求结束,后续的Interceptor 和Controller 都不会再执行;当返回值为true 时就会继续调用下一个Interceptor 的preHandle 方法,如果已经是最后一个Interceptor 的时候就会去调用当前请求的Controller 方法

(2)postHandle (HttpServletRequest request, HttpServletResponse response, Object handle, ModelAndView modelAndView)方法 ,由preHandle 方法的解释我们知道这个方法包括后面要说到的afterCompletion 方法都只能是在当前所属的Interceptor 的preHandle 方法的返回值为true 时才能被调用。postHandle 方法,顾名思义就是在当前请求进行处理之后,也就是Controller 方法调用之后执行,但是它会在DispatcherServlet 进行视图返回渲染之前被调用,所以我们可以在这个方法中对Controller 处理之后的ModelAndView 对象进行操作postHandle 方法被调用的方向跟preHandle 是相反的,也就是说先声明的Interceptor 的postHandle 方法反而会后执行,这和Struts2 里面的Interceptor 的执行过程有点类似。Struts2 里面的Interceptor 的执行过程也是链式的,只是在Struts2 里面需要手动调用ActionInvocation 的invoke 方法来触发对下一个Interceptor 或者是Action 的调用,然后每一个Interceptor 中在invoke 方法调用之前的内容都是按照声明顺序执行的,而invoke 方法之后的内容就是反向的。

(3)afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handle, Exception ex) 方法,该方法也是需要当前对应的Interceptor 的preHandle 方法的返回值为true 时才会执行。顾名思义,该方法将在整个请求结束之后,也就是在DispatcherServlet 渲染了对应的视图之后执行。这个方法的主要作用是用于进行资源清理工作的

2.4 Spring MVC 默认提供了4种 HandlerMapping的实现

1、org.springframework.web.servlet.handler.SimpleUrlHandlerMapping
通过配置请求路径和Controller映射建立关系,找到相应的Controller

2、org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping
通过 Controller 的类名找到请求的Controller。

3、org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping
通过定义的 beanName 进行查找要请求的Controller

4、org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping
通过注解 @RequestMapping("/userlist") 来查找对应的Controller。

HandlerMapping 的4种配置

<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
    <property name="mappings">
        <props>
            <prop key="/userlist.htm">userController</prop>
        </props>
    </property>
</bean>
    
<bean class="org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping"/>

<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>
    
<bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping"/>

<bean id="userController" name="/users" class="com.qunar.web.controller.UserController"></bean>

UserController

@Controller
public class UserController extends AbstractController {

    @Override
    @RequestMapping("/userlist")
    protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response)
            throws Exception {
        
        List<User> userList = new ArrayList<User>();
        
        userList.add(new User("zhangsan", 18));
        userList.add(new User("list", 16));
        
        return new ModelAndView("userList", "users", userList);
    }
}

HandlerMapping 4种访问路径

1、SimpleUrlHandlerMapping
访问方式: http://ip:port/project/userlist.htm

2、ControllerClassNameHandlerMapping
访问方式: http://ip:port/project/user
注:类的首字母要小写

3、BeanNameUrlHandlerMapping
访问方式: http://ip:port/project/users
注:bean name属性必须要以“/”开头。

4、DefaultAnnotationHandlerMapping
访问方式: http://ip:port/project/userlist
注:@RequestMapping("/userlist")定义的路径

参考
https://www.cnblogs.com/killbug/p/3593059.html

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值