【设计模式】行为型设计模式之 职责链模式,探究过滤器、拦截器、Mybatis插件的底层原理

一、介绍

职责链模式在开发场景中经常被用到,例如框架的过滤器、拦截器、还有 Netty 的编解码器等都是典型的职责链模式的应用。

标准定义

GOF 定义:将请求的发送和接收解耦,让多个接收对象都有机会处理这个请求,将这些接收对象串成一条链,并沿着这条链传递这个请求,直到链条上的某个对象能够处理这个请求为止;

更常见的变体

实际上,职责链的实际应用中往往会更多的使用另一种变体,就是职责链上的每个对象都将请求处理一遍而不是遇到能处理的就终止!

二、代码举例

2.1 第一种:使用链表保存职责链

//抽象的处理对象
public abstract class Handler {//链条的下一个处理对象protected Handler successor = null;public abstract void handle();
}//处理对象实现 HandlerA
public class HandlerA extends Handler{@Overridepublic void handle (){//是否能够处理,默认设置falseboolean handled = false;//如果自己不能够处理,则交由链条的下一个处理对象处理if(!handled && successor!=null){successor.handle();}}
}//处理对象实现 HandlerB
public class HandlerB extends Handler{@Overridepublic void handle (){//是否能够处理,默认设置falseboolean handled = false;//如果自己不能够处理,则交由链条的下一个处理对象处理if(!handled && successor!=null){successor.handle();}}
}
//职责链的链表实现
public class HandlerChain{private Handler head=null;private Handler tail=null;public void addHandler(Handler handler){handler.setSuccessor(null);if(head==null){head=handler;tail=handler;return;}tail.setSuccessor(handler);tail=handler;}public void handle () {if(head !=null){head.handler;}}
}
public class Application {public static void main (String[] args){HandlerChain chain = new HandlerChain();chain.addHandler(new HandlerA());chain.addHandler(new HandlerB());chain.handler();}
}

**存在的问题:**handler 函数中存在对下一个处理器的调用,一旦被忘掉就链条就断了。
**改进:**使用模板方法模式改进

使用模板方法改进版本
//抽象的处理对象
public abstract class Handler {//链条的下一个处理对象protected Handler successor = null;//模板方法public final void handle(){boolean handled = doHandle();if(successor!=null && !handled){successor.handle();}}//抽象方法,由子类重写public abstract boolean doHandle();
}//处理对象实现 HandlerA
public class HandlerA extends Handler{@Overridepublic void handle (){//是否能够处理,默认设置falseboolean handled = false;....return handled;}
}//处理对象实现 HandlerB
public class HandlerB extends Handler{@Overridepublic void handle (){//是否能够处理,默认设置falseboolean handled = false;....return handled;}
}

2.2 第二种:使用数组保存职责链

public interface IHandler{boolean handle();
}//处理对象实现 HandlerA
public class HandlerA implements IHandler{@Overridepublic void handle (){//是否能够处理,默认设置falseboolean handled = false;....return handled;}
}//处理对象实现 HandlerB
public class HandlerB extends IHandler{@Overridepublic void handle (){//是否能够处理,默认设置falseboolean handled = false;....return handled;}
}
public class HandlerCHain{private List<IHandler> handlers = new ArrayList<>();public void addHandler(Handler handler){this.handlers.add(handler);}public void handle(){for(Ihandler handler : handlers){boolea handled = handler.handle();if(handled){break;}}}
}

三、应用举例

3.1 职责链模式过滤敏感词

处理敏感词有两种方式第一种是包含敏感词直接禁止发布,可以使用职责链模式的标准模式。
第二种则是包含敏感词后直接替换为其他符号后扔给下一个处理器处理,可以使用职责链的常见变体。

3.2 职责链模式在 Servlet Filter 中的应用剖析

Servlet Filter 介绍
  1. Servlet Filter 过滤器可以实现对 Http 请求的过滤功能,可以实现鉴权、限流、记录日志、参数验证等功能。
  2. Servlet Filter 是 Servlet 规范的一部分,只要支持 Servlet 规范的 Web 容器,例如 Tomcat、Jetty 等都支持过滤器功能。
过滤器使用举例
public class LogFilter implements Filter{@Overridepublic void init (FilterConfig filterConfig) throws ServletException{//Filter创建时自动调用    }@Overridepublic void doFilter(ServletRequest request,ServletResponse response,FilterChain chain) throws IOException,ServletException{System.out.println("请求路径上的处理");chain.doFilter(request,response);System.out.pringln("响应路径上的处理");}@Overridepublic void destory(){//销毁filter时自动调用}
}
  1. 过滤器的使用非常简单,实现 Filter 接口后添加过滤器配置即可,这完美符合开闭原则.
  2. 过滤器中使用了职责链模式,其中 Filter 就是职责链的 Handler,FilterChain 对应着就是 HandlerChain
过滤器 FilterChain 简化源码简单解析
// org.apache.catalina.core.ApplicationFilterChain 类的简化表示
public class ApplicationFilterChain {// 过滤器数组,存储了配置在某个servlet前的所有过滤器private ApplicationFilterConfig[] filters;// 链中的下一个实体,通常是目标Servletprivate Servlet servlet;// 当前请求在过滤器链中的位置private int pos = 0; // 注意:实际实现中可能不会直接暴露此字段作为公共状态public ApplicationFilterChain(Filter[] filters, Servlet servlet) {this.filters = filters;this.servlet = servlet;}// 处理请求的主要方法public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {// 如果还有过滤器待执行if (pos < filters.length) {// 获取当前要执行的过滤器ApplicationFilterConfig filterConfig = filters[pos++];Filter currentFilter = filterConfig.getFilter();// 调用过滤器的doFilter方法,将责任传递给下一个过滤器或最终的ServletcurrentFilter.doFilter(request, response, this); // 注意这里将自身作为参数传递,形成链式调用} else {// 所有过滤器都已执行完毕,调用目标Servlet处理请求servlet.service(request, response);}}
}
  1. 这里的 doFilter 方法其实是一个递归调用,一直调用到最后一个 Filter 后,会调用真正的 Servlet 的 Service 方法执行具体的业务逻辑
  2. servlet 的 service 方法执行之后,会开始执行 Filter.doFilter 方法中chain.doFilter(request,response);之后的部分。也就是说,Servlet 的 filter 机制使用了 doFilter 方法的递归调用实现了一个请求和响应的双向拦截,非常巧妙。

3.3 职责链模式在 SpringMVC Interceptor 中的应用剖析

SpringMVC 拦截器介绍

SpringMVC 的拦截器 Interceptor 和上述的过滤器的作用十分相似,他们都是用来实现对 Http 请求实现拦截处理的功能。
不同之处在于

