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

本文主要是介绍【设计模式】行为型设计模式之 职责链模式,探究过滤器、拦截器、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. 最终代理对象一层层执行后,最后执行的才是那四个对象的原始方法。

这篇关于【设计模式】行为型设计模式之 职责链模式,探究过滤器、拦截器、Mybatis插件的底层原理的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1044703

相关文章

如何突破底层思维方式的牢笼

我始终认为,牛人和普通人的根本区别在于思维方式的不同,而非知识多少、阅历多少。 在这个世界上总有一帮神一样的人物存在。就像读到的那句话:“人类就像是一条历史长河中的鱼,只有某几条鱼跳出河面,看到世界的法则,但是却无法改变,当那几条鱼中有跳上岸,进化了,改变河道流向,那样才能改变法则。”  最近一段时间一直在不断寻在内心的东西,同时也在不断的去反省和否定自己的一些思维模式,尝试重

如何开启和关闭3GB模式

https://jingyan.baidu.com/article/4d58d5414dfc2f9dd4e9c082.html

持久层 技术选型如何决策?JPA,Hibernate,ibatis(mybatis)

转自:http://t.51jdy.cn/thread-259-1-1.html 持久层 是一个项目 后台 最重要的部分。他直接 决定了 数据读写的性能,业务编写的复杂度,数据结构(对象结构)等问题。 因此 架构师在考虑 使用那个持久层框架的时候 要考虑清楚。 选择的 标准: 1,项目的场景。 2,团队的技能掌握情况。 3,开发周期(开发效率)。 传统的 业务系统,通常业

十五.各设计模式总结与对比

1.各设计模式总结与对比 1.1.课程目标 1、 简要分析GoF 23种设计模式和设计原则,做整体认知。 2、 剖析Spirng的编程思想,启发思维,为之后深入学习Spring做铺垫。 3、 了解各设计模式之间的关联,解决设计模式混淆的问题。 1.2.内容定位 1、 掌握设计模式的"道" ,而不只是"术" 2、 道可道非常道,滴水石穿非一日之功,做好长期修炼的准备。 3、 不要为了

十四、观察者模式与访问者模式详解

21.观察者模式 21.1.课程目标 1、 掌握观察者模式和访问者模式的应用场景。 2、 掌握观察者模式在具体业务场景中的应用。 3、 了解访问者模式的双分派。 4、 观察者模式和访问者模式的优、缺点。 21.2.内容定位 1、 有 Swing开发经验的人群更容易理解观察者模式。 2、 访问者模式被称为最复杂的设计模式。 21.3.观察者模式 观 察 者 模 式 ( Obser

Linux系统稳定性的奥秘:探究其背后的机制与哲学

在计算机操作系统的世界里,Linux以其卓越的稳定性和可靠性著称,成为服务器、嵌入式系统乃至个人电脑用户的首选。那么,是什么造就了Linux如此之高的稳定性呢?本文将深入解析Linux系统稳定性的几个关键因素,揭示其背后的技术哲学与实践。 1. 开源协作的力量Linux是一个开源项目,意味着任何人都可以查看、修改和贡献其源代码。这种开放性吸引了全球成千上万的开发者参与到内核的维护与优化中,形成了

WordPress网创自动采集并发布插件

网创教程:WordPress插件网创自动采集并发布 阅读更新:随机添加文章的阅读数量,购买数量,喜欢数量。 使用插件注意事项 如果遇到404错误,请先检查并调整网站的伪静态设置,这是最常见的问题。需要定制化服务,请随时联系我。 本次更新内容 我们进行了多项更新和优化,主要包括: 界面设置:用户现在可以更便捷地设置文章分类和发布金额。代码优化:改进了采集和发布代码,提高了插件的稳定

数据库原理与安全复习笔记(未完待续)

1 概念 产生与发展:人工管理阶段 → \to → 文件系统阶段 → \to → 数据库系统阶段。 数据库系统特点:数据的管理者(DBMS);数据结构化;数据共享性高,冗余度低,易于扩充;数据独立性高。DBMS 对数据的控制功能:数据的安全性保护;数据的完整性检查;并发控制;数据库恢复。 数据库技术研究领域:数据库管理系统软件的研发;数据库设计;数据库理论。数据模型要素 数据结构:描述数据库

vscode-创建vue3项目-修改暗黑主题-常见错误-element插件标签-用法涉及问题

文章目录 1.vscode创建运行编译vue3项目2.添加项目资源3.添加element-plus元素4.修改为暗黑主题4.1.在main.js主文件中引入暗黑样式4.2.添加自定义样式文件4.3.html页面html标签添加样式 5.常见错误5.1.未使用变量5.2.关闭typescript检查5.3.调试器支持5.4.允许未到达代码和未定义代码 6.element常用标签6.1.下拉列表