职责链模式:如何实现可灵活扩展算法的敏感信息过滤框架?

        今天,我们主要讲解职责链模式的原理和实现。除此之外,我还会利用职责链模式,带你实现一个可以灵活扩展算法的敏感词过滤框架。下一节课,我们会更加贴近实战,通过剖析Servlet Filter、Spring Interceptor来看,如何利用职责链模式实现框架中常用的过滤器、拦截器。

职责链模式的原理和实现

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

在职责链模式中,多个处理器(也就是刚刚定义中说的“接收对象”)依次处理同一个请求。一个请求先经过A处理器处理,然后再把请求传递给B处理器,B处理器处理完后再传递给C处理器,以此类推,形成一个链条。链条上的每个处理器各自承担各自的处理职责,所以叫作职责链模式。

 第一种实现如下:Handler 是所有处理器类的抽象父类,handle()是抽象方法,每个具体的处理器类HandlerA 和HadnlerB的handle() 函数的代码结构类似,如果它能处理该请求就不继续往下传递,如果不能处理就需要往下传递了。

public abstract class Handler {protected Handler successor = null;public void setSuccessor(Handler successor) {this.successor = successor;}public abstract void handle();
}public class HandlerA extends Handler {@Overridepublic void handle() {boolean handled = false;//...if (!handled && successor != null) {successor.handle();}}
}public class HandlerB extends Handler {@Overridepublic void handle() {boolean 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.handle();}}
}// 使用举例
public class Application {public static void main(String[] args) {HandlerChain chain = new HandlerChain();chain.addHandler(new HandlerA());chain.addHandler(new HandlerB());chain.handle();}
}

实际上,上面的代码实现不够优雅。处理器类的handle()函数,不仅包含自己的业务逻辑,还包含对下一个处理器的调用,也就是代码中的successor.handle()。一个不熟悉这种代码结构的程序员,在添加新的处理器类的时候,很有可能忘记在handle()函数中调用successor.handle(),这就会导致代码出现bug。

针对这个问题,我们对代码进行重构,利用模板模式或者接口,将调用successor.handle()的逻辑从具体的处理器类中剥离出来,放到抽象父类中。这样具体的处理器类只需要实现自己的业务逻辑就可以了。重构之后的代码如下所示:

public interface IHandler {boolean handle();
}public class HandlerA implements IHandler {@Overridepublic boolean handle() {boolean handled = false;//...return handled;}
}public class HandlerB implements IHandler {@Overridepublic boolean handle() {boolean handled = false;//...return handled;}
}public class HandlerChain {private List<IHandler> handlers = new ArrayList<>();public void addHandler(IHandler handler) {this.handlers.add(handler);}public void handle() {for (IHandler handler : handlers) {boolean handled = handler.handle();if (handled) {break;}}}
}// 使用举例
public class Application {public static void main(String[] args) {HandlerChain chain = new HandlerChain();chain.addHandler(new HandlerA());chain.addHandler(new HandlerB());chain.handle();}
}

实际上,职责链模式还有一种变体,那就是请求会被所有的处理器都处理一遍,不存在中途终止的情况。这种变体也有两种实现方式:用链表存储处理器和用数组存储处理器,跟上面的两种实现方式类似,只需要稍微修改即可。

 仅仅将handle代码的if判断去掉即可

public void handle() {for (IHandler handler : handlers) {boolean handled = handler.handle();}}

职责链模式的应用场景举例

对于支持UGC(User Generated Content,用户生成内容)的应用(比如论坛)来说,用户生成的内容(比如,在论坛中发表的帖子)可能会包含一些敏感词(比如涉黄、广告、反动等词汇)。针对这个应用场景,我们就可以利用职责链模式来过滤这些敏感词。

public interface SensitiveWordFilter {boolean doFilter(Content content);
}public class SexyWordFilter implements SensitiveWordFilter {@Overridepublic boolean doFilter(Content content) {boolean legal = true;//...return legal;}
}// PoliticalWordFilter、AdsWordFilter类代码结构与SexyWordFilter类似public class SensitiveWordFilterChain {private List<SensitiveWordFilter> filters = new ArrayList<>();public void addFilter(SensitiveWordFilter filter) {this.filters.add(filter);}// return true if content doesn't contain sensitive words.public boolean filter(Content content) {for (SensitiveWordFilter filter : filters) {if (!filter.doFilter(content)) {return false;}}return true;}
}public class ApplicationDemo {public static void main(String[] args) {SensitiveWordFilterChain filterChain = new SensitiveWordFilterChain();filterChain.addFilter(new AdsWordFilter());filterChain.addFilter(new SexyWordFilter());filterChain.addFilter(new PoliticalWordFilter());boolean legal = filterChain.filter(new Content());if (!legal) {// 不发表} else {// 发表}}
}

首先,我们来看,职责链模式如何应对代码的复杂性。

将大块代码逻辑拆分成函数,将大类拆分成小类,是应对代码复杂性的常用方法。应用职责链模式,我们把各个敏感词过滤函数继续拆分出来,设计成独立的类,进一步简化了SensitiveWordFilter类,让SensitiveWordFilter类的代码不会过多,过复杂。

其次,我们再来看,职责链模式如何让代码满足开闭原则,提高代码的扩展性。

当我们要扩展新的过滤算法的时候,比如,我们还需要过滤特殊符号,按照非职责链模式的代码实现方式,我们需要修改SensitiveWordFilter的代码,违反开闭原则。不过,这样的修改还算比较集中,也是可以接受的。而职责链模式的实现方式更加优雅,只需要新添加一个Filter类,并且通过addFilter()函数将它添加到FilterChain中即可,其他代码完全不需要修改。


上面介绍了如何责任链模式的原理和应用场景,那么接下来我们看一下责任链模式在框架中的应用,为框架提供可靠的扩展点。

Servlet Filter

Servlet Filter是Java Servlet规范中定义的组件,翻译成中文就是过滤器,它可以实现对HTTP请求的过滤功能,比如鉴权、限流、记录日志、验证参数等等。因为它是Servlet规范的一部分,所以,只要是支持Servlet的Web容器(比如,Tomcat、Jetty等),都支持过滤器功能。为了帮助你理解,我画了一张示意图阐述它的工作原理,如下所示。

 在实际项目中,我们该如何使用Servlet Filter呢?我写了一个简单的示例代码,如下所示。添加一个过滤器,我们只需要定义一个实现javax.servlet.Filter接口的过滤器类,并且将它配置在web.xml配置文件中。Web容器启动的时候,会读取web.xml中的配置,创建过滤器对象。当有请求到来的时候,会先经过过滤器,然后才由Servlet来处理。主要是涉及到过滤器的处理范围。

public class LogFilter implements Filter {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {// 在创建Filter时自动调用,// 其中filterConfig包含这个Filter的配置参数,比如name之类的(从配置文件中读取的)}@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {System.out.println("拦截客户端发送来的请求.");chain.doFilter(request, response);System.out.println("拦截发送给客户端的响应.");}@Overridepublic void destroy() {// 在销毁Filter时自动调用}
}// 在web.xml配置文件中如下配置:
<filter><filter-name>logFilter</filter-name><filter-class>com.xzg.cd.LogFilter</filter-class>
</filter>
<filter-mapping><filter-name>logFilter</filter-name><url-pattern>/*</url-pattern>
</filter-mapping>

职责链模式的实现包含处理器接口(IHandler)或抽象类(Handler),以及处理器链(HandlerChain)。对应到Servlet Filter,javax.servlet.Filter就是处理器接口FilterChain就是处理器链。接下来,我们重点来看FilterChain是如何实现的。

public final class ApplicationFilterChain implements FilterChain {private int pos = 0; //当前执行到了哪个filterprivate int n; //filter的个数private ApplicationFilterConfig[] filters;private Servlet servlet;@Overridepublic void doFilter(ServletRequest request, ServletResponse response) {if (pos < n) {ApplicationFilterConfig filterConfig = filters[pos++];Filter filter = filterConfig.getFilter();filter.doFilter(request, response, this);} else {// filter都处理完毕后,执行servletservlet.service(request, response);}}public void addFilter(ApplicationFilterConfig filterConfig) {for (ApplicationFilterConfig filter:filters)if (filter==filterConfig)return;if (n == filters.length) {//扩容ApplicationFilterConfig[] newFilters = new ApplicationFilterConfig[n + INCREMENT];System.arraycopy(filters, 0, newFilters, 0, n);filters = newFilters;}filters[n++] = filterConfig;}
}

Spring Interceptor

刚刚讲了Servlet Filter,现在我们来讲一个功能上跟它非常类似的东西,Spring Interceptor,翻译成中文就是拦截器。尽管英文单词和中文翻译都不同,但这两者基本上可以看作一个概念,都用来实现对HTTP请求进行拦截处理。

它们不同之处在于,Servlet Filter是Servlet规范的一部分,实现依赖于Web容器。Spring Interceptor是Spring MVC框架的一部分,由Spring MVC框架来提供实现。客户端发送的请求,会先经过Servlet Filter,然后再经过Spring Interceptor,最后到达具体的业务代码中。我画了一张图来阐述一个请求的处理流程,具体如下所示。

 ·

public class LogInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {System.out.println("拦截客户端发送来的请求.");return true; // 继续后续的处理}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {System.out.println("拦截发送给客户端的响应.");}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {System.out.println("这里总是被执行.");}
}//在Spring MVC配置文件中配置interceptors
<mvc:interceptors><mvc:interceptor><mvc:mapping path="/*"/><bean class="com.xzg.cd.LogInterceptor" /></mvc:interceptor>
</mvc:interceptors>

同样,我们还是来剖析一下,Spring Interceptor底层是如何实现的。

当然,它也是基于职责链模式实现的。其中,HandlerExecutionChain类是职责链模式中的处理器链。它的实现相较于Tomcat中的ApplicationFilterChain来说,逻辑更加清晰,不需要使用递归来实现,主要是因为它将请求和响应的拦截工作,拆分到了两个函数中实现。HandlerExecutionChain的源码如下所示,同样,我对代码也进行了一些简化,只保留了关键代码。

public class HandlerExecutionChain {private final Object handler;private HandlerInterceptor[] interceptors;public void addInterceptor(HandlerInterceptor interceptor) {initInterceptorList().add(interceptor);}boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {HandlerInterceptor[] interceptors = getInterceptors();if (!ObjectUtils.isEmpty(interceptors)) {for (int i = 0; i < interceptors.length; i++) {HandlerInterceptor interceptor = interceptors[i];if (!interceptor.preHandle(request, response, this.handler)) {triggerAfterCompletion(request, response, null);return false;}}}return true;}void applyPostHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView mv) throws Exception {HandlerInterceptor[] interceptors = getInterceptors();if (!ObjectUtils.isEmpty(interceptors)) {for (int i = interceptors.length - 1; i >= 0; i--) {HandlerInterceptor interceptor = interceptors[i];interceptor.postHandle(request, response, this.handler, mv);}}}void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, Exception ex)throws Exception {HandlerInterceptor[] interceptors = getInterceptors();if (!ObjectUtils.isEmpty(interceptors)) {for (int i = this.interceptorIndex; i >= 0; i--) {HandlerInterceptor interceptor = interceptors[i];try {interceptor.afterCompletion(request, response, this.handler, ex);} catch (Throwable ex2) {logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);}}}}
}

注意看代码里面的 applyPreHandle 和 applyPostHandle 的循环方式,一个是前项循环一个是后项循环,而且,在applyPreHandle执行为false时, triggerAfterCompletion 也是要执行的。

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

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

相关文章

对链表进行插入排序

给定单个链表的头 head &#xff0c;使用 插入排序 对链表进行排序&#xff0c;并返回 排序后链表的头 。 插入排序 算法的步骤: 插入排序是迭代的&#xff0c;每次只移动一个元素&#xff0c;直到所有元素可以形成一个有序的输出列表。 每次迭代中&#xff0c;插入排序只从输…

9、PHP超级全局变量$_REQUEST 、$_POST、$_GET

1、PHP $_REQUEST 、$_POST用于收集HTML表单提交的数据。 以下代码演示了一个输入字段&#xff08;input&#xff09;及提交按钮(submit)的表单(form)。 当用户通过点击 "Submit" 按钮提交表单数据时, 表单数据将发送至<form>标签中 action 属性中指定的脚本文…

Word 常用操作总结

文章目录 【 1. 公式篇 】1.1 编号右居中自动编号1.2 多行公式对齐编号右靠下编号右居中 1.3 公式引用1.4 更新编号1.5 Mathtype公式编辑器自动编号右居中多行公式换行以及等号对齐更新编号 【 1. 公式篇 】 简述&#xff1a;通过“#换行”的方式使编号右对齐&#xff0c;通过…

01. Docker基础环境构建

目录 1、前言 2、关于Docker 2.1、几个术语 2.2、Docker容器化的价值 3、搭建基础环境 3.1、安装VMware 3.2、安装Doker 3.3、启动 3.4、验证Docker环境 4、小结 1、前言 在这里我们将学习关于Docker的一些技能知识&#xff0c;那么首先我们应该怼Docker有一个基础的…

centos7.6下安装mysql

1.下载yum源&#xff1a; wget https://dev.mysql.com/get/mysql80-community-release-el7-5.noarch.rpm2.执行安装&#xff1a; rpm -ivh mysql80-community-release-el7-5.noarch.rpm3.开始安装 yum install -y mysql-server4.启动mysql服务 systemctl start mysqld5.查看…

(Linux)查看端口占用并关闭进程

1、查看端口占用 netstat -anp |grep 端口号 → 列出所有端口-a或--all&#xff1a;显示所有连线中的Socket&#xff1b;-n: 显示数字地址-p: 显示程序的PID和名称 netstat -tunlp |grep 3306 → 端口号netstat -tunlp |grep mysql → 进程名称netstat -tunlp |grep 29520 →…

Scala学习(三)

2.8 浮点类型&#xff08;Float、Double&#xff09; Scala的浮点类型可以表示一个小数&#xff0c;比如123.4f&#xff0c;7.8&#xff0c;0.12等等。 1&#xff09;浮点型分类 数据类型 描述 Float [4] 32 位, IEEE 754标准的单精度浮点数 Double [8] 64 位 IEEE 754标准的双…

Vue 和 React 前端框架的比较

Vue 和 React 前端框架的比较 本文研究了流行的前端框架 Vue 和 React 之间的区别。通过对它们的学习曲线、视图层处理方式、组件化开发、响应式数据处理方式和生态系统及社区支持进行比较分析&#xff0c;得出了它们在不同方面的优劣和特点。该研究对于开发者在选择合适的前端…

Thread-local storage is not supported for the current target

xcode编译时遇到上述报错&#xff0c;解决办法&#xff1a;调整最低系统版本配置

Sql构建

Sql构建 SQL 构建对象介绍 之前通过注解开发时&#xff0c;相关 SQL 语句都是直接拼写的&#xff0c;一些关键字写起来比较麻烦、而且容易出错 MyBatis 提供了 org.apache.ibatis.jdbc.SQL 功能类&#xff0c;专门用于构建 SQL 语句 sql拼接测试&#xff1a; public class …

请问如何用oracle触发器实现不允许新增/删除表/增加/减少/修改字段类型

请问如何用oracle触发器实现不允许新增/删除表/增加/减少/修改字段类型 给本帖投票 56211打赏收藏 分享 转发到动态举报 写回复 性能测试中发现oracle11g数据库每天22点,oralce进程CPU占用率突增>> 11 条回复 切换为时间正序 请发表友善的回复… 发表回复 microsof…

最新版本的OpenLens,有两个隐藏技能

最新版本的OpenLens v6.4.15&#xff0c;有两个隐藏技能 1、需要添加扩展插件alebcay/openlens-node-pod-menu&#xff0c;查看pod时才会出现进入命令行的按钮 2、测试环境查看pod、node时可能会出现监控数据未显示&#xff0c;点击集群的Setting&#xff0c;在Metrics里选…

软件测试用例的八大步骤你都知道吗?

目录 第一步、UI体验测试 第二步、功能完整性测试 第三步、业务流程测试 第四步、容错机制测试 第五步、常规性测试 第六步、性能测试 第七步、交互体验测试 第八步、兼容性测试 总结&#xff1a; 第一步、UI体验测试 1.风格、样式、颜色是否协调 2. 界面布局是否整齐、…

【C++】开源:cpp-tbox百宝箱组件库

&#x1f60f;★,:.☆(&#xffe3;▽&#xffe3;)/$:.★ &#x1f60f; 这篇文章主要介绍cpp-tbox百宝箱组件库。 无专精则不能成&#xff0c;无涉猎则不能通。。——梁启超 欢迎来到我的博客&#xff0c;一起学习&#xff0c;共同进步。 喜欢的朋友可以关注一下&#xff0c;…

Jenkins (一)

Jenkins (一) Docker Jenkins 部署 一. 安装 jenkins $ mkdir -p /home/tester/data/docker/jenkins $ vim jenkins:lts-jdk11.sh./jenkins:lts-jdk11.sh 内容 #! /bin/bash mkdir -p /home/tester/data/docker/jenkins/jenkins_homesudo chown -R 1000:1000 /home/tester/da…

后端查询出的数据库数字自动补零和不补零

select CAST(YTD_CHANGE*100 as decimal(18,1)), round(YTD_CHANGE*100,1) from RP where data_date 20211231补零 round(PYTD_CHANGE_PER*100,1)不补零 CAST(PYTD_CHANGE_PER*100 as decimal(18,1))

PageHelper分页失效,只能查出第一页

PageHelper分页失效&#xff0c;只能查出第一页 1. 现象2. 原因3. PageHelper工作原理 1. 现象 分页代码如下&#xff1a; int pageId Constants.ONE;boolean isHasNextPage;do {PageHelper.startPage(pageId, Constants.DEFAULT_PAGE_SIZE);List<String> projectIdLi…

【SCI一区】【电动车】基于ADMM双层凸优化的燃料电池混合动力汽车研究(Matlab代码实现)

目录 &#x1f4a5;1 概述 1.2 电动车动力学方程 1.3 电池模型 &#x1f4da;2 运行结果 &#x1f389;3 参考文献 &#x1f308;4 Matlab代码、数据、文章讲解 &#x1f4a5;1 概述 文献来源&#xff1a; 随着车辆互联性的出现&#xff0c;互联汽车 (CVs) 在增强道路安全、改…

数学建模-图论 最短路径

作图 %% 注意&#xff1a;以下代码需要较新版本的matlab才能运行&#xff08;最好是2016版本及以上哦&#xff09; % 如果运行出错请下载新版的matlab代码再运行%% Matlab作无向图 % &#xff08;1&#xff09;无权重&#xff08;每条边的权重默认为1&#xff09; % 函数graph(…

SQL进阶(2)——SQL语句类型 增删改查CRUD 事务初步 表关联关系 视图 +索引

目录 引出SQL语句类型1.DML数据操纵语言&#xff08;重点&#xff09;2.DQL数据查询语言&#xff08;重点&#xff09;3.DDL(Data Definition Language了解)4.DCL(Data Control Language了解)5.TCL 事务控制语言 运算符和其他函数1.运算符2.其它函数增删改查CRUD 视图索引事务1…