JAVA设计模式之责任链设计模式

Java基础

浏览数:36

2020-6-13

AD:资源代下载服务

责任链模式:责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为型模式。

在这种模式中,通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。

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

何时使用:在处理消息的时候以过滤很多道。

如何解决:拦截的类都实现统一接口。

关键代码:Handler 里面聚合它自己,在 HandlerRequest 里判断是否合适,如果没达到条件则向下传递,向谁传递之前 set 进去。

应用实例: 1、红楼梦中的”击鼓传花”。 2、JS 中的事件冒泡。 3、JAVA WEB 中 Apache Tomcat 对 Encoding 的处理,Struts2 的拦截器,jsp servlet 的 Filter。

优点: 1、降低耦合度。它将请求的发送者和接收者解耦。 2、简化了对象。使得对象不需要知道链的结构。 3、增强给对象指派职责的灵活性。通过改变链内的成员或者调动它们的次序,允许动态地新增或者删除责任。 4、增加新的请求处理类很方便。

缺点: 1、不能保证请求一定被接收。 2、系统性能将受到一定影响,而且在进行代码调试时不太方便,可能会造成循环调用。 3、可能不容易观察运行时的特征,有碍于除错。

废话不多说,直接上手:
项目背景:网关作为微服务项目入口,拦截客户端所有的请求实现权限控制,如判断API接口限流->黑名单拦截->用户会话->参数过滤

环境准备:JDK8、springboot2.0.x、idea工具、mysql数据库…
整体流程可以缩概为如下图所示:

1、定义抽象的handler接口

/**
 * 网关拦截器
 * @author zhoumin
 * @create 2020-03-14 13:20
 */
public abstract class GatewayHandler {

    /**
     * 使用抽象类定义共同的行为方法
     */
    public abstract void service();
}

2、定义具体实现事项

/**
 * @author zhoumin
 * @create 2020-03-14 13:25
 */
public class ApiLimitHandler extends GatewayHandler {
    //这里需要指定下一个handler
    private BlacklistHandler blacklistHandler;

    /**
     * 使用抽象类定义共同的行为方法
     */
    @Override
    public void service() {
        System.out.println("1、api接口限流.........");
        //下一个handler
        blacklistHandler.service();
    }

    //指定下一个handler
    public void setNextGatewayHandler(BlacklistHandler blacklistHandler) {
        this.blacklistHandler = blacklistHandler;
    }
}
/**
 * 黑名单拦截
 *
 * @author zhoumin
 * @create 2020-03-14 13:26
 */
public class BlacklistHandler extends GatewayHandler {
    //这里需要指定下一个handler
    private ConversationHandler conversationHandler;

    /**
     * 使用抽象类定义共同的行为方法
     */
    @Override
    public void service() {
        System.out.println("2、黑名单拦截.........");
        //下一个handler
        conversationHandler.service();
    }

    //指定下一个handler
    public void setNextGatewayHandler(ConversationHandler conversationHandler) {
        this.conversationHandler = conversationHandler;
    }
}
/**
 * 用户会话拦截
 *
 * @author zhoumin
 * @create 2020-03-14 13:27
 */
public class ConversationHandler extends GatewayHandler {
    //这里需要指定下一个handler
    private ParamHandler paramHandler;

    /**
     * 使用抽象类定义共同的行为方法
     */
    @Override
    public void service() {
        System.out.println("3、用户会话拦截.........");
        //下一个handler
        paramHandler.service();
    }

    //指定下一个handler
    public void setNextGatewayHandler(ParamHandler paramHandler) {
        this.paramHandler = paramHandler;
    }
}
/**
 * 参数拦截
 *
 * @author zhoumin
 * @create 2020-03-14 13:30
 */
public class ParamHandler extends GatewayHandler {
    /**
     * 使用抽象类定义共同的行为方法
     */
    @Override
    public void service() {
        System.out.println("4、用户参数过滤.........");
    }
}

注意:需要在每个具体handler里面执行下一个需要执行的handler,直到最后一个

这个时候发现,只要获取第一个handler后,并执行,那么整个链路就能顺利完成,那么怎么获取第一个handler呢?答案是:使用工厂模型

/**
 * 工厂创建对象
 *
 * @author zhoumin
 * @create 2020-03-14 13:49
 */
public class FactoryHandler {

    //这里static 为了方便后面调试运行
    public static ApiLimitHandler getFirstGatewayHandler(){
        ApiLimitHandler apiLimitHandler = new ApiLimitHandler();
        BlacklistHandler blacklistHandler = new BlacklistHandler();
        apiLimitHandler.setNextGatewayHandler(blacklistHandler);
        ConversationHandler conversationHandler = new ConversationHandler();
        blacklistHandler.setNextGatewayHandler(conversationHandler);
        ParamHandler paramHandler = new ParamHandler();
        conversationHandler.setNextGatewayHandler(paramHandler);
        return apiLimitHandler;
    }
}

