专栏首页屈定‘s Blog设计模式--责任链模式的思考

设计模式--责任链模式的思考


标准责任链模式

责任链模式: 客户端发出的请求,客户端本身并不知道被哪一个对象处理,而直接扔给对象链,该请求在对象链中共享,由对象本身决定是否处理. 当请求被处理后该链终止.本质目的是把客户端请求与接收者解耦,但是解耦的太彻底了,只能让接收者一个个来看看是不是自己该处理的请求. 标准的责任链模式一个请求只被一个对象处理,一旦处理成功后则链终止,请求不再被继续传递.标准的责任链模式并不是很通用,这种一对一模式大多场景可以用策略模式来代替,只有在客户端并不清楚具体的执行者是哪个对象的时候,责任链才比较适合. 举个例子:你想在天朝办理一个证,但是你不知道去哪比较好,因此你的选择就是一条链路,先去A局,A局让你去B局,B局让你去C局等等,直到解决你的问题,当然也存在白跑一趟的结果.这也是标准责任链的缺点,产生了太多没必要的调用.标准的责任链实际上应用场景并不是很多,而常使用的是升级版的功能链.

功能链

功能链是责任链的演变,结构上并没有实质的变化,只是每一个节点都可以处理请求,处理完转向下一个,也就是每一个请求都经历全部的链.这种应用场景就比较多了,比如我要办一件事,先去A再去B最后去C,这个例子还有点说明ABC三者的关系,取决于构造链时的顺序,另外每一步没处理好可以自由的选择退出链. 文字说的不是很理解,下面举几个实际中的代码实例.

Java中Filter链

对于Filter,其是由FilterChain来进行链的组合调用,请求的request与返回response实际上是共享的上下文信息,每一个处理的Filter都可以查看与修改.

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

在Tomcat中实现类为org.apache.catalina.core.ApplicationFilterChain,其结构如下图:

其中数组filters就是所谓的filter链,利用pos(当前执行到的位置)与n(filter链长度)来进行链的调用. 那么怎么让链节点选择继续执行还是停止执行呢?答案的FilterdoFilter方法,该方法把责任链作为参数FilterChain一直传递下去,继续就调用chain的doFilter方法,不继续则不调用.

void doFilter(ServletRequest request, ServletResponse response,
           FilterChain chain) throws IOException, ServletException;

Spring Security中拦截链

Spring Security中的链实际上是由SecurityFilterChain接口所定义,其很简单的就是暴露出一个list的链.其中matches判断请求是否是这条链来处理.

public interface SecurityFilterChain {

    boolean matches(HttpServletRequest request);

    List<Filter> getFilters();
}

在其入口处类org.springframework.security.web.FilterChainProxy中包含着多条链.

private List<SecurityFilterChain> filterChains;

每一个链处理的是一类请求.Spring Security使用简单的for循环判断定位到具体执行的链.

private List<Filter> getFilters(HttpServletRequest request)  {
      for (SecurityFilterChain chain : filterChains) {
          if (chain.matches(request)) {
              return chain.getFilters();
          }
      }
      return null;
  }

那么这个结构因为这个设计模式就很清晰了(这也是熟悉了设计模式之后的优势,看源码可以有一种全局把控感)

还有一个问题,链是如何自由执行的? 这一点与Java Filter一模一样,Spring Security实现了一个org.springframework.security.web.FilterChainProxy.VirtualFilterChain类,该类同样实现了FilterChain接口,里面的调用逻辑也与tomcat方式一致.具体就不讨论了.

另外Spring Security也提供了一种数据共享的方式,利用ThreadLocal保证线程安全,达到共享数据的目的.另外这里的SecurityContextHolderStrategy是策略模式的一种应用,值得一看.

final class ThreadLocalSecurityContextHolderStrategy implements
		SecurityContextHolderStrategy {
    		
	private static final ThreadLocal<SecurityContext> contextHolder = new ThreadLocal<SecurityContext>();

	public void clearContext() {
		contextHolder.remove();
	}

	public SecurityContext getContext() {
		SecurityContext ctx = contextHolder.get();

		if (ctx == null) {
			ctx = createEmptyContext();
			contextHolder.set(ctx);
		}

		return ctx;
	}
	public void setContext(SecurityContext context) {
		Assert.notNull(context, "Only non-null SecurityContext instances are permitted");
		contextHolder.set(context);
	}

	public SecurityContext createEmptyContext() {
		return new SecurityContextImpl();
	}
}

Mybatis中插件链

Mybatis中插件使用的是类似责任链的一种模式,当然也可以称之为责任链模式,毕竟思想都是类似的.其中插件是通过Interceptor接口实现的,其中plugin方法就是为目标对象套上该链的一个节点.

public interface Interceptor {

  Object intercept(Invocation invocation) throws Throwable;

  Object plugin(Object target);

  void setProperties(Properties properties);
}

那么如何构造这个链?在InterceptorChain中有如下方法,InterceptorChain是在构造配置时组装好的,燃后对目标使用pluginAll方法,构造完整链.

public class InterceptorChain {
  private final List<Interceptor> interceptors = new ArrayList<Interceptor>();
  public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
      target = interceptor.plugin(target);
    }
    return target;
  }
...
}

其中plugin官方推荐Plugin.wrap(target, this)方法,该方法本质上是用代理模式嵌套住目标类

public static Object wrap(Object target, Interceptor interceptor) {
  Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
  Class<?> type = target.getClass();
  Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
  if (interfaces.length > 0) {
    return Proxy.newProxyInstance(
        type.getClassLoader(),
        interfaces,
        new Plugin(target, interceptor, signatureMap));
  }
  return target;
}

