作者:周可强

一、责任链模式简介

1、责任链模式定义

责任链(Chain of Responsibility)模式的定义:为了避免请求发送者与多个请求处理者耦合在一起,于是将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。在责任链模式中,客户只需要将请求发送到责任链上即可,无须关心请求的处理细节和请求的传递过程,请求会自动进行传递。所以责任链将请求的发送者和请求的处理者解耦了。

2、责任链特点

责任链模式是一种对象行为型模式,

其主要优点如下。

1).降低了对象之间的耦合度。该模式使得一个对象无须知道到底是哪一个对象处理其请求以及链的结构,发送者和接收者也无须拥有对方的明确信息。

2).增强了系统的可扩展性。可以根据需要增加新的请求处理类,满足开闭原则。

3).增强了给对象指派职责的灵活性。当工作流程发生变化,可以动态地改变链内的成员或者调动它们的次序,也可动态地新增或者删除责任。责任链简化了对象之间的连接。每个对象只需保持一个指向其后继者的引用,不需保持其他所有处理者的引用,这避免了使用众多的 if 或者 if···else 语句。

4).责任分担。每个类只需要处理自己该处理的工作,不该处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则。

其主要缺点如下。

1).不能保证每个请求一定被处理。由于一个请求没有明确的接收者,所以不能保证它一定会被处理,该请求可能一直传到链的末端都得不到处理。

2).对比较长的职责链,请求的处理可能涉及多个处理对象,系统性能将受到一定影响。

3).职责链建立的合理性要靠客户端来保证,增加了客户端的复杂性,可能会由于职责链的错误设置而导致系统出错,如可能会造成循环调用。

3、责任链结构图

二、Dubbo中的责任链模式

1、过滤器日志

通过打印过滤器的日志,我们可以看到在发布服务的过程中,会依次经过dubbo的每个过滤器类,以此来保证服务的完善。

2、过滤器简图

dubbo通过将每个过滤器类filter封装成dubbo的核心模型invoker进行组装,最终形成晚上的过滤器责任链filterChain。

3、过滤器类图

Protocol是核心模型invoker暴露和引用的主功能入口,采用SPI的接口,他的两个方法export和refer分别对应provider和consumer端的服务功能,ProtocolFilterWapper则是Dubbo的过滤器的主要实现类,通过重写的export和refer指向buildInvokerChain方法,在buildInvokerChain中进行责任链的获取与组装,在extensionLoader中通过SPI获取Filter的各实现类,并通过ActivateComparator进行排序,最终形成完整的责任链。

三、Dubbo中各Filter责任介绍

1、provider用到的filter

2、consumer用到的filter

四、源码探析

进入到核心类ProtocolFilterWrapper中,在实现类中export和refer,都采用相同的构造责任链方法buildInvokerChain,只是通过参数group进行区分

在buildInvokerChain中,通过getActivateExtension获取过滤器数组,并在之后封装成核心模型invoker并组装成责任链

private static <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, String key, String group) {
Invoker<T> last = invoker;
// 获得过滤器数组 (已经排好序的)
List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group);
// 创建带 Filter 链的 Invoker 对象
if (!filters.isEmpty()) {
for (int i = filters.size() - 1; i >= 0; i--) {
final Filter filter = filters.get(i);
final Invoker<T> next = last;
last = new Invoker<T>() { @Override
public Class<T> getInterface() {
return invoker.getInterface();
} @Override
public URL getUrl() {
return invoker.getUrl();
} @Override
public boolean isAvailable() {
return invoker.isAvailable();
} @Override
public Result invoke(Invocation invocation) throws RpcException {
return filter.invoke(next, invocation);
} @Override
public void destroy() {
invoker.destroy();
} @Override
public String toString() {
return invoker.toString();
}
};
}
}
System.out.println("group:" + group);
for (Filter filter : filters) {
System.out.println(filter.getClass());
}
return last;
}

