参数过滤 责任链模式 :责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为型模式。
在这种模式中,通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。
主要解决:职责链上的处理者负责处理请求,客户只需要将请求发送到职责链上即可,无须关心请求的处理细节和请求的传递,所以职责链将请求的发送者和请求的处理者解耦了。
何时使用:在处理消息的时候以过滤很多道。
如何解决:拦截的类都实现统一接口。
关键代码:Handler 里面聚合它自己,在 HandlerRequest 里判断是否合适,如果没达到条件则向下传递,向谁传递之前 set 进去。
应用实例:1、红楼梦中的"击鼓传花"。 2、JS 中的事件冒泡。 3、JAVA WEB 中 Apache Tomcat 对 Encoding 的处理,Struts2 的拦截器,jsp servlet 的 Filter。
废话不多说,直接上手:
项目背景:网关作为微服务项目入口,拦截客户端所有的请求实现权限控制,如判断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()方法。
完~