责任链模式在复杂数据处理场景中的实战

news2024/11/16 6:17:14

fdb9e51c591ce14fddbf967a20d0e6a2.gif

相信大家在日常的开发中都遇到过复杂数据处理和复杂数据校验的场景,本文从一线开发者的角度,分享了责任链模式在这种复杂数据处理场景下的实战案例,此外,作者在普通责任链模式的基础上进行了升级改造,可以适配更加复杂的应用场景;文章整体读下来,可以让读者对于设计模式-责任链模式有深刻的印象。

5082e313b254cb9682da12cfede9b26e.png

什么是责任链模式

  概念

责任链模式让多个对象都有机会处理同一个请求。它将请求的发送者和处理者之间进行解耦,同时将这些处理者对象连成一条链,并沿着这条链传递该请求,满足条件的处理者会执行相应的逻辑直至走完整个链条;

  应用场景

如果在一次请求中,需要多个处理者处理多种复杂的逻辑,且希望能够解耦多个处理者,实现高扩展性,可以考虑使用责任链模式。

8f687836f8ca439da90fe74a88a10102.png

Servlet中的过滤器(Filter)和过滤器链(FilterChain)

  概念

Filter和FilterChain是【责任链模式】的一种热门应用场景,过滤器Filter相信大家都很熟悉了,我们在Servlet中经常能发现它的身影。

【Filter】一般用于Servlet处理之前做一些前置的校验,每个Filter都有自己的职责和逻辑。调用filter时,需要传入当前filterChain的引用,来告诉filter当前执行的是哪一个filterChain

public interface Filter {
    //初始化方法
    public void init(FilterConfig filterConfig) throws ServletException;
    //处理逻辑,
    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException;
    //生命周期销毁
    public void destroy();
}

【FilterChain】是由多个Filter组成的链条,如果在链上的filter校验通过或处理完成,那么调用"chain.doFilter(request, response)"就可以让下一个filter继续执行逻辑直到filterChain结束

public interface FilterChain {
    public void doFilter(ServletRequest request, ServletResponse response)
            throws IOException, ServletException;
}

  在Servlet中的拦截过程

请求资源时,过滤器链中的过滤器依次对请求进行处理,并将请求传递给下一个过滤器,直到最后将请求传递给目标资源。发送响应信息时,则按照相反的顺序对响应进行处理,直到将响应返回给客户端;

3398d3993d37bdfcc828f4825df797b2.png

  启发

Servlet中的责任链模式给我们展示的是利用Filter和FilterChain来过滤和拦截请求;我们在复杂的业务场景中是否也能模仿来实现一个业务处理的责任链呢?

我们可以发现,Filter与Filter之间是互相解耦的,我们可以很轻量级的加入一个新的Filter到FilterChain当中;

此外,我们还可以实现多个FilterChain,其中装载不同的Filter来适配多种业务场景。

804f6b7e9beb7b144d799c6c47589eba.png

业务场景应用案例

  业务场景


在电商平台的很多业务场景下,涉及到对于数据的多重校验或多重过滤等操作。而随着业务的增长,校验逻辑或者数据处理逻辑会变得越来越复杂,这个时候责任链模式就能够体现出很好的优势了。

拿创建优惠券活动来举例;用户可以自由选择某些类目、某些商品或者某些门店来参与券活动;并且可以按需导入或者选择自己需要参与活动的数据;

【系统需要校验用户上传的数据是否满足业务条件、并将校验失败的数据返回给客户】

  复杂点