getActivateExtension是主要的组装逻辑,他包含获取与排序等逻辑

首先进行判断是否采用系统默认的Filter过滤器,并对每一个系统过滤器进行校验是否移除,然后对系统过滤器排序,再通过指定的参数,增加用户自定义的过滤器组装责任链


public List<T> getActivateExtension(URL url, String key, String group) {
// 从 Dubbo URL 获得参数值
String value = url.getParameter(key);
// 获得符合自动激活条件的拓展对象数组
return getActivateExtension(url, value == null || value.length() == 0 ? null : Constants.COMMA_SPLIT_PATTERN.split(value), group);
}
public List<T> getActivateExtension(URL url, String[] values, String group) {
List<T> exts = new ArrayList<T>();
//所有用户自己配置的filter信息(有些Filter是默认激活的,有些是配置激活的,这里的names就指的配置激活的filter信息)
List<String> names = values == null ? new ArrayList<String>(0) : Arrays.asList(values);
// 处理自动激活的拓展对象们
// 判断不存在配置 `"-name"` 。例如,<dubbo:service filter="-default" /> ,代表移除所有默认过滤器。
if (!names.contains(Constants.REMOVE_VALUE_PREFIX + Constants.DEFAULT_KEY)) {
// 获得拓展实现类数组
getExtensionClasses();
// 循环
for (Map.Entry<String, Activate> entry : cachedActivates.entrySet()) {
//name指的是SPI读取的配置文件的key
String name = entry.getKey();
Activate activate = entry.getValue();
if (isMatchGroup(group, activate.group())) { // 匹配分组
// 获得拓展对象
T ext = getExtension(name);
if (!names.contains(name) // 不包含在自定义配置里。如果包含,会在下面的代码处理。
&& !names.contains(Constants.REMOVE_VALUE_PREFIX + name) // 判断是否配置移除。例如 <dubbo:service filter="-monitor" />,则 MonitorFilter 会被移除
&& isActive(activate, url)) { // 判断是否激活
exts.add(ext);
}
}
}
// 排序
Collections.sort(exts, ActivateComparator.COMPARATOR);
}
// 处理自定义配置的拓展对象们。例如在 <dubbo:service filter="demo" /> ,代表需要加入 DemoFilter
List<T> usrs = new ArrayList<T>();
for (int i = 0; i < names.size(); i++) {
String name = names.get(i);
if (!name.startsWith(Constants.REMOVE_VALUE_PREFIX) && !names.contains(Constants.REMOVE_VALUE_PREFIX + name)) { // 判断非移除的
// 将配置的自定义在自动激活的拓展对象们前面。例如,<dubbo:service filter="demo,default,demo2" /> ,则 DemoFilter 就会放在默认的过滤器前面。
if (Constants.DEFAULT_KEY.equals(name)) {
if (!usrs.isEmpty()) {
exts.addAll(0, usrs);
usrs.clear();
}
} else {
// 获得拓展对象
T ext = getExtension(name);
usrs.add(ext);
}
}
}
// 添加到结果集
if (!usrs.isEmpty()) {
exts.addAll(usrs);
}
return exts;
}

系统默认的过滤器和udf过滤器进行区分

以ContextFilter为例,系统默认过滤器包含Activate注解,用于指定所属分组与排序权重,用户自己实现的过滤器则不能添加Activate注解通过发布时指定所需的过滤器

我们看下具体的排序比较方法,首先判断Activate注解是否指定before和after参数用来指定排序,若不存在则采用order权重进行排序

