likes
comments
collection
share

责任链模式在SpringMVC中的应用

作者站长头像
站长
· 阅读数 19

思考,输出,沉淀。用通俗的语言陈述技术,让自己和他人都有所收获。 作者:毅航😜


前言

有关责任链模式的网上有很多文章介绍,但万变不离其宗,责任链的本质就是一种将请求发送者和接收者解耦的设计模式。其将多个对象组成链式结构,每个对象都有机会处理请求,直到遍历到可以处理的处理器。

这样说你还是会感觉有点陌生,用更加通俗易的话来说就是在职责链模式中,我们可以配置多个处理器,这些处理器可依次对同一个请求进行处理。

具体来看,当一个请求到达时,会依次遍历责任链中配置的处理器。例如,某个请求到达时,其会经过 A 处理器判断,如果可以处理则进行处理,处理完便不再向下传递;反之,则不断向下传递。以此类推,形成一个链条。这一过程其实有点像数据结构中的链表结构。而处于链表上的每个处理器各自承担各自的处理职责,并记录者后续的处理节点,因此也称其为职责链模式。

同时,由于责任链模式酷像链表,因此责任链在实现方式也有点像链表。即每个处理器中维护一个处理器的引用。至此,相信你对责任链模式有了更深刻的认识, 接下来我们便来看看在SpringMVC中的拦截器是如何使用这一模式的。

SpringMVC中的拦截器

责任链模式在SpringMVC中的应用

上图展示了SpringMVC中拦截器的大致工作原理,即

  1. 请求进入拦截器链:当客户端发起一个HTTP请求时,请求首先会进入DispatcherServlet。其内部的doDispatch方法会根据请求的URL和处理器映射(HandlerMapping)找到匹配的处理器Controller

  2. 拦截器执行:在找到匹配的处理器之后,拦截器链开始执行。每个拦截器在请求处理过程的不同阶段都有机会执行自定义逻辑,例如在请求处理前、请求处理后或视图渲染前执行。每个拦截器都有preHandlepostHandleafterCompletion等方法用于执行相应的逻辑。

    • preHandle: 在请求处理前执行,返回true表示继续执行后续拦截器和处理器,返回false将终止请求处理链。
    • postHandle: 在处理器执行后执行
    • afterCompletion: 在视图渲染完成后执行,用于资源清理等操作。
  3. 响应返回给客户端:最终,DispatcherServlet将处理器执行的结果(通常是一个视图)返回给客户端。

进一步,Spring MVC的拦截器的实现责任链模式的一个典型应用。具体来看,其将请求和响应的拦截工作,拆分到了两个函数中实现。HandlerExecutionChain 有关的处理逻辑如下所示:

HandlerExecutionChain # applyPreHandle和applyPostHandle的方法信息


boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
    // <1> 获得拦截器数组
    HandlerInterceptor[] interceptors = getInterceptors();
    if (!ObjectUtils.isEmpty(interceptors)) {
        // <2> 遍历拦截器数组
        for (int i = 0; i < interceptors.length; i++) {
           // 判断能否处理,并进行处理
        }
    }
    // <4> 返回 true ,前置处理成功
    return true;
}


void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)
      throws Exception {

   HandlerInterceptor[] interceptors = getInterceptors();
   // 如果配置有拦截器
   if (!ObjectUtils.isEmpty(interceptors)) {
       // 逆序遍历拦截器信息,省略处理逻辑
      }
   }
}

SpringMVC 框架中,DispatcherServletdoDispatch() 方法来分发请求,它在真正的业 务逻辑执行前后,执行 HandlerExecutionChain 中的 applyPreHandle()applyPostHandle() 方法来对请求进行加工处理。HandlerExecutionChain 中存在一个interceptors数组,用以保存用户所配置的拦截器信息,进而保证了请求进入到控制之前可以对其进行增强处理,这些interceptors中配置拦截器便构成了责任链。

不难发现,在Spring MVC中实现拦截器时的方式没有采用之前讨论的链表的形式。其在HandlerExecutionChain 中存在一个interceptors数组,然后在DispatcherServlet中执行框架配置的拦截器,这其实是责任链模式的一种变体

这样做的目的其实也很明显,就是为了方便用户扩展使用。不妨思考一个问题,如果你要编写一个拦截器,你还需要获取框架内部旧有的拦截器链的末端节点,然后再将自己的拦截逻辑加到其末尾,这种模式想想都很费劲,缺乏可扩展性。

此外,在Spring MVC中拦截器的工作原理与责任链设计模式的原有定义并不相同。在 GoF对责任链的定义中提到 一旦某个处理器能处理这个请求,就不会继续将请求传递给后续的处理器了。

而在Spring MVC的拦截器,虽然也存在拦截器链的概念,但处理方式有所不同。SpringMVC中如果一个拦截器的preHandle方法执行错误,即返回false,则请求处理链将被终止,不会继续向下传递执行。这意味着请求不会继续到达处理器方法(Controller中的方法)或者视图渲染的阶段。

preHandle方法返回true时,请求会继续传递到下一个拦截器(如果有多个拦截器配置)或到达处理器方法,然后执行控制器中的逻辑。如果某个拦截器的preHandle方法返回false,它会中止请求处理链,即不会继续执行后续拦截器的preHandle方法和控制器方法。

可以看到,SpringMVC中拦截器在实现时并没有完全遵照Gof责任链的定义,但从功能上来说,它们可以用于类似的场景。你可以编写多个拦截器,每个拦截器在请求处理的不同阶段执行自定义逻辑,以满足各种需求,例如身份验证、日志记录等。

其实谈到SpringMVC的拦截器,不禁让笔者想到了前几年网上流行的一个很火的面试题就是问SpringMVC的拦截器和Tomcat中的Filter有何区别。在笔者看来两者其实没什么本质区别,都是在请求进入到服务时对请求进行处理,使用到的模式也都是责任链模式,其实看懂如下这张图其实你也就明白了拦截器过滤器的区别。

责任链模式在SpringMVC中的应用

责任链的其他应用场景

职责链模式的原理和应用讲完了,接下来再通过一个实际的例子来帮助理解。对于程序中的日志记录来说,不同的日志信息可能需要被记录到不同的地方,如控制台、文件、数据库等。我们可以使用职责链模式,创建一系列处理者,每个处理者负责将日志信息传递到不同的目标,然后,根据日志的类型或级别来决定如何处理。例如,一个处理者可以将错误日志写入文件,另一个处理器可以将警告日志输出到控制台。除此之外,责任链模式还可应用于下列场景:

1. 认证和授权处理: 在一个网络应用中,用户请求可能需要经过多个层级的认证和授权检查。例如,一个Web应用可能需要检查用户的身份,然后检查他们是否有访问特定资源的权限。这个过程可以使用职责链模式来实现,每个处理者可以处理一种类型的认证或授权检查,如果一个处理者无法通过,请求会传递给下一个处理者,直到找到一个能够通过的处理者或者请求被拒绝。

2. 订单处理: 在电子商务系统中,订单处理可能包含多个步骤,如验证订单信息、库存检查、支付处理、配送等等。这些步骤可以被设计成一个职责链,每个处理者负责一个特定的订单处理步骤。如果某个步骤失败,可以中止整个订单处理过程或者执行一些回滚操作。

总结

虽然GoF 给出的责任链中指出:如果处理器链上的某个处理器能够处理这个请求,那就不会继续往 下传递请求。 但在实际使用中,职责链模式通常会被改造为请求会被所有的处理器都处理一遍,不存在中途终止的情况。例如,SpringMVC的拦截器。