设计模式——责任链

责任链模式是一种行为设计模式,用于将请求的发送者和接收者解耦。在这种模式中,请求通过一条由多个对象组成的链传递,直到有一个对象能够处理该请求为止。每个对象都可以决定是否处理请求以及是否将请求传递给下一个对象。

责任链模式通常在以下场景中使用:

处理请求的对象可能不确定:当请求的处理对象需要动态确定时,可以使用责任链模式。责任链模式允许请求在链中传递,直到有一个处理者能够处理该请求。

需要避免发送者和接收者之间的耦合:责任链模式可以将发送者和接收者解耦,发送者不需要知道具体的接收者是谁,只需要将请求发送给责任链的第一个处理者即可。

处理请求的对象集合需要动态组合:责任链模式允许动态地组织处理请求的对象集合,可以根据需要灵活地添加、删除或修改处理者,而不影响客户端的代码。

处理请求的对象需要按顺序执行:如果需要按照一定的顺序依次执行处理者来处理请求,责任链模式是一个很好的选择。

需要对请求的发送者和接收者进行解耦:责任链模式可以帮助发送者和接收者之间的解耦,发送者只需要将请求发送给责任链的第一个处理者,而不需要知道请求最终由谁来处理。

使用场景:

  • 多条件流程判断:权限控制
  • ERP系统流程审批:总经理、人事经理、项目经理
  • Java过滤器的底层实现Filter

假设现在有一个闯关游戏,进入下一关的条件是上一关的分数要高于 xx,并且每一关的实际内容都不一样,那么就需要创建每个关卡的对应类

java 复制代码
//第一关
public class FirstPassHandler {
    public int handler(){
        System.out.println("第一关-->FirstPassHandler");
        return 80;
    }
}
//第二关
public class SecondPassHandler {
    public int handler(){
        System.out.println("第二关-->SecondPassHandler");
        return 90;
    }
}
//第三关
public class ThirdPassHandler {
    public int handler(){
        System.out.println("第三关-->ThirdPassHandler,这是最后一关啦");
        return 95;
    }
}

//客户端
public class HandlerClient {
    public static void main(String[] args) {

        FirstPassHandler firstPassHandler = new FirstPassHandler();//第一关
        SecondPassHandler secondPassHandler = new SecondPassHandler();//第二关
        ThirdPassHandler thirdPassHandler = new ThirdPassHandler();//第三关

        int firstScore = firstPassHandler.handler();
        //第一关的分数大于等于80则进入第二关
        if(firstScore >= 80){
            int secondScore = secondPassHandler.handler();
            //第二关的分数大于等于90则进入第二关
            if(secondScore >= 90){
                thirdPassHandler.handler();
            }
        }
    }
}

注意,如果关卡特别多,那么主函数代码的if判断就开始变得复杂。

责任链工厂实现网关权限控制

在网关作为微服务程序的入口,拦截客户端所有的请求实现权限控制 ,比如先判断Api接口限流、黑名单、用户会话、参数过滤。 Api接口限流→黑名单拦截→用户会话→参数过滤

  • 抽象处理者(Handler)角色:定义一个处理请求的接口,包含抽象处理方法和一个后继连接。
  • 具体处理者(Concrete Handler)角色:实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理请求则处理,否则将该请求转给它的后继者。
  • 客户类(Client)角色:创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。

具体实现思路:通过配置文件或者枚举类的方式表明角色之间的关系,然后通过链表连接起来,形成责任链的方式,然后再每个权限控制处理类上抽象出一个父类或者接口,每个具体的处理类去继承或者实现。

定义实体类GatewayEntity,其中包括权限的名称和具体的处理类路径(用于反射寻找具体处理类):

java 复制代码
@Data
@AllArgsConstructor
public class GatewayEntity {

    private Integer handlerId;

    private String name;

    private String conference;

    // 可以根据实际需求增加字段,用于给不同处理类进行判断是否有权限处理,这里一律假设为字段condition
    private String condition;
    
    private Integer preHandlerId;

    private Integer nextHandlerId;
    


}

定义枚举类GatewayEnum,排列好关系(也可也将这些关系通过数据库表的方式存储):

方式1:

java 复制代码
public enum GatewayEnum {

    API_HANDLER(new GatewayEntity(1, "api 接口限流", "GateWay.Impl.ApiLimitGatewayHandler", "条件1", null, 2)),
    BLACKLIST_HANDLER(new GatewayEntity(2, "黑名单列表拦截", "GateWay.Impl.BlacklistGatewayHandler", "条件2", 1, 3)),
    SESSION_HANDLER(new GatewayEntity(3, "用户会话拦截", "GateWay.Impl.SessionGatewayHandler", "条件3", 2, null));

