【责任链】一条龙服务处理流程

责任链

责任链模式,简而言之,就是将多个操作组装成一条链路进行处理。请求在链路上传递,链路上的每一个节点就是一个处理器,每个处理器都可以对请求进行处理,或者传递给链路上的下一个处理器处理。

应用场景

  • 操作需要经过一系列的校验,通过校验后才执行某些操作。
  • 工作流。企业中通常会制定很多工作流程,一级一级的去处理任务。

案例一

以创建商品为例,假设商品创建逻辑分为以下三步完成:①创建商品、②校验商品参数、③保存商品。

在这里插入图片描述
当校验场景越来越多,这中间的校验逻辑越来越拥挤,其他模块又不能服用这种校验逻辑,这时候适合用责任链优化。

创建商品的每个校验步骤都可以作为一个单独的处理器,抽离为一个单独的类,便于复用。这些处理器形成一条链式调用,请求在处理器链上传递,如果校验条件不通过,则处理器不再向下传递请求,直接返回错误信息;若所有的处理器都通过检验,则执行保存商品步骤。

在这里插入图片描述

责任链模式实现创建商品校验

UML类图

AbstractCheckHandler表示处理器抽象类,负责抽象处理器行为。其有3个子类,分别是:

  • NullValueCheckHandler:空值校验处理器
  • PriceCheckHandler:价格校验处理
  • StockCheckHandler:库存校验处理器

1.AbstractCheckHandler 抽象类中, handle()定义了处理器的抽象方法,其子类需要重写handle()方法以实现特殊的处理器校验逻辑;

2.protected ProductCheckHandlerConfig config 是处理器的动态配置类,使用protected声明,每个子类处理器都持有该对象。该对象用于声明当前处理器、以及当前处理器的下一个处理器nextHandler,另外也可以配置一些特殊属性,比如说接口降级配置、超时时间配置等。

3.AbstractCheckHandler nextHandler 是当前处理器持有的下一个处理器的引用,当前处理器执行完毕时,便调用nextHandler执行下一处理器的handle()校验方法;

4.protected Result next() 是抽象类中定义的,执行下一个处理器的方法,使用protected声明,每个子类处理器都持有该对象。当子类处理器执行完毕(通过)时,调用父类的方法执行下一个处理器nextHandler。

5.HandlerClient 是执行处理器链路的客户端,HandlerClient.executeChain()方法负责发起整个链路调用,并接收处理器链路的返回值。

)1_ProductVO 商品参数对象:保存商品的入参

ProductVO是创建商品的参数对象,包含商品的基础信息。并且其作为责任链模式中多个处理器的入参,多个处理器都以ProductVO为入参进行特定的逻辑处理。实际业务中,商品对象特别复杂。咱们化繁为简,简化商品参数如下:

/**
 * 商品对象
 */
@Data
@Builder
public class ProductVO {
    /**
     * 商品SKU,唯一
     */
    private Long skuId;
    /**
     * 商品名称
     */
    private String skuName;
    /**
     * 商品图片路径
     */
    private String Path;
    /**
     * 价格
     */
    private BigDecimal price;
    /**
     * 库存
     */
    private Integer stock;
}

2_AbstractCheckHandler 抽象类处理器:抽象行为,子类共有属性、方法

/**
 * 抽象类处理器
 */
@Component
public abstract class AbstractCheckHandler {

    /**
     * 当前处理器持有下一个处理器的引用
     */
    @Getter
    @Setter
    protected AbstractCheckHandler nextHandler;


    /**
     * 处理器配置
     */
    @Setter
    @Getter
    protected ProductCheckHandlerConfig config;

    /**
     * 处理器执行方法
     * @param param
     * @return
     */
    public abstract Result handle(ProductVO param);

    /**
     * 链路传递
     * @param param
     * @return
     */
    protected Result next(ProductVO param) {
        //下一个链路没有处理器了,直接返回
        if (Objects.isNull(nextHandler)) {
            return Result.success();
        }

        //执行下一个处理器
        return nextHandler.handle(param);
    }

}