ActivateComparator.class
public int compare(Object o1, Object o2) {
// 基本排序
if (o1 == null && o2 == null) {
return 0;
}
if (o1 == null) {
return -1;
}
if (o2 == null) {
return 1;
}
if (o1.equals(o2)) {
return 0;
} Activate a1 = o1.getClass().getAnnotation(Activate.class);
Activate a2 = o2.getClass().getAnnotation(Activate.class); // 使用注解的 `after` 和 `before` 属性,排序
if ((a1.before().length > 0 || a1.after().length > 0 || a2.before().length > 0 || a2.after().length > 0) // (a1 或 a2) 存在 (`after` 或 `before`) 属性。
&& o1.getClass().getInterfaces().length > 0 && o1.getClass().getInterfaces()[0].isAnnotationPresent(SPI.class)) { // 实现的接口,有 @SPI 注解。
// 获得拓展加载器
ExtensionLoader<?> extensionLoader = ExtensionLoader.getExtensionLoader(o1.getClass().getInterfaces()[0]);
// 以 a1 的视角,进行一次比较
if (a1.before().length > 0 || a1.after().length > 0) {
String n2 = extensionLoader.getExtensionName(o2.getClass());
for (String before : a1.before()) {
if (before.equals(n2)) {
return -1;
}
}
for (String after : a1.after()) {
if (after.equals(n2)) {
return 1;
}
}
}
// 以 a2 的视角,进行一次比较。
if (a2.before().length > 0 || a2.after().length > 0) {
String n1 = extensionLoader.getExtensionName(o1.getClass());
for (String before : a2.before()) {
if (before.equals(n1)) {
return 1;
}
}
for (String after : a2.after()) {
if (after.equals(n1)) {
return -1;
}
}
}
} // 使用注解的 `order` 属性,排序。
int n1 = a1 == null ? 0 : a1.order();
int n2 = a2 == null ? 0 : a2.order();
// never return 0 even if n1 equals n2, otherwise, o1 and o2 will override each other in collection like HashSet
return n1 > n2 ? 1 : -1;
}

总结:

责任链模式是设计模式中简单且常见的设计模式,可能我们日常中也会经常应用责任链模式,dubbo中的责任链模式将灵活性发挥的很充分,不论是从分组概念、通过注解指定排序的优先级、每个filter的是否移除 等,将每个filter做成了可插拔的,减少对代码的侵入性,这点是非常值得我们学习的。