    GatewayEntity gatewayEntity;

    public GatewayEntity getGatewayEntity(){
        return gatewayEntity;
    }

    GatewayEnum(GatewayEntity gatewayEntity){
        this.gatewayEntity = gatewayEntity;
    }

}

方式2:

sql 复制代码
CREATE TABLE `gateway_handler` (
  `handlerId` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `name` varchar(32) DEFAULT NULL COMMENT 'handler名称',
  `conference` varchar(32) DEFAULT NULL COMMENT 'handler主键id',
  `condition` varchar(32) DEFAULT NULL COMMENT '判断需求字段',
  `prev_handler_id` int(11) DEFAULT NULL,
  `next_handler_id` int(11) DEFAULT NULL COMMENT '下一个handler',
  PRIMARY KEY (`ID`)
) ENGINE=InnoDB AUTO_INCREMENT=19 DEFAULT CHARSET=utf8 COMMENT='权限表';

-- ----------------------------
-- Records of gateway_handler
-- ----------------------------
INSERT INTO `gateway_handler` VALUES ('1', 'Api接口限流', 'GateWay.Impl.ApiLimitGatewayHandler', "条件1", null, '2');
INSERT INTO `gateway_handler` VALUES ('2', '黑名单列表拦截', 'GateWay.Impl.BlacklistGatewayHandler', "条件2", '1', '3');
INSERT INTO `gateway_handler` VALUES ('3', '用户会话拦截', 'GateWay.Impl.SessionGatewayHandler', "条件3", '2', null);

定义逻辑接口层GatewayDao,编写获取实体类GatewayEntity的接口:

java 复制代码
public interface GatewayDao {

    GatewayEntity getFirstGatewayEntity();

    GatewayEntity getGatewayEntity(Integer handlerId);
    
}

逻辑接口的具体实现GatewayImpl:

java 复制代码
public class GatewayImpl implements GatewayDao{

    private static Map<Integer, GatewayEntity> map = new HashMap<Integer, GatewayEntity>();
    /**
     * 初始化,将枚举中配置的handler初始化到map中,方便获取
     */
    static {
        GatewayEnum[] values = GatewayEnum.values();
        for (GatewayEnum value : values) {
            GatewayEntity gatewayEntity = value.getGatewayEntity();
            map.put(gatewayEntity.getHandlerId(), gatewayEntity);
        }
    }

    @Override
    public GatewayEntity getFirstGatewayEntity() {
        for (Map.Entry<Integer, GatewayEntity> entry : map.entrySet()) {
            GatewayEntity value = entry.getValue();
            if(value.getPreHandlerId() == null){
                return value;
            }
        }
        return null;
    }

    @Override
    public GatewayEntity getGatewayEntity(Integer handlerId) {
        return map.get(handlerId);
    }
}

将每个权限控制的具体实现抽象出来,编写GatewayHandler抽象类:

java 复制代码
@Data
public abstract class GatewayHandler {

    // 下一关卡的处理类
    protected GatewayHandler next;

    public String condition;
    
    // 通过传入条件判断对象是否能够处理请求
    public abstract String service(String condition);

    public void setNext(GatewayHandler next){
     this.next = next;
    }
}

每个不同权限控制的具体实现类:

java 复制代码
public class ApiLimitGatewayHandler extends GatewayHandler {

    @Override
    public String service(String condition) {
        System.out.println("第一关->api接口限流");
        if(!this.condition.equals(condition)){
            System.out.println("我是第一关,但我的权限不够,我给下一个节点");
            if(this.next != null){
                return this.next.service(condition);
            }
        }
        System.out.println("我是第一关,我自己能处理");
        return condition;
    }
}

public class BlacklistGatewayHandler extends GatewayHandler {

    @Override
    public String service(String condition) {
        System.out.println("第二关->黑名单列表拦截");
        if(!this.condition.equals(condition)){
            System.out.println("我是第二关,但我的权限不够,我给下一个节点");
            if(this.next != null){
                return this.next.service(condition);
            }
        }
        System.out.println("我是第二关,我自己能处理");
        return condition;
    }
}

public class SessionGatewayHandler extends GatewayHandler {