根据上述业务场景,在一次请求中,我们需要做多种校验逻辑:

  1. 数据鉴权校验、过滤用户无权限的数据

  2. 若用户选择商品,需对商品类型进行校验;(电商的商品模型有很多种,每一种商品模型都对应一种校验规则)

  3. 若用户选择门店,需对门店类型进行校验;(电商的门店类型也有很多,比如线上门店、旗舰店、线下门店等等,需要判断门店是否能够参与优惠活动)

  4. 对于不同投放渠道也有不同渠道的校验规则

  5. 我们需要完整的走完所有校验逻辑,而不能因为中途的一个逻辑校验不通过而阻断校验,因为我们需要返回给用户一个完整的数据校验结果;举个例子,如果用户上传的商品当中,既存在无权限的商品,又存在不符合商品类型的数据,那么我们需要走完所有校验逻辑,一并给用户返回所有的报错,而不是只返回无权限的商品;

  6. 其他校验规则……

  • 如果不使用设计模式

如果使用流水式代码,将会显得很臃肿,且有很多ifelse……嵌套,让人很难看懂和维护。如下:

//校验数据
if (用户选择商品) {
        if (商品模型一) {
        //校验逻辑1
      } else if (商品模型二) {
        //校验逻辑2
      } else if (商品模型三) {
        //校验逻辑3
      } else {
        //校验逻辑4
      }
} else if (用户选择门店) {
        if (门店模型一) {
            //校验逻辑1
          } else if (门店模型二) {
            //校验逻辑2
          }
  //校验逻辑……
} else if (用户选择类目) {
  //校验逻辑……
}
//校验渠道
if (渠道是A渠道){


} else if (渠道是B渠道){


}

上述伪代码仅仅只覆盖了几种简单的校验场景;试想就算开发完成之后,如果下次再有一个业务逻辑校验需要加入进来,则对代码需要进行很大的改动,需要重新梳理if else 的逻辑,缺乏代码的可读性和可拓展性。

  • 如果使用责任链模式

我们可以遵守单一职责原则,定义多个Filter对象,每个对象实现自己的业务校验逻辑;同时主干代码上仅需要初始化一个FilterChain,并调用doFilter方法执行链上每一个filter即可。

  使用责任链模式+改进 [业务代码均做了简化处理]


参照Servlet的filter与filterChain接口【见2.1】,自己实现了多种不同的过滤器,也在其基础上结合业务需求进行了相应的改进:

  • 定义AbstractOrderFilter抽象类

让Filter对象具备顺序属性,初始化FilterChain的时候,可以按顺序排列filter;同时定义accept方法,让filter自行控制是否处理请求。

@Data
public abstract class AbstractOrderFilter implements Filter, Comparable<AbstractOrderFilter> {
    protected Integer order;
    @Override
    public int compareTo(AbstractOrderFilter o) {
        return getOrder().compareTo(o.getOrder());
    }
    //根据Filter自己使用的业务场景,自行定义
    public boolean accept(FilterRequestDTO filterRequestDTO) {
        return true;
    }
    @Override
    public void doFilter(FilterRequestDTO filterRequestDTO, FilterChain filterChain) {
    }
}
  • 继承AbstractOrderFilter,遵守单一职责原则,实现多种Filter

举例:定义一个ItemPermissionFilter,专门做商品权限校验

@Slf4j
public class ItemPermissionFilter extends AbstractOrderFilter {
    //当前filter对应的业务逻辑manager(自行根据业务场景定义)
    ItemCheckManager itemCheckManager;
    //构造器私有
    private ItemPermissionFilter(Integer order, ItemCheckManager itemCheckManager) {
        super.order = order;
        this.itemCheckManager = itemCheckManager;
    }
    @Override
    public void doFilter(FilterRequestDTO filterRequestDTO, FilterChain filterChain) {
        if (accept(filterRequestDTO)) {
          //业务逻辑对应的manager进行校验处理(不做展开)
            itemCheckManager.checkItemPermission(filterRequestDTO, elementCheckResults);
        }
        //继续走责任链的下一个filter
        filterChain.doFilter(filterRequestDTO);
    }
    @Override
    public boolean accept(FilterRequestDTO filterRequestDTO) {
        //自行根据业务场景定义处理何种请求
        return true;
    }
    //对外暴露的create方法
    public static ItemPermissionFilter create(Integer order, ItemCheckManager itemCheckManager) {
        return new ItemPermissionFilter(order, itemCheckManager);
    }
}
  • 定义CouponFilterChain实现filterChain接口,定义对于内部filter的处理逻辑

