Skip to content

1. 责任链模式—是什么

专业版

  • 含义:客户端发出一个请求,链上的对象都有机会来处理这一请求,而客户端不需要知道谁是具体的处理对象。
  • 内在含义:
    • 使多个对象都有机会处理请求,从而避免了请求的发送者和接受者之间的耦合关系。
    • 将这些对象连成一条链,并沿着这条链传递该请求,直到有对象处理它为止。
    • 其过程实际上是一个递归调用。
  • 它属于一种行为模式。

生活版

例如:《红楼梦》中的击鼓传花

击鼓串花:是一种热闹而又紧张的饮酒游戏。

**游戏规则:**在酒宴上宾客一次坐定位置,由一人击鼓,击鼓的地方与传花的地方是分开的,以示公正。开始击鼓时,花束就开始依次传递,鼓声遗落,如果花束在某人手中,则该人就得饮酒。

比如说,贾母、贾赦、贾政、贾宝玉和贾环是五个参加击鼓传花游戏的传花者,他们组成一个环链。击鼓者将话传给贾母,开始传花游戏。花由贾母传给贾赦,由贾赦传给贾政,由贾政传给贾宝玉,由贾宝玉传给贾环,由贾环传回给贾母,如此往复,如下图所示。当鼓声停止时,手中有花的人就得执行酒令。

如图所示:

例如:打游戏《植物大战僵尸》闯关

每一个关独有一个老大,如果打不过在原关重来,打过了就下一关,每打过一关就会解锁下一关,这种就相当于链表一样,你的责任就是负责闯关,不管你闯到第几关,给我打个老大回来就好,打不过告诉你失败再那关,重新来,直到打败老大

2. 责任链模式—作用

职责链上的处理者负责处理请求,客户只需要将请求发送到职责链上即可,无须关心请求的处理细节和请求的传递,所以职责链将请求的发送者和请求的处理者解耦了。

3. 责任链模式—核心使用规则

抽象处理者(Handler)角色

  • 定义出一个处理请求的接口。
  • 如果需要,接口可以定义出一个方法以设定和返回对下一个对象的引用。
  • 这个角色通常由一个 Java 抽象类或者 Java 接口实现。

具体处理者(ConcreteHandler)角色

  • 具体处理者接到请求后,可以选择将请求处理掉,或者将请求传给下一个对象。
  • 由于具体处理者持有对下一个对象的引用。

4. 责任链模式—应用场景

  • 多条件流程判断:权限控制

  • ERP 系统中流程审批业务:总经理、人事经理、项目经理

  • Java 过滤器的底层实现 Filter :在 Java 过滤器中客户端发送请求到服务器端,过滤会经过参数过滤、session 过滤、表单过滤、隐藏过滤、检测请求头过滤。

5. 责任链模式—案例 1:模拟网关权限控制

功能要求:在网关作为微服务程序的入口,拦截客户端所有的请求实现权限控制 。

  • 第一关:API 接口限流
    • 第二关:黑名单拦截
      • 第三关:用户会话信息拦截

UML 结构图如下所示:

1. 抽象处理者(Handler)

  • 作用:负责处理请求
  • 步骤:
    • 创建一个抽象处理者 Handler
    • 定义出一个方法()以设定下一个对象 setNextHandler()
    • 定义一个返回对下一个对象的引用getNextHandler()
java
package com.gateway.handler.abstracts;

/**
 * Created by Calvin on 2019/5/9
 * 定义一个抽象的处理器
 */
public abstract class Handler {

    /**
     * 下一个处理任务对象
     */
    protected Handler nextHandler;

    /**
     * 共同的处理行为(强制必须实现)
     * 具体功能
     */
    public abstract void function();

    /**
     * 设置下一个处理任务
     * @param nextHandler 下一个处理对象
     */
    public void setNextHandler(Handler nextHandler){
        this.nextHandler = nextHandler;
    }

    /**
     * 获取下一个处理任务
     */
    protected void getNextHandler(){
        if(nextHandler != null) {
            // 指向下一个处理器功能
            nextHandler.function();
        }
    }
}

2. 具体处理者(ConcreteHandler)

  • 具体处理者:
    • ApiCurrentLimitHandler API 接口限流
    • BlackListHandler 黑名单拦截
    • ConversationHandler 用户会话信息拦截
  • 功效:
    • 具体处理者接到请求后,可以选择将请求处理掉,或者将请求传给下一个对象。
    • 由于具体处理者持有对下一个对象的引用 getNextHandler()