Dubbo架构设计与源码解析(三)责任链模式的更多相关文章

  1. Java生鲜电商平台-电商会员体系系统的架构设计与源码解析

    Java生鲜电商平台-电商会员体系系统的架构设计与源码解析 说明:Java生鲜电商平台中会员体系作为电商平台的基础设施,重要性不容忽视.我去年整理过生鲜电商中的会员系统,但是比较粗,现在做一个最好的整 ...

  2. Java生鲜电商平台-优惠券系统的架构设计与源码解析

    Java生鲜电商平台-优惠券系统的架构设计与源码解析 电商后台:实例解读促销系统 电商后台系统包括商品管理系统.采购系统.仓储系统.订单系统.促销系统.维权系统.财务系统.会员系统.权限系统等,各系统 ...

  3. Java生鲜电商平台-促销系统的架构设计与源码解析

    Java生鲜电商平台-促销系统的架构设计与源码解析 说明:本文重点讲解现在流行的促销方案以及源码解析,让大家对促销,纳新有一个深入的了解与学习过程. 促销系统是电商系统另外一个比较大,也是比较复杂的系 ...

  4. Java生鲜电商平台-秒杀系统微服务架构设计与源码解析实战

    Java生鲜电商平台-秒杀系统微服务架构设计与源码解析实战 Java生鲜电商平台-  什么是秒杀 通俗一点讲就是网络商家为促销等目的组织的网上限时抢购活动 比如说京东秒杀,就是一种定时定量秒杀,在规定 ...

  5. Celery 源码解析三: Task 对象的实现

    Task 的实现在 Celery 中你会发现有两处,一处位于 celery/app/task.py,这是第一个:第二个位于 celery/task/base.py 中,这是第二个.他们之间是有关系的, ...

  6. Mybatis源码解析(三) —— Mapper代理类的生成

    Mybatis源码解析(三) -- Mapper代理类的生成   在本系列第一篇文章已经讲述过在Mybatis-Spring项目中,是通过 MapperFactoryBean 的 getObject( ...

  7. 6 admin(注册设计)源码解析、单例模式

    1.单例模式 https://www.cnblogs.com/yuanchenqi/articles/8323452.html 单例模式(Singleton Pattern)是一种常用的软件设计模式, ...

  8. ReactiveCocoa源码解析(三) Signal代码的基本实现

    上篇博客我们详细的聊了ReactiveSwift源码中的Bag容器,详情请参见<ReactiveSwift源码解析之Bag容器>.本篇博客我们就来聊一下信号量,也就是Signal的的几种状 ...

  9. ReactiveSwift源码解析(三) Signal代码的基本实现

    上篇博客我们详细的聊了ReactiveSwift源码中的Bag容器,详情请参见<ReactiveSwift源码解析之Bag容器>.本篇博客我们就来聊一下信号量,也就是Signal的的几种状 ...

  10. 第四章 dubbo内核之aop源码解析

    ExtensionLoader<Protocol> loader = ExtensionLoader.getExtensionLoader(Protocol.class); final P ...

随机推荐

  1. 我公司是属于生产制造业,最近考虑实施ERP,生产制造业的ERP那家比较好?

    直接告诉你用哪家ERP,那我就太不负责任了,不同企业的规模选用不同的系统,匹配很重要!比如你大型企业,业务管理都比较标准规范,变化性也不大,不差钱预算没问题(千万元起步),你可以考虑下头部厂商.但如果 ...

  2. 洛谷P1714 切蛋糕(单调队列)

    先放代码...... 1 #include<bits/stdc++.h> 2 using namespace std; 3 const int N=5e5+10,M=0x3f3f3f3f; ...

  3. Js实现一键复制小功能

    function copyToClipboard(textToCopy) { // navigator clipboard 需要https等安全上下文 if (navigator.clipboard ...

  4. HFS局域网分享文件的神器(附下载链接)

    温馨提示,下载链接在页末 前言 假如说你需要传递个学习资料给好基友,我们有许多种方式可选:硬盘媒介.网络分享等. 要是论速度,还是得拿3.0或以上的U盘来拷贝,确实神速哈哈.但是其也有局限性,比如需要 ...

  5. JUI(6)线程池

    文章目录 1.SynchronousQueue 2.线程池(重点) 2.1 使用单例 2.2.使用固定大小的线程 2.3.缓存线程池 2.4 七大参数 1.SynchronousQueue packa ...

  6. SpringCloud(二) - Eureka注册中心,feign远程调用,hystrix降级和熔断

    1.项目模块介绍 2. 父项目 主要依赖 spring-cloud 的 版本控制 <properties> <!-- springCloud 版本 --> <scd.ve ...

  7. pycharm安装第三方的包

    这里以安装selenium为例子 在file下找到settings 在弹出的窗口点击python interpreter ,然后在右侧点击[+] 在弹窗顶部输入要导入的包名,在下方列表找到对应包后,点 ...

  8. MFC 学习笔记

    MFC 学习笔记 一.MFC编程基础: 概述: 常用头文件: MFC控制台程序: MFC库程序: 规则库可以被各种程序所调用,扩展库只能被MFC程序调用. MFC窗口程序: 示例: MFC库中类的简介 ...

  9. 【Virt.Contest】CF1215(div.2)

    第二次打虚拟赛. CF 传送门 T1:Yellow Cards 黄色卡片 中规中矩的 \(T1\). 首先可以算出一个人也不罚下时发出的最多黄牌数: \(sum=a1*(k1-1)+a2*(k2-1) ...

  10. 软件开发-客观综合(GO)

    1 对从go源码和汇编源码生成可执行程序的过程,下面描述错误的是() ​A. 使用go tool compile可以将go源码编译成目标文件 B. 使用go tool asm可以将go源码编译成汇编代 ...