注意其中几个属性:

  1. 【filters】是filterChain当中的filter集合;

  2. 【posLocal】是一个ThreadLocal变量,记录着当前filterChain执行到了第几个filter的index;

  3. 【checkResult】也是一个ThreadLocal变量,它记录着全局所有Filter的校验结果,每执行一个filter,filter就会把当前的执行结果记录在该变量中,之后会统一返回给用户,大大减少了参数的传递复杂度;

public class CouponFilterChain implements FilterChain {
    /**
     * 责任链中的所有的处理组件 非变量
     */
    private final List<? extends AbstractOrderFilter> filters;
    /**
     * 当前执行到的位置 这是个共享变量
     */
    private static ThreadLocal<Integer> posLocal = ThreadLocal.withInitial(() -> 0);


    /**
     * 责任链的校验结果--即需要给用户反馈的校验结果,共享变量,threadLocal,会作为全局参数
     */
    public static final ThreadLocal<List<CheckResult>> checkResult = new ThreadLocal<>();


    /**
     * 包含filter数量 非变量
     */
    private final int size;


    @Override
    public void doFilter(FilterRequestDTO filterRequestDTO) {
      //共享变量记住当前filterChain执行的filter的index,直至结束
        Integer pos = posLocal.get();
        if (pos < size) {
            pos++;
            posLocal.set(pos);
            Filter filter = this.filters.get(pos - 1);
            filter.doFilter(filterRequestDTO, this);
        }
    }


    //供外部业务代码调用的主要方法
   public BaseResult<CheckResult> process(FilterRequestDTO filterRequestDTO) {
        this.doFilter(filterRequestDTO);
        //将共享变量里面的结果取出来,返回给用户
        return BaseResult.makeSuccess(checkResult.get(););
    }


    @Override
  //注意避免ThreadLocal内存泄漏,要remove
    public void reset() {
        posLocal.remove();
        posLocal.set(0);
        checkResult.remove();
    }


    public CouponFilterChain(List<? extends AbstractOrderFilter> filters) {
        filters.sort(AbstractOrderFilter::compareTo);
        this.filters = filters;
        this.size = filters.size();
    }
}
  • 责任链初始化--根据业务场景自行拼接filter

在实现好了Filter已经FilterChain之后,我们需要对他们进行初始化,这个时候就可以根据你所需要的业务场景自行组装filter到filterChain当中; 有多种初始化方法,下面只简单介绍一种(将商品filter和门店filter初始化)

@Component
@Slf4j
public class FilterChainManager {
    @Resource
    StoreManager storeManager;
    @Resource
    ItemManager itemManager;


    private CouponFilterChain couponFilterChain;


    //初始化责任链
    @PostConstruct
    private void init() {
        //总链
        List<AbstractOrderFilter> filters = new ArrayList<>();
        //按需添加链上的filter……
        //商品校验filter
        filters.add(ItemFilter.create(100, ItemManager));
        //门店校验filter
        filters.add(StoreFilter.create(200, StoreManager));
        this.couponFilterChain = new CouponFilterChain(filters);
    }


   //供外部调用的方法
   public BaseResult<CheckResult> process(FilterRequestDTO filterRequestDTO) {
        BaseResult<CheckResult> result = null;
        try {
            //责任链模式,校验每一个参数的合法性并输出错误原因
            result = couponFilterChain.process(filterRequestDTO);
            return result
        } catch (Exception e) {
            return TMPResult.failOf("system error", e.getMessage());
        } finally {
            //这里非常重要 必须重置
            if (couponFilterChain != null) {
                couponFilterChain.reset();
            }
        }
    }
}

到此我们已经实现了责任链模式,可以画个图理解一下:

eb7b32039dd0ca35b133c7c5a462d697.png

  • 拓展--组合过滤器CompositeFilter + FilterChain

有时候我们的filter当中可能需要加上一些子处理,为了遵守单一职责原则,不适合将这些业务逻辑放在同一个filter中,于是考虑将多个filter合并组合成一个大的Filter; SpringMVC还有一种过滤器叫做组合过滤器CompositeFilter,过滤器里面嵌套过滤器,使得整个处理过程更加有层次;参照org.springframework.web.filter.CompositeFilter【有兴趣的同学可以参考下源码】,自己定义了一个CompositeFilter

/**
 * 合成的过滤器,改过滤器内部由多个过滤器组合而成
 */
public class CompositeFilter extends AbstractOrderFilter {
    /**
     * 所有的责任事件
     */
    private List<? extends AbstractOrderFilter> filters = new ArrayList();


    public CompositeFilter(Integer order, List<? extends AbstractOrderFilter> filters) {
        super.order = order;
        this.filters = filters;
    }


    @Override
    public void doFilter(FilterRequestDTO filterRequestDTO, FilterChain filterChain) {
        (new InnerFilterChain(filterChain, this.filters)).doFilter(filterRequestDTO);
    }


    /**
     * 内部链处理逻辑,优先将合成过滤器的内部过滤器进行处理,然后再传给下一个过滤器
     */
    private static class InnerFilterChain implements FilterChain {
        private final FilterChain originalChain;
        private final List<? extends AbstractOrderFilter> additionalFilters;
        private int currentPosition = 0;


        public InnerFilterChain(FilterChain chain, List<? extends AbstractOrderFilter> additionalFilters) {
            this.originalChain = chain;
            this.additionalFilters = additionalFilters;
        }


        @Override
        public void doFilter(FilterRequestDTO filterRequestDTO) {
            if (this.currentPosition >= this.additionalFilters.size()) {
                //如果已经执行完了内部过滤器,则跳到外部继续执行外部下一个节点的过滤器
                this.originalChain.doFilter(filterRequestDTO);
            } else {
                //继续执行内部过滤器
                this.currentPosition++;
                AbstractOrderFilter currentFilter = this.additionalFilters.get(this.currentPosition - 1);
                currentFilter.doFilter(filterRequestDTO, this);
            }


        }


        @Override
        public void reset() {


        }
    }


    public static CompositeFilter create(Integer order, List<? extends AbstractOrderFilter> filters) {
        filters.sort(AbstractOrderFilter::compareTo);
        return new CompositeFilter(order, filters);
    }
}

实现了组合过滤器之后,可以将其与FilterChain结合; 示意图如下图所示,在一条责任链上可以有普通的Filter和CompositeFilter,当执行到B时,按照B内部的顺序,从内部子filterB1执行开始一直到B4,直到执行完整个组合责任链然后再执行C,依次类推。可以看出CompositeFilter让整个责任链模块化,模块与模块之间能够各司其职,模块内部也能按照自定义的顺序执行。

d1ea9aa5428f4d0d3a1337976b345b2c.png

ba153cf6fb28dea07202fdb42fa4abaf.png

思考

本文实现的责任链仅供参考,大家可以结合自己的业务场景定义合适的FilterChain和Filter;与Servlet中的Filter不同的是,本文中定义的Filter只有一个入参FilterRequestDTO,而是将response作为了ThreadLocal共享变量,大家使用时也一定要注意内存泄漏的风险。

其实不需要FilterChain,我们只需要使用一个List<Filter>并保留Filter与Filter之间的引用关系即可,如一个Filter的next指针指向下一个Filter;定义FilterChain的原因我想也是开发者考虑到封装和更好的变化。

如果直接上手阅读源码,很容易被层层的方法带乱自己的阵脚;带着问题和目的去阅读和学习源码是一件事半功倍的事情,会让你在学习过程中有一条清晰明朗的线。

