责任链模式:责任链模式(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()方法。
完~
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。