至此,完成了整个流程,现在可以来简单测试下

/**
 * @author zhoumin
 * @create 2020-03-14 14:49
 */
@RestController
public class HandlerController {

    @GetMapping("/clientHandler")
    public String clientHandler(){
        ApiLimitHandler apiLimitHandler = FactoryHandler.getFirstGatewayHandler();
        apiLimitHandler.service();
        return "success!!";
    }
}

运行后可以看到控制台打印数据:

断点调试可以清晰看到整个链路结构:

我是一个分割线emmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm…..

美中不足,我们发现代码有很多冗余,包括setNextGatewayHandler以及出现在每个handler中的下个handler,那么如何优化呢?————->将公共方法抽取到父类中!

/**
 * 网关拦截器
 * @author zhoumin
 * @create 2020-03-14 13:20
 */
public abstract class GatewayHandler {
    protected GatewayHandler nextGatewayHandler;

    /**
     * 使用抽象类定义共同的行为方法
     */
    public abstract void service();

    //设置下一个handler
    public void setNextGatewayHandler(GatewayHandler gatewayHandler){
        this.nextGatewayHandler = gatewayHandler;
    }

    //执行下一个handler
    protected void nextService(){
        if (nextGatewayHandler != null){
            nextGatewayHandler.service();
        }
    }
}

子类优化:

/**
 * @author zhoumin
 * @create 2020-03-14 13:25
 */
public class ApiLimitHandler extends GatewayHandler {
    //这里需要指定下一个handler
//    private BlacklistHandler blacklistHandler;

    /**
     * 使用抽象类定义共同的行为方法
     */
    @Override
    public void service() {
        System.out.println("1、api接口限流.........");
        //下一个handler
//        blacklistHandler.service();
        nextService();
    }

    //指定下一个handler
//    public void setNextGatewayHandler(BlacklistHandler blacklistHandler) {
//        this.blacklistHandler = blacklistHandler;
//    }
}
/**
 * 黑名单拦截
 *
 * @author zhoumin
 * @create 2020-03-14 13:26
 */
public class BlacklistHandler extends GatewayHandler {
    //这里需要指定下一个handler
//    private ConversationHandler conversationHandler;

    /**
     * 使用抽象类定义共同的行为方法
     */
    @Override
    public void service() {
        System.out.println("2、黑名单拦截.........");
        //下一个handler
//        conversationHandler.service();
        nextService();
    }

    //指定下一个handler
//    public void setNextGatewayHandler(ConversationHandler conversationHandler) {
//        this.conversationHandler = conversationHandler;
//    }
}
/**
 * 用户会话拦截
 *
 * @author zhoumin
 * @create 2020-03-14 13:27
 */
public class ConversationHandler extends GatewayHandler {
    //这里需要指定下一个handler
//    private ParamHandler paramHandler;

    /**
     * 使用抽象类定义共同的行为方法
     */
    @Override
    public void service() {
        System.out.println("3、用户会话拦截.........");
        //下一个handler
//        paramHandler.service();
        nextService();
    }

    //指定下一个handler
//    public void setNextGatewayHandler(ParamHandler paramHandler) {
//        this.paramHandler = paramHandler;
//    }
}
/**
 * 参数拦截
 *
 * @author zhoumin
 * @create 2020-03-14 13:30
 */
public class ParamHandler extends GatewayHandler {
    /**
     * 使用抽象类定义共同的行为方法
     */
    @Override
    public void service() {
        System.out.println("4、用户参数过滤.........");
    }
}

运行结果跟上图保持一致。

至此,结束了吗?显然没有,继续…..

可以看到我们在Factory中定义的关系强耦合,如果现在我们需要改变顺序或者是新增其他handler,需要改动代码,可扩展性较差。

结合上一章,启示可以把beanId关系维护到数据库中,利用spring容器,根据beanId获取到每个具体的实例对象(注:需要将上面每一个具体handler注入到spring容器中)。

现有数据结构和数据关系如下:

可以很清晰看到,第一个handler的prev为null,next为我们指定的handler;而最后一个handler的next为null。

定义我们的实体类以及mapper如下:

/**
 * @author zhoumin
 * @create 2020-03-14 15:35
 */
@Data
public class GatewayHandlerEntity {
    /** 主键ID */
    private Integer id;

    /** handler名称 */
    private String handlerName;

    /** handler主键id */
    private String handlerId;