java
package com.gateway.handler;

import com.gateway.handler.abstracts.Handler;
import org.springframework.stereotype.Component;

/**
 * Created by Calvin on 2019/5/9
 * API接口限流处理器
 */
@Component
public class ApiCurrentLimitHandler extends Handler {

    @Override
    public void function() {
        System.out.println(">>>>>>>>>>> 第一关:API接口限流......");
        // 第一关执行完毕执行第二关
        getNextHandler();
    }
}
java
package com.gateway.handler;

import com.gateway.handler.abstracts.Handler;
import org.springframework.stereotype.Component;

/**
 * Created by Calvin on 2019/5/9
 * 黑名单拦截处理器
 */
@Component
public class BlackListHandler extends Handler {

    @Override
    public void function() {
        System.out.println(">>>>>>>>>>> 第二关:黑名单拦截......");
        // 第二关执行第三关任务
        getNextHandler();
    }
}
java
package com.gateway.handler;


import com.gateway.handler.abstracts.Handler;
import org.springframework.stereotype.Component;

/**
 * Created by Calvin on 2019/5/9
 */
@Component
public class ConversationHandler extends Handler {

    @Override
    public void function() {
        System.out.println(">>>>>>>>>>>第三关:用户会话信息拦截......");
    }
}

3. 在网关实现类中,通过数据库获取处理器 BeanID,再通过处理器 BeanId 从上下文中实例处理器,并且递归设置处理器中的下一个处理器;最终,执行处理器中的所有功能。

3.1 执行相应的 SQL 语句

  • 实现方式:数据库表中设置链表结构,在处理器中添加上一个处理器和下一个处理器。
mysql
CREATE TABLE `gateway_handler` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `handler_name` varchar(32) DEFAULT NULL COMMENT '处理器名称',
  `handler_bean_id` varchar(32) DEFAULT NULL COMMENT '处理器BeanID',
  `pre_handler_bean_id` varchar(32) DEFAULT NULL COMMENT '上一个处理器BeanID',
  `next_handler_bean_id` varchar(32) DEFAULT NULL COMMENT '下一个处理器BeanID',
  PRIMARY KEY (`ID`)
) ENGINE=InnoDB AUTO_INCREMENT=19 DEFAULT CHARSET=utf8 COMMENT='权限表';

-- ----------------------------
-- Records of gateway_handler
-- ----------------------------
INSERT INTO `gateway_handler` VALUES ('1', 'Api接口限流', 'apiCurrentLimitHandler', null, 'blackListHandler');
INSERT INTO `gateway_handler` VALUES ('2', '黑名单拦截', 'blackListHandler', 'apiCurrentLimitHandler', 'conversationHandler');
INSERT INTO `gateway_handler` VALUES ('3', '会话验证', 'conversationHandler', 'blackListHandler', null);

3.2 创建数据访问层,查询处理器

  • getFirstHandler()查询第一个处理器。
  • getByHandler()根据处理器 BeanId, 查询处理器。
java
package com.gateway.mapper;

import com.gateway.entity.GatewayHandlerEntity;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;

/**
 * Created by Calvin on 2019/5/9
 */
public interface HandlerMapper {

    /**
     * 查询第一个处理器
     *
     * @return 网管处理器实体类
     */
    @Select("SELECT " +
            "id AS id," +
            "handler_name AS handlerName," +
            "handler_bean_id AS handlerBeanId ," +
            "pre_handler_bean_id AS preHandlerBeanId ," +
            "next_handler_bean_id AS nextHandlerBeanId  " +
            "FROM gateway_handler " +
            "WHERE pre_handler_bean_id IS NULL;")
    GatewayHandlerEntity getFirstHandler();

    /**
     * 根据处理器BeanId, 查询处理器
     *
     * @param handlerBeanId 处理器BeanId
     * @return 网管处理器实体类
     */
    @Select("SELECT " +
            "id AS id," +
            "handler_name AS handlerName," +
            "handler_bean_id AS handlerBeanId ," +
            "pre_handler_bean_id AS preHandlerBeanId ," +
            "next_handler_bean_id AS nextHandlerBeanId  " +
            "FROM gateway_handler " +
            "WHERE handler_bean_id=#{handlerBeanId}")
    GatewayHandlerEntity getByHandler(@Param("handlerBeanId") String handlerBeanId);
}

