⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 juejin.cn/post/7011490664714240008 「小杰博士」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注**微信公众号:【芋道源码】**有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言将得到认真回复。甚至不知道如何读源码也可以请教噢
  4. 新的源码解析文章实时收到通知。每周更新一篇左右
  5. 认真的源码交流微信群。

背景

最近在做需求,写一个方法,先在前面做验证,if 不满足 A 条件则 return,if 不满足 B 条件则 return...一共写了 5 个验证,等验证通过以后才执行下面的逻辑,这个也没问题。过了一阵产品提了需求,跟这个方法类似,我又把这个方法 copy 了一份,只不过验证条件稍微有点不一样,要变成 6 个验证了。

这时候我就发现有三个问题,第一重复代码,相同的 A 条件 B 条件 C 条件写了两份,没有复用。第二,“头重脚轻”,比如 100 行的方法,前面 60 行都是验证,后面 40 行才是真正有用的业务代码,你看一个方法功能的时候前面验证肯定是不关心的,只看后面 40 行到底在干什么逻辑,所以要缩短验证代码的行数。第三,先后顺序,一个方法A是在B之前验证的,另一个方法 A 是在 B 之后验证的,调整起来很不方便。

这时候我就想到了用**「责任链模式」**来进行优化解决。

定义

责任链模式(Chain of Responsibility Pattern)是将链中每一个节点看作是一个对象,每个节点处理的请求均不同,且内部自动维护一个下一节点对象。当一个请求从链式的首端发出时,会沿着链的路径依次传递给每一个节点对象,直至有对象处理这个请求为止。属于行为型模式。

生活中的应用场景就是**「审批流」**。责任链模式主要是解耦了请求与处理,客户只需将请求发送到链上即可,无需关心请求的具体内容和处理细节,请求会自动进行传递直至有节点对象进行处理。

通用UML类图

责任链模式

例子

下面写一个登录验证判断的例子,一般责任链模式会搭配着**「建造者模式」一起用,即「链式编程」**。因为这样链条看起来更加清晰明了,而传统的写法很抽象,很难看出谁谁谁在谁的前面,谁谁谁在谁的后面,如下所示:

AAAHandler.setNextHandler(deptManagerLeaveHandler);
directLeaderLeaveHandler.setNextHandler(deptManagerLeaveHandler);
BBBHandler.setNextHandler(AAAHandler);
deptManagerLeaveHandler.setNextHandler(gManagerLeaveHandler);

下面先创建一个 Handler 的抽象类,这个类里面有一个下一个 Handler 处理器 next,还有一个 Builder,这个就是用来构建链的,也是方便我们的链式编程。

public abstract class Handler<T> {

protected Handler next;

private void next(Handler next) {
this.next = next;
}

public abstract void doHandler(Member member);

public static class Builder<T> {
private Handler<T> head;
private Handler<T> tail;

public Builder<T> addHandler(Handler handler) {
if (this.head == null) {
this.head = this.tail = handler;
return this;
}
this.tail.next(handler);
this.tail = handler;
return this;
}

public Handler<T> build() {
return this.head;
}
}
}

下面写非空校验 ValidateHandler 类,这里面先判断用户名和密码是否为空,空的话返回,非空的话判断 next 是否为空,非空的话就丢给下一个处理器去执行。

public class ValidateHandler extends Handler {
@Override
public void doHandler(Member member) {
if (StringUtils.isEmpty(member.getUsername()) ||
StringUtils.isEmpty(member.getPassword())) {
System.out.println("用户名和密码不能为空");
return;
}
if (null != next) {
next.doHandler(member);
}
}
}

创建登录检验LoginHandler类,判断账号密码是否正确

public class LoginHandler extends Handler {

@Override
public void doHandler(Member member) {
if (!"jack".equals(member.getUsername()) || !"666".equals(member.getPassword())) {
System.out.println("用户名密码不正确");
return;
}
if (null != next) {
next.doHandler(member);
}
}
}

创建权限检验 AuthHandler 类,判断角色是否有权限

public class AuthHandler extends Handler {
@Override
public void doHandler(Member member) {
if (!"管理员".equals(member.getRoleName())) {
System.out.println("您不是管理员,没有操作权限");
return;
}
if (null != next) {
next.doHandler(member);
}
}
}