    /** 下一个handler */
    private String nextHandlerId;

}
/**
 * @author zhoumin
 * @create 2020-03-14 15:33
 */
@Mapper
public interface GatewayHandlerMapper {
    /**
     * 获取第一个handler
     * @return
     */
    GatewayHandlerEntity getFirstGatewayHandler();

    /**
     * 根据beanId获取当前handler
     * @return
     */
    GatewayHandlerEntity getByHandler(String handlerId);

}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zm.test.mapper.GatewayHandlerMapper">
    <select id="getFirstGatewayHandler" resultType="com.zm.test.entity.GatewayHandlerEntity">
        SELECT
            handler_name AS handlerName,
            handler_id AS handlerid ,
            prev_handler_id AS prevhandlerid ,
            next_handler_id AS nexthandlerid
        FROM gateway_handler
        WHERE  prev_handler_id is null
    </select>

    <select id="getByHandler" resultType="com.zm.test.entity.GatewayHandlerEntity">
        SELECT
            handler_name AS handlerName,
            handler_id AS handlerid ,
            prev_handler_id AS prevhandlerid ,
            next_handler_id AS nexthandlerid
        FROM gateway_handler
        WHERE  handler_id=#{handlerId}
    </select>
</mapper>

对应的service可以处理为:

/**
 * @author zhoumin
 * @create 2020-03-14 15:38
 */
@Component
public class GatewayHandlerService {
    @Resource
    private GatewayHandlerMapper gatewayHandlerMapper;

    private GatewayHandler firstGatewayHandler;

    public GatewayHandler getDbFirstGatewayHandler() {
        //判断是否加载过
        if (this.firstGatewayHandler != null) {
            return this.firstGatewayHandler;
        }
        // 1.查询数据库第一个handler配置
        GatewayHandlerEntity firstGatewayHandlerEntity = gatewayHandlerMapper.getFirstGatewayHandler();
        if (firstGatewayHandlerEntity == null) {
            return null;
        }
        String firstBeanHandlerId = firstGatewayHandlerEntity.getHandlerId();
        if (StringUtils.isEmpty(firstBeanHandlerId)) {
            return null;
        }
        // 2.根据beanId,从SpringBoot容器获取第一个handler对象
        GatewayHandler firstGatewayHandler = SpringUtils.getBean(firstBeanHandlerId, GatewayHandler.class);
        if (firstGatewayHandler == null) {
            return null;
        }
        // 3. 获取下一个handlerBeanId
        String nextBeanHandlerId = firstGatewayHandlerEntity.getNextHandlerId();
        // 定义临时循环遍历指针
        GatewayHandler tempNextGatewayHandler = firstGatewayHandler;
        while (StringUtils.isNotEmpty(nextBeanHandlerId)) {
            // 4.根据beanId,从SpringBoot容器获取下一个handler对象
            GatewayHandler nextGatewayHandler = SpringUtils.getBean(nextBeanHandlerId, GatewayHandler.class);
            if (nextGatewayHandler == null) {
                break;
            }
            // 5.从数据库查询该hanlder信息,从而获取下一个beanId
            GatewayHandlerEntity nextGatewayHandlerEntity = gatewayHandlerMapper.getByHandler(nextBeanHandlerId);
            if (nextGatewayHandlerEntity == null) {
                break;
            }
            // 6.设置下一个white循环遍历hanlderid
            nextBeanHandlerId = nextGatewayHandlerEntity.getNextHandlerId();
            tempNextGatewayHandler.setNextGatewayHandler(nextGatewayHandler);
            tempNextGatewayHandler = nextGatewayHandler;
        }

        //设置只在启动时加载查询一次
        this.firstGatewayHandler = firstGatewayHandler;
        return firstGatewayHandler;
    }
}

相应,做一次测试:

/**
 * @author zhoumin
 * @create 2020-03-14 14:49
 */
@RestController
public class HandlerController {
    @Autowired
    private GatewayHandlerService gatewayHandlerService;

    @GetMapping("/clientHandler")
    public String clientHandler(){
        ApiLimitHandler apiLimitHandler = FactoryHandler.getFirstGatewayHandler();
        apiLimitHandler.service();
        return "success!!";
    }

    @GetMapping("/clientHandler2")
    public String clientHandler2(){
        GatewayHandler gatewayHandler = gatewayHandlerService.getDbFirstGatewayHandler();
        gatewayHandler.service();
        return "success!!";
    }
}

运行后,数据如下:

如果想调节顺序,或者是新增节点,只需要修改数据库即可,相应,我们也可以把这块放到我们自己的管理后台管理。

在常用代码中,过滤器Filter就是这一模型很好的应用实例,具体可以看下:

doFilter即类似我们这里的nextService()方法。

完~

作者:盘子