Su的技术博客

  • 首页
  • 原创
  • 视频
  • Java
  • MySQL
  • DDD
  • 事故复盘
  • 架构方案
  • AI
  • Other
  • 工具
    • AI工具集
    • 工具清单
    • JSON在线格式化
    • JSON在线比较
    • SQL在线格式化
  • 打赏
  • 关于
路很长,又很短
  1. 首页
  2. Java
  3. 正文
                           

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

2023-08-21 4115点热度 0人点赞 0条评论

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

01 什么是责任链模式

▐  概念

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

▐  应用场景

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

 

02 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中的拦截过程

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

 

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

 

▐  启发

Servlet中的责任链模式给我们展示的是利用Filter和FilterChain来过滤和拦截请求;我们在复杂的业务场景中是否也能模仿来实现一个业务处理的责任链呢?
 
我们可以发现,Filter与Filter之间是互相解耦的,我们可以很轻量级的加入一个新的Filter到FilterChain当中;
 
此外,我们还可以实现多个FilterChain,其中装载不同的Filter来适配多种业务场景。
 

03 业务场景应用案例

 

▐  业务场景

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

 

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

 

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

 

▐  复杂点

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

  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,专门做商品权限校验

 

@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) {
    }
}

 

  • 定义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初始化)

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();
    }
}

 

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

 

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

  • 拓展--组合过滤器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让整个责任链模块化,模块与模块之间能够各司其职,模块内部也能按照自定义的顺序执行。
 
责任链模式在复杂数据处理场景中的实战

 

04 思考

本文实现的责任链仅供参考,大家可以结合自己的业务场景定义合适的FilterChain和Filter;与Servlet中的Filter不同的是,本文中定义的Filter只有一个入参FilterRequestDTO,而是将response作为了ThreadLocal共享变量,大家使用时也一定要注意内存泄漏的风险。
 
其实不需要FilterChain,我们只需要使用一个List<Filter>并保留Filter与Filter之间的引用关系即可,如一个Filter的next指针指向下一个Filter;定义FilterChain的原因我想也是开发者考虑到封装和更好的变化。
如果直接上手阅读源码,很容易被层层的方法带乱自己的阵脚;带着问题和目的去阅读和学习源码是一件事半功倍的事情,会让你在学习过程中有一条清晰明朗的线。

本文仅供学习!所有权归属原作者。侵删!文章来源: 大淘宝技术 -黄维杰(水沐) :http://mp.weixin.qq.com/s/eIxFizo6dokG5kcTslZhmQ

更多文章:

  1. 浅析设计模式5 -- 责任链模式
  2. 构建一个布隆过滤器 —— Building a Bloom filter
  3. 浅析设计模式3 —— 装饰者模式
  4. 殷浩详解DDD 第四讲:领域层设计规范
  5. 殷浩详解DDD 第三讲 - Repository模式
  6. 全链路压测之影子库及ShardingSphere实现影子库源码剖析
  7. 设计模式在外卖营销业务中的实践
  8. 手把手教你实战TDD
  9. DDD系列第五讲:聊聊如何避免写流水账代码
  10. 解构领域驱动设计(三):领域驱动设计
标签: 淘宝 Java 设计模式 责任链模式
最后更新:2023-08-21

coder

分享干货文章,学习先进经验。

打赏 点赞
< 上一篇
下一篇 >

文章评论

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
取消回复

广告
文章目录
  • 01 什么是责任链模式
  • 02 Servlet中的过滤器(Filter)和过滤器链(FilterChain)
  • 03 业务场景应用案例
  • 04 思考
最新 热点 推荐
最新 热点 推荐
微服务架构:必懂的6大性能维度 Anthropic Code with Claude 开发者大会:开启 AI Agent 新时代 视频笔记-微服务架构P4:必懂5种设计模式 视频笔记:微服务架构P4 设计模式:每服务数据库、API 网关和事件驱动架构 干货 | 论Elasticsearch数据建模的重要性 马蜂窝消息总线——面向业务的消息服务设计 基于 MySQL Binlog 实现可配置的异构数据同步 视频笔记:Google发布Agent2Agent协议
视频笔记:微服务架构P4 设计模式:每服务数据库、API 网关和事件驱动架构干货 | 论Elasticsearch数据建模的重要性视频笔记-微服务架构P4:必懂5种设计模式Anthropic Code with Claude 开发者大会:开启 AI Agent 新时代微服务架构:必懂的6大性能维度
笔记 | 面试官问我:TCP与UDP的区别 浅谈SQL优化小技巧 如何画好一张架构图/业务图/流程图,掌握这4个关键点 京东平台研发朱志国:领域驱动设计(DDD)理论启示 系统设计 | UUID 和 自增 ID 怎么选? Redis的BigKey(大key)、HotKey(热key)又引发了线上事故 你所说的“事件驱动”是什么? What do you mean by “Event-Driven”? 分布式事务场景、概念和方案整理(含概念图)

CRUD (1) Event Sourcing (1) graphql (1) id (1) NoSQL (1) quarkus (1) rest (1) RocketMQ (2) Spring Boot (1) zk (1) zookeeper (1) 上下文 (1) 事务消息 (1) 二级缓存 (1) 值对象 (1) 关系数据库 (1) 分布式缓存 (1) 原子性 (1) 唯一ID (1) 商品 (1) 多对多 (1) 子域 (1) 字符集 (1) 客户端心跳 (1) 幂等 (2) 干货 (1) 并发 (1) 应用场景 (1) 应用架构图 (1) 康威定律 (2) 异步复制 (1) 微服务架构 (3) 总体方案 (1) 技术方案 (2) 技术架构 (2) 技术架构图 (1) 技能 (1) 持续集成 (1) 支撑域 (1) 故障恢复 (1) 数据架构图 (1) 方案选型 (1) 日记 (1) 服务发现 (1) 服务治理 (1) 服务注册 (2) 机房 (1) 核心域 (1) 泄漏 (1) 洋葱架构 (1) 消息队列 (5) 源码剖析 (1) 灰度发布 (1) 熔断 (1) 生态 (1) 画图工具 (1) 研发团队 (1) 线程 (2) 组织架构 (1) 缓存架构 (1) 编码 (1) 视频 (20) 读写分离 (1) 贵州 (1) 软件设计 (1) 迁移 (1) 通用域 (1) 集群化 (1) 雪花算法 (1) 顺序消息 (1)

推荐链接🔗
  • AI工具集
  • 工具箱🛠️

COPYRIGHT © 2014-2025 verysu.com . ALL RIGHTS RESERVED.

Theme Kratos Made By Seaton Jiang

粤ICP备15033072号-2