创建执行业务逻辑类

public class BusinessLogicHandler extends Handler {

@Override
public void doHandler(Member member) {
System.out.println("执行业务逻辑。。");
}
}

好,下面写个测试类来测试一下

public class Test {

public static void main(String[] args) {
Handler.Builder builder = new Handler.Builder();
//这里就是链式编程,谁在前谁在后看的清清楚楚,明明白白
builder.addHandler(new ValidateHandler())
.addHandler(new LoginHandler())
.addHandler(new AuthHandler())
.addHandler(new BusinessLogicHandler());
Member member = new Member();
member.setUsername("");
member.setPassword("");
builder.build().doHandler(member);
}

}

执行一下,提示用户名密码不能为空

修改下用户名和密码

执行一下,提示用户名密码不正确

直到把用户名密码权限都设置正确

此时所有验证都通过,开始执行业务逻辑了

源码中的应用

我们来看一个J2EE标准中非常常见的Filter类:

public interface Filter {
public default void init(FilterConfig filterConfig) throws ServletException {}

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

public default void destroy() {}
}

这个Filter接口的定义非常简单,相当于责任链模型中的handler抽象角色,我们来看Spring中的实现MockFilterChain类:

public class MockFilterChain implements FilterChain {

@Nullable
private ServletRequest request;
@Nullable
private ServletResponse response;
private final List<Filter> filters;
@Nullable
private Iterator<Filter> iterator;

public MockFilterChain() {
this.filters = Collections.emptyList();
}

public MockFilterChain(Servlet servlet) {
this.filters = initFilterList(servlet);
}

public MockFilterChain(Servlet servlet, Filter... filters) {
Assert.notNull(filters, "filters cannot be null");
Assert.noNullElements(filters, "filters cannot contain null values");
this.filters = initFilterList(servlet, filters);
}

private static List<Filter> initFilterList(Servlet servlet, Filter... filters) {
Filter[] allFilters = ObjectUtils.addObjectToArray(filters, new ServletFilterProxy(servlet));
return Arrays.asList(allFilters);
}

@Nullable
public ServletRequest getRequest() {
return this.request;
}

@Nullable
public ServletResponse getResponse() {
return this.response;
}

@Override
public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
Assert.notNull(request, "Request must not be null");
Assert.notNull(response, "Response must not be null");
Assert.state(this.request == null, "This FilterChain has already been called!");

if (this.iterator == null) {
this.iterator = this.filters.iterator();
}
//核心代码执行
if (this.iterator.hasNext()) {
Filter nextFilter = this.iterator.next();
nextFilter.doFilter(request, response, this);
}

this.request = request;
this.response = response;
}

public void reset() {
this.request = null;
this.response = null;
this.iterator = null;
}

...
}

这里面把链条中所有的 Filter 都放到List<Filter> filters中, 在 doFilter() 方法中有一段核心的代码this.iterator.hasNext(),这个就相当于 for 循环的执行 filters 中的 Filter 方法。「虽然写法不同,但也起到了责任链的功能,所以在学习设计模式中,不要拘泥于标准的写法,很多都是变种的,或者写着写着四不像的模式,既像这个设计模式,又像那个设计模式,这个很正常,能起到精简代码,高效运行的都是好代码。」

优缺点

优点:

  1. 将请求与处理解耦。
  2. 请求处理者(节点对象)只需关注自己感兴趣的请求进行处理,对于不感兴趣的请求,直接转发给下一级节点对象。
  3. 具备链式传递处理请求功能,请求发送者无需知晓链路结构,只需等待请求处理结果。
  4. 链路结构灵活,可以通过改变链路结构动态地新增或删减责任。
  5. 易于扩展新的请求处理类(节点),符合开闭原则。

缺点:

  1. 责任链太长或者处理时间过长,会影响整体性能。
  2. 如果节点对象存在循环引用时,会造成死循环,导致系统崩溃。
文章目录
  1. 1. 背景
  2. 2. 定义
  3. 3. 通用UML类图
  4. 4. 例子
  5. 5. 源码中的应用
  6. 6. 优缺点