¤ 拓展阅读 ¤

3DXR技术 | 终端技术 | 音视频技术

服务端技术 | 技术质量 | 数据算法

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

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

相关文章

34_DAC原理及数模转换实验

目录 数模转换原理 DAC模块框图 事件选择控制数字模拟转换 DAC转换 DAC数据格式 选择DAC触发 DAC输出电压计算 硬件连接 DAC配置步骤 实验源码 数模转换原理 STM32的DAC模块(数字/模拟转换模块)是12位数字输入,电压输出型的DAC。DAC可以配置为8位或12位模式,也可以与…

linux安装nginx

1.nginx官网 http://nginx.org/en/download.html 下载安装包&#xff0c;如图所示下载nginx-1.23.2&#xff0c;并上传到指定目录&#xff1a;/usr/local/src/nginx 2.解压 tar -zxvf nginx-1.23.2.tar.gz3.安装nginx&#xff0c; cd /usr/local/src/nginx/nginx-1.23.2 该目录…

Titanic 泰坦尼克数据集 特诊工程 机器学习建模

以下内容为讲课时使用到的泰坦尼克数据集分析、建模过程&#xff0c;整体比较完整&#xff0c;分享出来&#xff0c;希望能帮助大家。部分内容由于版本问题&#xff0c;可能无法顺利运行。 Table of Contents 1 经典又有趣的Titanic问题1.1 目标1.2 解决方法1.3 项目目的2…

Vector-常用CAN工具 - CANoe入门到精通_03

NetWork Node 前面已经介绍了CANoe的基本情况、硬件环境搭建、CANoe软件环境配置&#xff0c;今天我们就来聊一下NetWork Node&#xff0c;在我们的测试工作中&#xff0c;大部分情况我们默认CANoe作为一个Client端&#xff0c;但是有些情况&#xff0c;我们需要实时监测被测件…

Akka 学习(四)Remote Actor

目录一 介绍1.1 Remote Actor1.2 适用场景1.3 踩坑点二 实战2.1 需求2.2 Java 版本2.2.1 效果图2.2.2 实体类2.2.3 服务端Actor 处理2.2.4 服务端配置文件2.2.5 客服端Actor处理2.2.6 客服端配置文件2.2.7 测试2.3 Scala 版本2.3.1 效果2.2.3 服务端Actor处理2.3.4 客户端Actor…

使用 Excel 数据透视表深入研究数据分析

问题 1(文章数据在底部) 为美国选民案例研究创建一个数据透视表,并用它来回答以下问题: A) 有多少个州的选民人口百分比低于 55%?哪些州? 答:有5个州的选民人数低于55%,分别是得克萨斯州、阿肯色州、俄克拉荷马州、夏威夷州和西弗吉尼亚州。 步骤:根据以下结果,创建…

基于jsp+java+ssm的社会保险信息管理系统-计算机毕业设计

项目介绍 课题研究的基本内容及预期目标或成果 用户注册与登录功能&#xff0c;在单位注册功能中有申请管理功能&#xff0c;填写具体信息。 系统管理员&#xff1a; 1&#xff09;个人密码修改&#xff1a;实现了管理员用户密码信息的修改。 2&#xff09;参保人员管理&a…

ORACE dbca创建报错Oracle system identifier(SID) “orcl“

最近项目需要通过备份恢复oracle实例&#xff0c;必须使用orcl&#xff0c;通过dbca创建实例是提示如下报错&#xff1a; 查看日志&#xff0c;$ORACLE_HOME/cfgtoollogs/dbca/dbcaui.log EVERE: [FATAL] A database instance with Oracle system identifier(SID) "orcl&…

零基础入门推荐系统 - 新闻推荐 - 实操2