    @Override
    public String service(String condition) {
        System.out.println("第三关->用户会话拦截");
        if(!this.condition.equals(condition)){
            System.out.println("我是第三关,但我的权限也不够,处理不了,而且我后面没人了");
        }else{
            System.out.println("我是第三关,我可以处理");
        }
        return condition;
    }

}

通过工厂类GatewayHandlerEnumFactory 按顺序连接不同的处理类(权限控制的具体内容)

java 复制代码
public class GatewayHandlerEnumFactory {

    private static GatewayDao gatewayDao = new GatewayImpl();

    // 提供静态方法,获取第一个handler
    public static GatewayHandler getFirstGatewayHandler(){
        GatewayEntity firstGatewayEntity = gatewayDao.getFirstGatewayEntity();
        GatewayHandler firstGatewayHandler = newGatewayHandler(firstGatewayEntity);

        if(firstGatewayHandler == null){
            return null;
        }
        firstGatewayHandler.setCondition(firstGatewayEntity.getCondition());

        GatewayEntity tempGatewayEntity = firstGatewayEntity;
        GatewayHandler tempGatewayHandler = firstGatewayHandler;
        Integer nextHandlerId = null;
        // 迭代遍历所有handler,以及将它们链接起来
        while((nextHandlerId = tempGatewayEntity.getNextHandlerId()) != null){
            GatewayEntity gatewayEntity = gatewayDao.getGatewayEntity(nextHandlerId);
            GatewayHandler gatewayHandler = newGatewayHandler(gatewayEntity);
            gatewayHandler.setCondition(gatewayEntity.getCondition());
            tempGatewayHandler.setNext(gatewayHandler);
            tempGatewayHandler = gatewayHandler;
            tempGatewayEntity = gatewayEntity;
        }
        // 返回第一个handler
        return firstGatewayHandler;
    }

    /**
     * 通过反射实例化具体的处理者
     * @param firstGatewayEntity
     * @return
     */
    public static GatewayHandler newGatewayHandler(GatewayEntity firstGatewayEntity){
        String conference = firstGatewayEntity.getConference();
        try {
            Class<?> clazz = Class.forName(conference);
            return (GatewayHandler) clazz.newInstance();
        } catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {
            e.printStackTrace();
        }
        return null;
    }

}

编写主类,进行测试:

java 复制代码
public class GatewayClient {
    public static void main(String[] args) {
        GatewayHandler gatewayHandler = GatewayHandlerEnumFactory.getFirstGatewayHandler();
        gatewayHandler.service("条件1");
        System.out.println();
        gatewayHandler.service("条件2");
        System.out.println();
        gatewayHandler.service("条件3");
        System.out.println();
        gatewayHandler.service("条件4");
        
    }
}

---输出---
第一关->api接口限流
我是第一关,我自己能处理

第一关->api接口限流
我是第一关,但我的权限不够,我给下一个节点
第二关->黑名单列表拦截
我是第二关,我自己能处理

第一关->api接口限流
我是第一关,但我的权限不够,我给下一个节点
第二关->黑名单列表拦截
我是第二关,但我的权限不够,我给下一个节点
第三关->用户会话拦截
我是第三关,我可以处理

第一关->api接口限流
我是第一关,但我的权限不够,我给下一个节点
第二关->黑名单列表拦截
我是第二关,但我的权限不够,我给下一个节点
第三关->用户会话拦截
我是第三关,但我的权限也不够,处理不了,而且我后面没人了

Process finished with exit code 0
相关推荐
苹果醋37 分钟前
React源码02 - 基础知识 React API 一览
java·运维·spring boot·mysql·nginx
Hello.Reader27 分钟前
深入解析 Apache APISIX
java·apache
菠萝蚊鸭1 小时前
Dhatim FastExcel 读写 Excel 文件
java·excel·fastexcel
旭东怪1 小时前
EasyPoi 使用$fe:模板语法生成Word动态行
java·前端·word
007php0071 小时前
Go语言zero项目部署后启动失败问题分析与解决
java·服务器·网络·python·golang·php·ai编程
∝请叫*我简单先生1 小时前
java如何使用poi-tl在word模板里渲染多张图片
java·后端·poi-tl
ssr——ssss1 小时前
SSM-期末项目 - 基于SSM的宠物信息管理系统
java·ssm
一棵星1 小时前
Java模拟Mqtt客户端连接Mqtt Broker
java·开发语言
鲤籽鲲2 小时前
C# Random 随机数 全面解析
android·java·c#
zquwei2 小时前
SpringCloudGateway+Nacos注册与转发Netty+WebSocket
java·网络·分布式·后端·websocket·网络协议·spring