3.3 创建网关实现类,通过数据库查询,递归设置下一个处理器。、

  • 网关实现类 GatewayServiceImpl.java
java
package com.gateway.service.impl;

import com.gateway.entity.GatewayHandlerEntity;
import com.gateway.handler.abstracts.Handler;
import com.gateway.mapper.HandlerMapper;
import com.gateway.service.GatewayService;
import com.gateway.utils.ContextUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;


/**
 * Created by Calvin on 2019/5/10
 */
@Service
public class GatewayServiceImpl implements GatewayService {

    @Autowired
    private HandlerMapper handlerMapper;

    // 处理器第二次存在内存当中
    private Handler handler;

    @Override
    public void function() {
        // 如果处理器为空,执行第一次赋值后,第二次存在内存当中
        if (handler == null) {
            // 1.从数据库中查找地址第一个处理器信息
            GatewayHandlerEntity firstHandlerEntity = handlerMapper.getFirstHandler();
            // 2.获取第一个处理器
            String handlerBeanId = firstHandlerEntity.getHandlerBeanId();
            Handler firstHandler = ContextUtils.getBean(handlerBeanId, Handler.class);
            // 3.记录当前循环handler对象
            Handler tempHandler = firstHandler;
            if(firstHandlerEntity != null){
                // 4.获取下一个处理器beanId
                String nextHandlerBeanId = firstHandlerEntity.getNextHandlerBeanId();
                // 5. 递归
                recursive(nextHandlerBeanId, tempHandler);
            }
            handler = tempHandler;
        }
        handler.function();
    }

    /**
     * 递归:设置下一个处理器
     *
     * @param nextHandlerBeanId 下一个处理器BeanId
     * @param tempHandler 临时处理器
     */
    private void recursive(String nextHandlerBeanId, Handler tempHandler) {
        // 处理器中的下一个处理器不为空。
        if (!StringUtils.isEmpty(nextHandlerBeanId)) {
            // 下一个处理器不为空,得到下一个处理器.
            Handler nextHandler = ContextUtils.getBean(nextHandlerBeanId, Handler.class);
            // 设置临时处理器中的下一个处理器
            tempHandler.setNextHandler(nextHandler);
            // 设置下一个nextHandlerId
            GatewayHandlerEntity nextHandlerEntity = handlerMapper.getByHandler(nextHandlerBeanId);

            if (null != nextHandlerEntity) {
                // 赋值的作用让它继续判断是否有下一个
                nextHandlerBeanId = nextHandlerEntity.getNextHandlerBeanId();
                tempHandler =  nextHandler;
                // 递归
                this.recursive(nextHandlerBeanId, tempHandler);
            }
        }
    }
}

4. 最后,创建网关 API 接口进行访问。

  • GatewayService.java: 网关接口
java
package com.gateway.service;

public interface GatewayService {

    /**
     * 网关功能
     * 1. API 网关限流处理器
     * 2. 黑名单拦截
     * 3. 用户会话信息拦截
     */
    void function();

}
  • GatewayController.java :网关 API 接口
java
package com.gateway.controller;

import com.gateway.service.GatewayService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping(value = "gateway")
public class GatewayController {

    @Autowired
    private GatewayService gatewayService;

    @GetMapping("/api")
    public String api() {
      gatewayService.function();
      return "success";
    }
}

6. 结果演示

代码链接:https://github.com/1016280226/design-patterns

7. 责任链模式—优缺点

优点:

  • 动态组合职责
    • 说明:职责链模式会把功能分散到单独的职责对象中,然后在使用时动态的组合形成链,从而可以灵活的分配职责对象,也可以灵活的添加改变对象职责。
  • 请求者和接受者解耦
    • 说明:请求者不需要知道接受者,也不需要知道如何处理。每个职责对象只负责自己的职责范围,其他的交给后继者。各个组件间完全解耦。

缺点:

  • 产生很多细粒度的对象
    • 说明:因为功能处理都分散到了单独的职责对象中,每个对象功能单一,要把整个流程处理完,需要很多的职责对象,会产生大量的细粒度职责对象。
  • 不能保证请求一定被接收
    • 说明:每个职责对象都只负责自己的部分,这样就可以出现某个请求,即使把整个链走完,都没有职责对象处理它。这就需要提供默认处理,并且注意构造链的有效性。