那么这种构造出的链大概如下面这种嵌套结构,这种链可以说是彻底的功能链,其一旦组装好就无法变化了.当然这种也适合Mybatis这种从配置中就定死了执行链.

业务开发中可以常用到的链

在业务开发中常常能遇到这类需求,比如退款操作,退款后可以恢复商品库存,恢复活动库存,退掉用户的优惠券,退掉用户的活动资格等等,该一系列的操作就是一条线性链,那么就可以利用责任链的思想来完成在这样的需求. 先提取出一个公共接口,链节点实现该接口,完成具体的退款操作

public interface RegainAfterRefundOrder {
  /**
   * 退回操作
   * @param bo 该订单,可能是子订单,也可能是主订单,自行判断
   * @param operator 操作人
   * @return true成功
   */
  boolean regain(BizOrderDO bo, Long operator);
}

接下来是链的统一管理,也就是需要Chain这个类来管理,可以按照下面的实现,其调用链只是简单的在applyAllPlugin循环调用,该过程可以按照Spring Security等方式实现更加灵活的调用. 可以根据需求设计为一旦创建就不可改变的类,包括类中的interceptors,这样使得代码更加健壮.

@Component
public class RefundOrderAndRegainChain {

  private final List<RegainAfterRefundOrder> interceptors = new ArrayList<>();

  public void applyAllPlugin(BizOrderDO bo, Long operator) {
    for (RegainAfterRefundOrder interceptor : interceptors) {
      interceptor.regain(bo, operator);
    }
  }

  public void addInterceptor(RegainAfterRefundOrder interceptor) {
    interceptors.add(interceptor);
  }

  public List<RegainAfterRefundOrder> getInterceptors() {
    return Collections.unmodifiableList(interceptors);
  }
}

最后是借助IOC实现链的组装,假设有RegainCoupon,RegainInventoryCount,RegainInvitationCodeWithDraw等RegainAfterRefundOrder的实现类,依次在Spring的Configuration类中实现注入,并构造出需要的RefundOrderAndRegainChain.最后再业务需要的地方直接注入该Chain即可.对于这种逻辑实现了解耦与灵活的组合.

@Configuration
public class RefundOrderAndRegainConfig {

  @Bean
  public RefundOrderAndRegainChain paidToRefund(
      RegainInventoryCount regainInventoryCount,
      RegainCoupon regainCoupon,
      RegainPromotionRegistered regainPromotionRegistered,
      RegainInvitationCodeWithDraw regainInvitationCode) {
    RefundOrderAndRegainChain chain = new RefundOrderAndRegainChain();
    chain.addInterceptor(regainInventoryCount);
    chain.addInterceptor(regainPromotionRegistered);
    chain.addInterceptor(regainCoupon);
    chain.addInterceptor(regainInvitationCode);
    return chain;
  }

}

责任链模式的本质

  1. 让请求者不关心具体接收者是谁,只需要得到自己的具体结果
  2. 在一个请求对应多个接收者情况下(Spring Security这种),接收者之间可以自由组合,灵活性很高
  3. 新增接收者处理也只需要增加链中的一个节点,不需要改动太多.

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Guava -- Bloom Filter原理

    去重在软件开发中经常需要用到,在Java当中一般使用Set集合,面对大量数据则可以利用取MD5签名等值后再进行去重,然而Set集合的实现原理决定了如果有大量的k...

    屈定
  • 设计模式--装饰者模式思考

    装饰者模式实际上是一直提倡的组合代替继承的实践方式,个人认为要理解装饰者模式首先需要理解为什么需要组合代替继承,继承又是为什么让人深恶痛绝.

    屈定
  • Java--死锁以及死锁的排查

    清单一代码有点长,但是逻辑很简单,有两个临界区变量lockA,lockB,线程A先获取到lockA在获取lockB,线程B则与之相反顺序获取锁,那么就可能会有以...

    屈定
  • 看了这篇,我确定你已经彻底搞懂Java的继承了

    遇到认真的读者是作者的一种幸运,真的,上一篇接口推送后,有好几个读者留言说,“二哥,你有一处内容需要修正,应该是接口中不能有 private 和 protect...

    沉默王二
  • SpringMvc解决js跨域

    跨站 HTTP 请求(Cross-site HTTP request)是指发起请求的资源所在域不同于该请求所指向资源所在的域的 HTTP 请求。比如说...

    王念博客
  • jbpm5.1介绍(1)

    介绍 jBPM是一个灵活的业务流程管理(BPM)套件。这使得业务分析师和开发人员之间的桥梁。传统的BPM引擎有一个重点,是有限的非技术人员。 jBPM的有两个...

    cloudskyme
  • 在谷歌、Facebook等顶级科技公司工作需要哪些技能

    https://lists.linkedin.com/2016/top-attractors/en/us

    CDA数据分析师
  • 谷歌SEO优化指南:做好这3步,排名也能很简单

    想要做好Google SEO,必须要有一个清晰的步骤流程。本次一尘SEO的谷歌SEO优化指南涵盖了做SEO的三个步骤:技术SEO, 站内SEO, 站外SEO。本...

    一尘SEO
  • 接口测试知识体系

    在2018年的总结还是在2019年的行业展望,都谈到一个关键字,那就是“接口测试”,针对大多数是来说,接口测试到底是什么,是简单的那个工具,发送一个...

    无涯WuYa
  • 【干货】因职业角色而异的十大数据科学技能

    有奖转发活动 回复“抽奖”参与《2015年数据分析/数据挖掘工具大调查》有奖活动。 ? 数据科学的实践需要三个一般领域的技能:商业洞察、计算机技术/编程和统计学...

    小莹莹

扫码关注云+社区

领取腾讯云代金券