内容导航: 零基础入门推荐系统 - 新闻推荐 - 实操2比赛数据分析:用户属性分析:训练集和测试集中分别有多少用户&#xff1f;用户城市分布有什么规律&#xff1f;平均每个用户会点击多少个文章&#xff1f;点击来源与文章点击次数是否存在关联&#xff1f;用户行为分析:零基础入…

【车载开发系列】UDS诊断---读取周期标识符($0x2A)

【车载开发系列】UDS诊断—读取周期标识符&#xff08;$0x2A&#xff09; UDS诊断---读取周期标识符&#xff08;$0x2A&#xff09;【车载开发系列】UDS诊断---读取周期标识符&#xff08;$0x2A&#xff09;一.概念定义二.报文格式1&#xff09;请求报文2&#xff09;初始响应3…

[附源码]计算机毕业设计课程在线测评系统Springboot程序

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

打破信息壁垒,提升业务水平,纷享销客CRM带给木链科技不一样的体验

步入数字化时代&#xff0c;企业业务模式和员工工作方式日新月异&#xff0c;传统协作方式很显然已经难以适应当前的需求&#xff0c;企业亟需一种新的面向信息化的协作方式&#xff0c;以提高工作效率&#xff0c;提升业务水平。 这样的挑战也发生在工业互联网安全企业&#…

Oracle一次获取多个序列值

Oracle一次获取多个序列值SQL 语句一次获取多个序列值获取序列中的多个值connect by level 生成多行数据JDBC 一次获取多个序列值MyBatis 一次获取多个序列值SQL 语句一次获取多个序列值 获取序列中的多个值 创建序列 CREATE SEQUENCE test_user_seq;获取一个序列值 SELECT…

Linux系统(Centos 7)配置DNS客户端

配置DNS客户端 DNS 客户端的配置非常简单&#xff0c;假设本地首选DNS服务器的IP地址为192.168.10.1&#xff0c;备用DNS 服务器的IP地址为192.168.10.2&#xff0c;则 DNS客户端的设置如下。 配置Windows 客户端 打开“Intermet 协议&#xff08;TCP/IP)”属性对话框&a…

8_3、Java基本语法之线程的生命周期与同步

一、线程的生命周期 JDK中用Thread.State类定义了线程的几种状态 要想实现多线程&#xff0c;必须在主线程中创建新的线程对象。Java语言使用Thread类 及其子类的对象来表示线程&#xff0c;在它的一个完整的生命周期中通常要经历如下的五种状态&#xff1a; 新建&#xff1a; …

基于ARIMA、SVM、随机森林销售的时间序列预测

如今DT&#xff08;数据技术&#xff09;时代&#xff0c;数据变得越来越重要&#xff0c;其核心应用“预测”也成为互联网行业以及产业变革的重要力量。最近我们被客户要求撰写关于销售时间序列预测的研究报告&#xff0c;包括一些图形和统计输出。对于零售行业来说&#xff0…

Elsevier(爱思唯尔)LaTex 模板详细说明

Elsevier 模板的使用 官方网站提供的 Latex Instructions&#xff0c;Elsevier 模板下载地址&#xff1a;elsarticle-template.zip [ 如果不了解文档类的作用&#xff0c;可以参考&#xff1a;documentclass ] Elsevier 提供了 3 种自定义的文档类&#xff1a; elsarticle…

大厂软件测试流程完整版

目 1.概述 1.1目的 有效的保证软件质量&#xff1b; 有效的制定不同测试类型&#xff08;软件系统测试、音频主观性测试、Field Trial、专项测试、自动化测试、性能测试、用户体验测试&#xff09;的软件测试计划&#xff1b; 按照计划进行测试&#xff0c;发现软件中存在…

Session | web应用的session机制、session的实现原理

目录 一&#xff1a;web应用的session机制 二&#xff1a;session的实现原理 一&#xff1a;web应用的session机制 &#xff08;1&#xff09;什么是会话&#xff1f; ①会话对应的英语单词&#xff1a;session ②用户打开浏览器&#xff0c;进行一系列操作&#xff0c;然后…