  1. Servlet 的过滤器是 Servlet 规范的一部分,由 Web 容器提供代码实现
  2. Spring Interceptor 是 SpringMVC 框架的一部分,由 SpringMVC 框架提供代码实现。拦截器只能拦截被扫描注册到 SpringMVC 的 dispatch 中的 Handler。对于一些静态资源,直接由 Web 容器管理的是无法被拦截器拦截的。

Spring MVC 的 Interceptor 使用举例
@Component
public class LoggingInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 在处理请求前执行System.out.println("Request URL: " + request.getRequestURL() + ", Start Time: " + System.currentTimeMillis());return true; // 返回true表示继续执行后续的Interceptor和Handler}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {// 在Handler方法调用后,渲染视图前执行// 这里可以对ModelAndView进行操作,但本例中不做任何处理}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {// 在整个请求处理完成之后执行,可以做清理工作long endTime = System.currentTimeMillis();System.out.println("Request URL: " + request.getRequestURL() + ", End Time: " + endTime + ", Duration: " + (endTime - request.getAttribute("startTime")));}
}
Spring MVC Interceptor 职责链实现分析

Interceptor 也是基于职责链模式实现的,其职责链的实现中的处理器链为 HandlerExecutionChain ,其简化源代码如下

package org.springframework.web.servlet;import java.util.ArrayList;
import java.util.List;public class HandlerExecutionChain {private final Object handler;private List<HandlerInterceptor> interceptors;public HandlerExecutionChain(Object handler) {this.handler = handler;this.interceptors = null;}public HandlerExecutionChain(Object handler, List<HandlerInterceptor> interceptors) {this.handler = handler;this.interceptors = (interceptors != null ? new ArrayList<>(interceptors) : null);}public void addInterceptor(HandlerInterceptor interceptor) {if (this.interceptors == null) {this.interceptors = new ArrayList<>();}this.interceptors.add(interceptor);}public Object getHandler() {return this.handler;}public List<HandlerInterceptor> getInterceptors() {return this.interceptors;}// 应用所有拦截器的preHandle方法public boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {if (this.interceptors != null) {for (HandlerInterceptor interceptor : this.interceptors) {if (!interceptor.preHandle(request, response, this.handler)) {return false; // 如果任意拦截器返回false,则短路后续处理}}}return true;}// 应用所有拦截器的postHandle方法public void applyPostHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView mv) throws Exception {if (this.interceptors != null) {for (HandlerInterceptor interceptor : this.interceptors) {interceptor.postHandle(request, response, this.handler, mv);}}}// 触发所有拦截器的afterCompletion方法public void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, Exception ex) throws Exception {if (this.interceptors != null) {for (int i = this.interceptors.size() - 1; i >= 0; i--) {this.interceptors.get(i).afterCompletion(request, response, this.handler, ex);}}}
}

简化后的 DispatchServlet 代码

public class DispatcherServlet extends HttpServletBean {// 省略了其他属性和方法...// 主要的请求分发方法protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {// 获取HandlerExecutionChain,这包括了Handler和一系列的InterceptorHandlerExecutionChain handlerExecutionChain = getHandlerExecutionChain(request);// 尝试应用所有拦截器的preHandle方法if (!handlerExecutionChain.applyPreHandle(request, response)) {return; // 如果有拦截器返回false,直接返回,不再继续处理}// 此处省略了根据Handler实际执行业务逻辑的部分// 例如,通过反射调用Controller方法,处理异常等ModelAndView modelAndView = null; // 假设这是Controller方法执行后的结果// 应用所有拦截器的postHandle方法if (modelAndView != null) {handlerExecutionChain.applyPostHandle(request, response, modelAndView);}// 渲染视图的逻辑省略...// 最后,触发所有拦截器的afterCompletion方法triggerAfterCompletion(request, response, null); // 假设没有异常发生}// 获取HandlerExecutionChain的简化方法private HandlerExecutionChain getHandlerExecutionChain(HttpServletRequest request) throws Exception {// 实际上这里会通过HandlerMapping查找合适的Handler并创建HandlerExecutionChainObject handler = // ... 省略了查找逻辑List<HandlerInterceptor> interceptors = // ... 省略了获取拦截器的逻辑return new HandlerExecutionChain(handler, interceptors);}// 触发所有拦截器的afterCompletion方法的简化实现private void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, Exception ex) throws Exception {HandlerExecutionChain handlerExecutionChain = getHandlerExecutionChain(request);handlerExecutionChain.triggerAfterCompletion(request, response, ex);}
}

SpirngMVC 的 HandlerExecutionChain 没有使用递归,而是将前置处理和后置处理拆成两个方法分别执行代码更加的直观。
DispatchServlet 类的在 doDispatch 中,会在调用 Handler 处理逻辑前后,分别调用过滤器链的 preHandler 方法

3.4 职责链模式在 MyBatis plugin 中的应用剖析

Mybatis 的插件机制和上面的过滤器、拦截器机制都很相似,都是在不修改原有流程代码的情况下拦截某些方法的调用进行链式处理。

MyBatis 插件的举例
@Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class SqlExecutionTimeInterceptor implements Interceptor {private static final Logger logger = LoggerFactory.getLogger(SqlExecutionTimeInterceptor.class);@Overridepublic Object intercept(Invocation invocation) throws Throwable {long start = System.currentTimeMillis();try {return invocation.proceed();} finally {long end = System.currentTimeMillis();MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];Object parameter = null;if (invocation.getArgs().length > 1) {parameter = invocation.getArgs()[1];}BoundSql boundSql = mappedStatement.getBoundSql(parameter);String sql = boundSql.getSql();long time = (end - start);logger.info("SQL: {} 耗时: {} ms", sql, time);}}@Overridepublic Object plugin(Object target) {return Plugin.wrap(target, this);}@Overridepublic void setProperties(Properties properties) {// 可以在这里读取自定义配置,如果有的话}
}

Mybatis 插件通过@Intercepts注解来指定拦截器拦截的范围,这个注解嵌套@Signature注解 该注解通过三个参数,type、method、args 来指明要拦截的类,要拦截的方法名、要拦截的方法对应的参数。通过制定这三个元素我们就能完全确定要拦截的具体是哪个方法了。

Mybatis 插件机制原理

Mybatis 使用 Executor 类执行 SQL 语句,Executor 类会创建 StatementHandler、ParameterHandler 和 ResultSetHandler 三个类的对象,Executor 执行 sql 时这三个对象按照下方顺序被调用。只要拦截这几个类的方法就可以实现非常多的功能了。

