责任链模式在SpringMVC中的应用
点击小卡片参与粉丝专属福利?思考,输出,沉淀。用通俗的语言陈述技术,让自己和他人都有所收获。
作者:毅航??
?本文皆在讨论责任链设计模式在SpringMVC拦截器中应用。因此,并不会对SpringMVC中拦截器工作原理进行详细描述,更多细节可参考SpringMVC流程分析(五):HandlerInterceptor组件分析(https://juejin.cn/post/7262342076397830204)
前言有关责任链模式的网上有很多文章介绍,但万变不离其宗,责任链的本质就是「一种将请求发送者和接收者解耦的设计模式。其将多个对象组成链式结构,每个对象都有机会处理请求,直到遍历到可以处理的处理器。」
这样说你还是会感觉有点陌生,用更加通俗易的话来说就是在职责链模式中,我们可以配置多个处理器,这些处理器可依次对同一个请求进行处理。
「具体来看,当一个请求到达时,会依次遍历责任链中配置的处理器。例如,某个请求到达时,其会经过 A处理器判断,如果可以处理则进行处理,处理完便不再向下传递;反之,则不断向下传递。以此类推,形成一个链条。这一过程其实有点像数据结构中的链表结构。而处于链表上的每个处理器各自承担各自的处理职责,并记录者后续的处理节点,因此也称其为职责链模式。」
同时,由于责任链模式酷像链表,因此责任链在实现方式也有点像链表。即每个处理器中维护一个处理器的引用。至此,相信你对责任链模式有了更深刻的认识, 接下来我们便来看看在SpringMVC中的拦截器是如何使用这一模式的。
SpringMVC中的拦截器
上图展示了SpringMVC中拦截器的大致工作原理,即
「请求进入拦截器链」:当客户端发起一个HTTP请求时,请求首先会进入DispatcherServlet。其内部的doDispatch方法会根据请求的URL和处理器映射(HandlerMapping)找到匹配的处理器Controller。
「拦截器执行」:在找到匹配的处理器之后,拦截器链开始执行。每个拦截器在请求处理过程的不同阶段都有机会执行自定义逻辑,例如在请求处理前、请求处理后或视图渲染前执行。每个拦截器都有preHandle、postHandle和afterCompletion等方法用于执行相应的逻辑。
preHandle: 在请求处理前执行,返回true表示继续执行后续拦截器和处理器,返回false将终止请求处理链。postHandle: 在处理器执行后执行afterCompletion: 在视图渲染完成后执行,用于资源清理等操作。「响应返回给客户端」:最终,DispatcherServlet将处理器执行的结果(通常是一个视图)返回给客户端。
进一步,Spring MVC的拦截器的实现责任链模式的一个典型应用。具体来看,其将请求和响应的拦截工作,拆分到了两个函数中实现。HandlerExecutionChain有关的处理逻辑如下所示:
?HandlerExecutionChain # applyPreHandle和applyPostHandle的方法信息
?
booleanapplyPreHandle(HttpServletRequestrequest,HttpServletResponseresponse)throwsException{
//1获得拦截器数组
HandlerInterceptor[]interceptors=getInterceptors();
if(!ObjectUtils.isEmpty(interceptors)){
//2遍历拦截器数组
for(inti=0;iinterceptors.length;i++){
//判断能否处理,并进行处理
}
}
//4返回true,前置处理成功
returntrue;
}
voidapplyPostHandle(HttpServletRequestrequest,HttpServletResponseresponse,@NullableModelAndViewmv)
throwsException{
HandlerInterceptor[]interceptors=getInterceptors();
//如果配置有拦截器
if(!ObjectUtils.isEmpty(interceptors)){
//逆序遍历拦截器信息,省略处理逻辑
}
}
}
在SpringMVC 框架中,DispatcherServlet 的 doDispatch()方法来分发请求,它在真正的业 务逻辑执行前后,执行 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有何区别。在笔者看来两者其实没什么本质区别,都是在请求进入到服务时对请求进行处理,使用到的模式也都是责任链模式,其实看懂如下这张图其实你也就明白了拦截器和过滤器的区别。
责任链的其他应用场景职责链模式的原理和应用讲完了,接下来再通过一个实际的例子来帮助理解。对于程序中的日志记录来说,不同的日志信息可能需要被记录到不同的地方,如控制台、文件、数据库等。我们可以使用职责链模式,创建一系列处理者,每个处理者负责将日志信息传递到不同的目标,然后,根据日志的类型或级别来决定如何处理。例如,一个处理者可以将错误日志写入文件,另一个处理器可以将警告日志输出到控制台。除此之外,责任链模式还可应用于下列场景:
「1. 认证和授权处理:」 在一个网络应用中,用户请求可能需要经过多个层级的认证和授权检查。例如,一个Web应用可能需要检查用户的身份,然后检查他们是否有访问特定资源的权限。这个过程可以使用职责链模式来实现,每个处理者可以处理一种类型的认证或授权检查,如果一个处理者无法通过,请求会传递给下一个处理者,直到找到一个能够通过的处理者或者请求被拒绝。
「2. 订单处理:」 在电子商务系统中,订单处理可能包含多个步骤,如验证订单信息、库存检查、支付处理、配送等等。这些步骤可以被设计成一个职责链,每个处理者负责一个特定的订单处理步骤。如果某个步骤失败,可以中止整个订单处理过程或者执行一些回滚操作。
总结虽然GoF 给出的责任链中指出:「如果处理器链上的某个处理器能够处理这个请求,那就不会继续往 下传递请求。」 但在实际使用中,职责链模式通常会被改造为请求会被所有的处理器都处理一遍,不存在中途终止的情况。例如,SpringMVC的拦截器。
如果文章对你有帮助的话欢迎「关注+点赞+收藏」
阅读原文
网站开发网络凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设、网站改版、域名注册、主机空间、手机网站建设、网站备案等方面的需求...
请立即点击咨询我们或拨打咨询热线:13245491521 13245491521 ,我们会详细为你一一解答你心中的疑难。 项目经理在线