在AbstractCheckHandler抽象类处理器中,使用protected声明子类可见的属性和方法。使用 @Component注解,声明其为Spring的Bean对象,这样做的好处是可以利用Spring轻松管理所有的子类,下面会看到如何使用。抽象类的属性和方法说明如下:

  • public abstract Result handle():表示抽象的校验方法,每个处理器都应该继承AbstractCheckHandler抽象类处理器,并重写其handle方法,各个处理器从而实现特殊的校验逻辑,实际上就是多态的思想。
  • protected ProductCheckHandlerConfig config:表示每个处理器的动态配置类,可以通过“配置中心”动态修改该配置,实现处理器的“动态编排”和“顺序控制”。配置类中可以配置处理器的名称、下一个处理器、以及处理器是否降级等属性。
  • protected AbstractCheckHandler nextHandler:表示当前处理器持有下一个处理器的引用,如果当前处理器handle()校验方法执行完毕,则执行下一个处理器nextHandler的handle()校验方法执行校验逻辑。
  • protected Result next(ProductVO param):此方法用于处理器链路传递,子类处理器执行完毕后,调用父类的next()方法执行在config 配置的链路上的下一个处理器,如果所有处理器都执行完毕了,就返回结果了。

3_ProductCheckHandlerConfig配置类

/**
 * 处理器配置类
 */
@AllArgsConstructor
@Data
public class ProductCheckHandlerConfig {
    /**
     * 处理器Bean名称
     */
    private String handler;
    /**
     * 下一个处理器
     */
    private ProductCheckHandlerConfig next;
    /**
     * 是否降级
     */
    private Boolean down = Boolean.FALSE;
}

4.子类处理器:处理特有的校验逻辑

AbstractCheckHandler抽象类处理器有3个子类分别是:

  • NullValueCheckHandler:空值校验处理器
  • PriceCheckHandler:价格校验处理
  • StockCheckHandler:库存校验处理器

super.getConfig().getDown()是获取AbstractCheckHandler处理器对象中保存的配置信息,如果处理器配置了降级,则跳过该处理器,调用super.next()执行下一个处理器逻辑。

/**
 * 空值校验处理器
 */
@Component
public class NullValueCheckHandler extends AbstractCheckHandler{

    @Override
    public Result handle(ProductVO param) {
        System.out.println("空值校验 Handler 开始...");
        
        //降级:如果配置了降级,则跳过此处理器,执行下一个处理器
        if (super.getConfig().getDown()) {
            System.out.println("空值校验 Handler 已降级,跳过空值校验 Handler...");
            return super.next(param);
        }
        
        //参数必填校验
        if (Objects.isNull(param)) {
            return Result.failure(ErrorCode.PARAM_NULL_ERROR);
        }
        //SkuId商品主键参数必填校验
        if (Objects.isNull(param.getSkuId())) {
            return Result.failure(ErrorCode.PARAM_SKU_NULL_ERROR);
        }
        //Price价格参数必填校验
        if (Objects.isNull(param.getPrice())) {
            return Result.failure(ErrorCode.PARAM_PRICE_NULL_ERROR);
        }
        //Stock库存参数必填校验
        if (Objects.isNull(param.getStock())) {
            return Result.failure(ErrorCode.PARAM_STOCK_NULL_ERROR);
        }
        
        System.out.println("空值校验 Handler 通过...");
        
        //执行下一个处理器
        return super.next(param);
    }
}
/**
 * 价格校验处理器
 */
@Component
public class PriceCheckHandler extends AbstractCheckHandler{
    @Override
    public Result handle(ProductVO param) {
        System.out.println("价格校验 Handler 开始...");

        //非法价格校验
        boolean illegalPrice =  param.getPrice().compareTo(BigDecimal.ZERO) <= 0;
        if (illegalPrice) {
            return Result.failure(ErrorCode.PARAM_PRICE_ILLEGAL_ERROR);
        }
        //其他校验逻辑...

        System.out.println("价格校验 Handler 通过...");

        //执行下一个处理器
        return super.next(param);
    }
}
/**
 * 库存校验处理器
 */
@Component
public class StockCheckHandler extends AbstractCheckHandler{
    @Override
    public Result handle(ProductVO param) {
        System.out.println("库存校验 Handler 开始...");

        //非法库存校验
        boolean illegalStock = param.getStock() < 0;
        if (illegalStock) {
            return Result.failure(ErrorCode.PARAM_STOCK_ILLEGAL_ERROR);
        }
        //其他校验逻辑..

        System.out.println("库存校验 Handler 通过...");

        //执行下一个处理器
        return super.next(param);
    }
}