  1. 首先使用 ParameterHandler 类来解析替换 SQL 中的占位符
  2. 使用 StatementHandler 来执行 SQL 语句
  3. 最后使用 ResultSetHandler 来封装 SQL 的结果

插件的执行过程

  1. Mybatis 解析配置后,将所有的拦截器加载到 InterceptorChain 中
//这里包含着一个方法的反射调用
public class Invocation {privatre final Object target;privatre final Method method;privatre final Object[] args;//省略构造函数和getter方法public Object proceed() throws InvocatrionTargetException,IllegalAccessException{return method.invocation(target,args);  }
}//这是拦截器的接口代码
public interface Interceptor{Object intercept(Invocation invocation) throw Throwable;Object plugin(Object target);void setProperties(Properties properties);
}//InterceptorChain Mybatis解析配置后,所有注册的Interceptor都保存到这儿
public class InterceptorChain{private final List<Interceptro> interceptors = new ArrayList<Interceptor>();public Object pluginAll(Object target){//这里循环对代理对象进行重复代理for(Interceptor interceptor : interceptors){//这里的plugin方法调用的Plugin.wrap方法,生成了一个代理对象target = interceptor.plugin(target);}return target;}
}
  1. Mybatis 执行 SQL 的过程中,会创建 Executor、ParameterHandler、StatementHandler、ResultSetHandler 等四个对象创建的代码在 Configuration 中。这四个对象的创建代码都会调用 InterceptorChain 的pluginAll 方法。
  2. pluginAll 方法的逻辑很简单,就是循环调用 Interceptor 的 plgin 方法,这个方法一般我们都会直接调用 Plugin 的静态 wrapper 方法
  3. Plugin 的 wrapper 方法的逻辑
    1. 检测 Interceptor 上的签名中是否包含当前 target 的类
    2. 如果不包含,则原样返回 target 也就是上面四个对象
    3. 如果包含,则生成代理对象,代理逻辑如下
      1. 判断当前方法是否是拦截器拦截的方法,如果是则调用拦截器的 Interceptor 方法。拦截器的 intercept 方法传入的 Invocation 则是使用代理对象传入的方法也就是被多层代理的对象。
      2. 如果当前方法不是拦截器拦截的方法,那么则直接调用被代理的原始方法。
    4. 最终代理对象一层层执行后,最后执行的才是那四个对象的原始方法。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/850393.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

k8s小型实验模拟

&#xff08;1&#xff09;Kubernetes 区域可采用 Kubeadm 方式进行安装。&#xff08;5分&#xff09; &#xff08;2&#xff09;要求在 Kubernetes 环境中&#xff0c;通过yaml文件的方式&#xff0c;创建2个Nginx Pod分别放置在两个不同的节点上&#xff0c;Pod使用hostPat…

Java | Leetcode Java题解之第139题单词拆分

题目&#xff1a; 题解&#xff1a; public class Solution {public boolean wordBreak(String s, List<String> wordDict) {Set<String> wordDictSet new HashSet(wordDict);boolean[] dp new boolean[s.length() 1];dp[0] true;for (int i 1; i < s.len…

JimuReport 积木报表 v1.7.52 版本发布,免费的低代码报表

项目介绍 一款免费的数据可视化报表工具&#xff0c;含报表和大屏设计&#xff0c;像搭建积木一样在线设计报表&#xff01;功能涵盖&#xff0c;数据报表、打印设计、图表报表、大屏设计等&#xff01; Web 版报表设计器&#xff0c;类似于excel操作风格&#xff0c;通过拖拽完…

智能变电站网络报文记录及故障录波分析装置

是基于Intel X86、PowerPC、FPGA等技术的高度集成化的硬件平台&#xff0c;采用了高性能CPU无风扇散热、网络数据采集、高速数据压缩存储加密等多种技术&#xff0c;实现了高性能计算、多端口同步高速数据采集、数据实时分析、大容量数据存储等功能。 ● 在满足工业标准的同时&…

数据结构 -- 树状数组

前言 树状数组或二叉索引树&#xff08;Binary Indexed Tree&#xff09;&#xff0c;又以其发明者命名为 Fenwick 树。其初衷是解决数据压缩里的累积频率的计算问题&#xff0c;现多用于高效计算数列的前缀和、区间和。它可以以 O(logn) 的时间得到任意前缀和。并同时支持在 …

Kali Linux 2024.2 释出

渗透测试发行版 Kali Linux 释出了最新的 2024.2。 主要新特性包括&#xff1a;桌面环境更新到 GNOME 46&#xff0c;Xfce 环境加入 HiDPI 模式&#xff0c;更新了网络侦察工具 AutoRecon&#xff0c;监视 Linux 进程的命令行工具 pspy&#xff0c;提取和显示 CVE 信息的 Splo…

项目验收总体计划书(实际项目验收原件参考Word)

测试目标&#xff1a;确保项目的需求分析说明书中的所有功能需求都已实现&#xff0c;且能正常运行&#xff1b;确保项目的业务流程符合用户和产品设计要求&#xff1b;确保项目的界面美观、风格一致、易学习、易操作、易理解。 软件全套文档过去进主页。 一、 前言 &#xff0…

Unity 编辑器扩展,获取目录下所有的预制件

先看演示效果 实现方案 1创建几个用于测试的cube 2&#xff0c;创建一个Editor脚本 3&#xff0c;编写脚本内容 附上源码 using UnityEditor; using UnityEngine;public class GetPrefeb : EditorWindow {private string folderPath "Assets/Resources"; // 指定预…

[FSCTF 2023]Tea_apk

得到密文和密钥 import base64 from ctypes import c_uint32import libnumDELTA 0x9E3779B9def decrypt(v, n, k):rounds 6 int(52 / n)sum c_uint32(rounds * DELTA)y v[0].valuewhile rounds > 0:e (sum.value >> 2) & 3p n - 1while p > 0:z v[p …

Django 连接mysql数据库配置

1&#xff0c;提前创建注册的app1应用 Test/Test/settings.py python manage.py startapp app1 2&#xff0c;配置mysql数据库连接 Test/Test/settings.py DATABASES {default: {ENGINE: django.db.backends.mysql,# 数据库名字NAME: db1,# 连接mysql数据库用户名USER: ro…

Python 基于阿里云的OSS对象存储服务实现本地文件上云框架

Python 基于阿里云的OSS对象存储服务实现将文件上云框架 文章目录 Python 基于阿里云的OSS对象存储服务实现将文件上云框架一、前言二、阿里云配置1、获取用户AKEY和AKeySecret2、创建Bucket 三、Python 阿里云oss上云框架1、安装oss2依赖库2、阿里云oss python 一、前言 未来…

2024年CKA模拟系统制作 | step-by-step | 1、基础环境准备

目录 一、软件环境 二、虚拟网络环境准备 1、编辑虚拟网络 2、网络设置 三、新建虚拟主机 1、新建目录 2、新建虚拟主机 四、系统安装 1、装载系统镜像 2、开启虚拟机 3、选择语言 4、键盘选择 5、网络配置 6、代理设置 7、设置软件源 8、存储设置 9、名称设置 …

摆脱Jenkins - 使用google cloudbuild 部署 java service 到 compute engine VM

在之前 介绍 cloud build 的文章中 初探 Google 云原生的CICD - CloudBuild 已经介绍过&#xff0c; 用cloud build 去部署1个 spring boot service 到 cloud run 是很简单的&#xff0c; 因为部署cloud run 无非就是用gcloud 去部署1个 GAR 上的docker image 到cloud run 容…

42【Aseprite 作图】梅花盆栽——拆解

1 花盆 是高度比较低的盆&#xff0c;只有一个下2&#xff1b;上面分两个 1 2 盆边缘颜色深&#xff0c;上面靠近外面的颜色浅&#xff0c;正面颜色稍微深一点&#xff0c;画两条竖的浅色线&#xff0c;作为装饰 2 花盆中的沙石 沙子颜色深一点&#xff0c;中间有浅一点的线…

[office] excel工作表数据分级显示 #其他#笔记

excel工作表数据分级显示 如下图1所示的工作表数据&#xff0c;我们按东区、西区、南区、北区来建立分级显示。 图1 这里先利用“创建组”命令建立分级显示。选取单元格区域A3:E5&#xff0c;单击功能区“数据”选项卡“分级显示”组中的“创建组——创建组…”命令&#xff…

【c语言】qsort函数及泛型冒泡排序的模拟实现

&#x1f31f;&#x1f31f;作者主页&#xff1a;ephemerals__ &#x1f31f;&#x1f31f;所属专栏&#xff1a;C语言 目录 一、qsort函数 1.回调函数 2.qsort函数 3.void* 指针 二、泛型冒泡排序的模拟实现 1.比较函数的编写 2.交换函数的编写 3.冒泡排序的编写 4…

JWT 快速入门

什么是 JWT JSON Web Token&#xff08;JWT&#xff09;是目前最流行的跨域身份验证解决方案 JSON Web Token Introduction - jwt.ioLearn about JSON Web Tokens, what are they, how they work, when and why you should use them.https://jwt.io/introduction 一、常见会…

APP单页分发源码下载安卓苹果自动识别apk描述文件免签自动安装

下载地址&#xff1a;APP单页分发源码下载安卓苹果自动识别apk描述文件免签自动安装

MySQL数据库操作基础(增删查改)

数据库操作基础(增删查改) 1.插入 1.1往数据表中插入一条数据 insert into 表名 values (值,值,值...)此处列出的这些值的数目和类型 要和表的相对应 1.2指定列插入 insert into 表名(列名) values (值);1.3一次插入多个记录 insert into 表名 values (值),(值)...一次插入…

wordpress主题导航主题v4.16.2哈哈版

1.下载授权接口源码onenav-auth-api-v2.zip &#xff0c;在宝塔新建一个网站&#xff0c;域名为 auth.iotheme.cn&#xff0c;设置wordpress伪静态&#xff0c;申请ssl证书。将上面源码解压后上传到此网站根目录。 2. 在宝塔根目录etc下 hosts 中添加 127.0.0.1 auth.iotheme.…