5_HandlerClient客户端:执行处理器链路

HandlerClient客户端类负责发起整个处理器链路的执行,通过executeChain()方法。如果处理器链路返回错误信息,即校验未通过,则整个链路截断(停止),返回相应的错误信息。

public class HandlerClient {

  public static Result executeChain(AbstractCheckHandler handler, ProductVO param) {
      //执行处理器
      Result handlerResult = handler.handle(param);
      if (!handlerResult.isSuccess()) {
          System.out.println("HandlerClient 责任链执行失败返回:" + handlerResult.toString());
          return handlerResult;
      }
      return Result.success();
  }
}

以上,责任链模式相关的类已经创建好了。接下来就可以创建商品了。

6_创建商品:抽象步骤,化繁为简

createProduct()创建商品方法抽象为2个步骤:①参数校验、②创建商品。参数校验使用责任链模式进行校验,包含:空值校验、价格校验、库存校验等等,只有链上的所有处理器均校验通过,才调用saveProduct()创建商品方法;否则返回校验错误信息。

在createProduct()创建商品方法中,通过责任链模式,我们将校验逻辑进行解耦。createProduct()创建商品方法中不需要关注都要经过哪些校验处理器,以及校验处理器的细节。

/**
 * 创建商品
 * @return
 */
@Test
public Result createProduct(ProductVO param) {

    //参数校验,使用责任链模式
    Result paramCheckResult = this.paramCheck(param);
    if (!paramCheckResult.isSuccess()) {
        return paramCheckResult;
    }

    //创建商品
    return this.saveProduct(param);
}

7_参数校验:责任链模式

参数校验paramCheck()方法使用责任链模式进行参数校验,方法内没有声明具体都有哪些校验,具体有哪些参数校验逻辑是通过多个处理器链传递的。如下:

/**
 * 参数校验:责任链模式
 * @param param
 * @return
 */
private Result paramCheck(ProductVO param) {

    //获取处理器配置:通常配置使用统一配置中心存储,支持动态变更
    ProductCheckHandlerConfig handlerConfig = this.getHandlerConfigFile();

    //获取处理器
    AbstractCheckHandler handler = this.getHandler(handlerConfig);

    //责任链:执行处理器链路
    Result executeChainResult = HandlerClient.executeChain(handler, param);
    if (!executeChainResult.isSuccess()) {
        System.out.println("创建商品 失败...");
        return executeChainResult;
    }

    //处理器链路全部成功
    return Result.success();
}

paramCheck()方法步骤说明如下:

(7.1)获取处理器配置
通过getHandlerConfigFile()方法获取处理器配置类对象,配置类保存了链上各个处理器的上下级节点配置,支持流程编排、动态扩展。通常配置是通过Ducc(京东自研的配置中心)、Nacos(阿里开源的配置中心)等配置中心存储的,支持动态变更、实时生效。

基于此,我们便可以实现校验处理器的编排、以及动态扩展了。我这里没有使用配置中心存储处理器链路的配置,而是使用JSON串的形式去模拟配置,大家感兴趣的可以自行实现。

/**
 * 获取处理器配置:通常配置使用统一配置中心存储,支持动态变更
 * @return
 */
private ProductCheckHandlerConfig getHandlerConfigFile() {
    //配置中心存储的配置
    String configJson = "{\"handler\":\"nullValueCheckHandler\",\"down\":true,\"next\":{\"handler\":\"priceCheckHandler\",\"next\":{\"handler\":\"stockCheckHandler\",\"next\":null}}}";
    //转成Config对象
    ProductCheckHandlerConfig handlerConfig = JSON.parseObject(configJson, ProductCheckHandlerConfig.class);
    return handlerConfig;
}

ConfigJson存储的处理器链路配置JSON串,在代码中可能不便于观看,我们可以使用json.cn等格式化看一下,如下,配置的整个调用链路规则特别清晰。
在这里插入图片描述
getHandlerConfigFile()类获到配置类的结构如下,可以看到,就是把在配置中心储存的配置规则,转换成配置类ProductCheckHandlerConfig对象,用于程序处理。

注意,此时配置类中存储的仅仅是处理器Spring Bean的name而已,并非实际处理器对象。

在这里插入图片描述
(7.2)步骤2:根据配置获取处理器。
上面步骤1通过getHandlerConfigFile()方法获取到处理器链路配置规则后,再调用getHandler()获取处理器。

getHandler()参数是如上ConfigJson配置的规则,即步骤1转换成的ProductCheckHandlerConfig对象;根据ProductCheckHandlerConfig配置规则转换成处理器链路对象。代码如下

 * 使用Spring注入:所有继承了AbstractCheckHandler抽象类的Spring Bean都会注入进来。MapKey对应Bean的name,Value是name对应相应的Bean
 */
@Resource
private Map<String, AbstractCheckHandler> handlerMap;

/**
 * 获取处理器
 * @param config
 * @return
 */
private AbstractCheckHandler getHandler (ProductCheckHandlerConfig config) {
    //配置检查:没有配置处理器链路,则不执行校验逻辑
    if (Objects.isNull(config)) {
        return null;
    }
    //配置错误
    String handler = config.getHandler();
    if (StringUtils.isBlank(handler)) {
        return null;
    }
    //配置了不存在的处理器
    AbstractCheckHandler abstractCheckHandler = handlerMap.get(config.getHandler());
    if (Objects.isNull(abstractCheckHandler)) {
        return null;
    }
    
    //处理器设置配置Config
    abstractCheckHandler.setConfig(config);
    
    //递归设置链路处理器
    abstractCheckHandler.setNextHandler(this.getHandler(config.getNext()));

    return abstractCheckHandler;
}

(7.3)递归设置处理器链路。

//递归设置链路处理器 abstractCheckHandler.setNextHandler(this.getHandler(config.getNext()));

在这里插入图片描述

通过递归调用getHandler()获取处理器方法,就将整个处理器链路对象串联起来了。如下:
在这里插入图片描述
友情提示:递归虽香,但使用递归一定要注意截断递归的条件处理,否则可能造成死循环哦!

实际上,getHandler()获取处理器对象的代码就是把在配置中心配置的规则ConfigJson,转换成配置类ProductCheckHandlerConfig对象,再根据配置类对象,转换成实际的处理器对象,这个处理器对象持有整个链路的调用顺序。

(7.4) 客户端执行调用链路。

public class HandlerClient {

  public static Result executeChain(AbstractCheckHandler handler, ProductVO param) {
      //执行处理器
      Result handlerResult = handler.handle(param);
      if (!handlerResult.isSuccess()) {
          System.out.println("HandlerClient 责任链执行失败返回:" + handlerResult.toString());
          return handlerResult;
      }
      return Result.success();
  }
}

在这里插入图片描述

测试

场景1:创建商品参数中有空值(如下skuId参数为null),链路被空值处理器截断,返回错误信息

//创建商品参数
ProductVO param = ProductVO.builder()
      .skuId(null).skuName("华为手机").Path("http://...")
      .price(new BigDecimal(1))
      .stock(1)
      .build();

在这里插入图片描述

案例二

同事小贾最近刚出差回来,她迫不及待的就提交了费用报销的流程。根据金额不同,分为以下几种审核流程。报销金额低于1000元,三级部门管理者审批即可,1000到5000元除了三级部门管理者审批,还需要二级部门管理者审批,而5000到10000元还需要一级部门管理者审批。即有以下几种情况:

  1. 小贾需报销500元,三级部门管理者审批即可。
  2. 小贾需报销2500元,三级部门管理者审批通过后,还需要二级部门管理者审批,二级部门管理者审批通过后,才完成报销审批流程。
  3. 小贾需报销7500元,三级管理者审批通过后,并且二级管理者审批通过后,流程流转到一级部门管理者进行审批,一级管理者审批通过后,即完成了报销流程。

在这里插入图片描述
AbstractFlowHandler作为处理器抽象类,抽象了approve()审核方法,一级、二级、三级部门管理者处理器继承了抽象类,并重写其approve()审核方法,从而实现特有的审核逻辑。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值