折腾Java设计模式之责任链模式

责任链模式

顾名思义,责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为型模式。在这种模式中,通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。

简介

意图 避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。

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

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

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

关键代码 Handler 里面聚合它自己,在 handleRequest 里判断是否合适,如果没达到条件则向下传递。

纯责任链与不纯责任链

  • 纯:纯责任链中的节点只有两种行为,一处理责任,二将责任传递到下一个节点。不允许出现某一个节点处理部分或全部责任后又将责任向下传递的情况。

  • 不纯:允许某个请求被一个节点处理部分责任后再向下传递,或者处理完后其后续节点可以继续处理该责任,而且一个责任可以最终不被任何节点所处理。

主要角色

  • Handler(抽象处理者): 定义一个处理请求的接口,提供对后续处理者的引用
  • ConcreteHandler(具体处理者): 抽象处理者的子类,处理用户请求,可选将请求处理掉还是传给下家;在具体处理者中可以访问链中下一个对象,以便请求的转发

应用实例

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

优点

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

缺点

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

使用场景

1、有多个对象可以处理同一个请求,具体哪个对象处理该请求由运行时刻自动确定。
2、在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。
3、可动态指定一组对象处理请求。

Github项目描述

跳转到我的责任链设计模式源码

1.出行方式

travel包里主要对出行方式的责任链模式。跟进用户身上的钱,在优先级如飞机->火车->大巴的顺序下选择对应的出行模式。


public class Application {

public static void main(String[] args) {

Handler planeHandler = new PlaneHandler();

Handler trainHandler = new TrainHandler();

Handler busHandler = new BusHandler();

planeHandler.setNext(trainHandler);

trainHandler.setNext(busHandler);

planeHandler.handleRequest("老王", 40d);

planeHandler.handleRequest("张三", 140d);

planeHandler.handleRequest("李四", 240d);

planeHandler.handleRequest("吴老五", 340d);

}

}

img

抽象处理


@Data

public abstract class Handler {

/**

* 下一个链节点

*/

protected Handler next;

public abstract void handleRequest(String name, Double wallet);

}

具体的处理者(飞机、火车、大巴)


@Slf4j

public class PlaneHandler extends Handler {

private double price = 280d;

@Override

public void handleRequest(String name, Double wallet) {

if (price <= wallet) {

log.info("{}身上的钱可以坐飞机。", name);

return;

}

if (Objects.nonNull(next)) {

next.handleRequest(name, wallet);

return;

}

log.info("{}钱不够,只能徒步啦", name);

}

}


@Slf4j

public class TrainHandler extends Handler {

private double price = 149.99d;

@Override

public void handleRequest(String name, Double wallet) {

if (price <= wallet) {

log.info("{}身上的钱只能坐火车。", name);

return;

}

if (Objects.nonNull(next)) {

next.handleRequest(name, wallet);

return;

}

log.info("{}钱不够,只能徒步啦", name);

}

}


@Slf4j

public class BusHandler extends Handler {

private double price = 59.99d;

@Override

public void handleRequest(String name, Double wallet) {

if (price <= wallet) {

log.info("{}身上的钱只能坐大巴。", name);

return;

}

if (Objects.nonNull(next)) {

next.handleRequest(name, wallet);

return;

}

log.info("{}钱不够,只能徒步啦", name);

}

}

2.出行方式2,参考Filter链的写法

travel2包是对travel包的重新写法。


public class Application {

public static void main(String[] args) {

HandlerChain chain = new HandlerChain();

Handler planeHandler = new PlaneHandler();

Handler trainHandler = new TrainHandler();

Handler busHandler = new BusHandler();

chain.addHandler(planeHandler);

chain.addHandler(trainHandler);

chain.addHandler(busHandler);

chain.handle("老王", 40d);

chain.handle("张三", 140d);

chain.handle("李四", 240d);

chain.handle("吴老五", 340d);

}

}

image-20181223222027677

抽象处理者


public interface Handler {

void handleRequest(String name, Double wallet, HandlerChain chain);

}

具体处理者(飞机、火车、大巴)


@Slf4j

public class PlaneHandler implements Handler {

private double price = 280d;

@Override

public void handleRequest(String name, Double wallet, HandlerChain chain) {

if (price <= wallet) {

log.info("{}身上的钱可以坐飞机。", name);

chain.reuse();

return;

}

chain.handle(name, wallet);

}

}


@Slf4j

public class TrainHandler implements Handler {

private double price = 149.99d;

@Override

public void handleRequest(String name, Double wallet, HandlerChain chain) {

if (price <= wallet) {

log.info("{}身上的钱只能坐火车。", name);

chain.reuse();

return;

}

chain.handle(name, wallet);

}

}


@Slf4j

public class BusHandler implements Handler {

private double price = 59.99d;

@Override

public void handleRequest(String name, Double wallet, HandlerChain chain) {

if (price <= wallet) {

log.info("{}身上的钱只能坐大巴。", name);

chain.reuse();

return;

}

chain.handle(name, wallet);

}

}

责任链管理者


@Slf4j

public class HandlerChain {

private List handlerList = new ArrayList<>();

/**

* 维护当前链上位置

*/

private int pos;

/**

* 链的长度

*/

private int handlerLength;

public void addHandler(Handler handler) {

handlerList.add(handler);

handlerLength = handlerList.size();

}

public void handle(String name, double wallet) {

if (CollectionUtils.isEmpty(handlerList)) {

log.error("有钱,但没提供服务,{}也估计就只能步行了。", name);

return;

}

if (pos >= handlerLength) {

log.error("身上钱不够,{}也估计就只能步行了。", name);

reuse();

return;

}

Handler handler = handlerList.get(pos++);

if (Objects.isNull(handler)) {

log.error("假服务,{}也估计就只能步行了。", name);

reuse();

return;

}

handler.handleRequest(name, wallet, this);

}

/**

* 链重新使用

*/

public void reuse() {

pos = 0;

}

}

学习Web项目的Filter

待补充…

补充补充遗留的Filter过滤器中的责任链处理。

本次主要是对Tomcat中的Filter处理简单的梳理,如有不正确的地方,还望指出来,大家互勉,共进。

老项目大家可以在web.xml中配置filter,现使用Springboot后,也有两种配置filter方式,通过创建FilterRegistrationBean的方式和通过注解@WebFilter+@ServletComponentScan的方式。

三个主要的角色

FIlter,不多介绍了。

FilterChain servlet容器提供的开发调用链的过滤请求的资源。通过调用下一个filter实现过滤,在整体链上。

FilterConfig filter的配置器,在servlet容器在Filter初始化的时候传递信息。

具体的filter,主要说说Spring中的两个抽象Filter,GenericFilterBean和OncePerRequestFilter。

前者主要是做init和destroy的操作,重点还是init方法,destroy只是空实现而已。

后者主要是做真正的doFilter操作,也是我们在Spring中创建Filter通常继承的。

而ApplicationFilterChain就算Tomcat中的FilterChain实现。


/**

* The int which is used to maintain the current position

* in the filter chain.

*/

private int pos = 0;

/**

* The int which gives the current number of filters in the chain.

*/

private int n = 0;

@Override

public void doFilter(ServletRequest request, ServletResponse response)

throws IOException, ServletException {

//安全相关的,暂不关注

if( Globals.IS_SECURITY_ENABLED ) {

final ServletRequest req = request;

final ServletResponse res = response;

try {

java.security.AccessController.doPrivileged(

new java.security.PrivilegedExceptionAction() {

@Override

public Void run()

throws ServletException, IOException {

internalDoFilter(req,res);

return null;

}

}

);

} catch( PrivilegedActionException pe) {

Exception e = pe.getException();

if (e instanceof ServletException)

throw (ServletException) e;

else if (e instanceof IOException)

throw (IOException) e;

else if (e instanceof RuntimeException)

throw (RuntimeException) e;

else

throw new ServletException(e.getMessage(), e);

}

} else {

//真正的doFilter

internalDoFilter(request,response);

}

}

private void internalDoFilter(ServletRequest request,

ServletResponse response)

throws IOException, ServletException {

//pos 调用链中当前连接点所在的位置

//n 调用链总节点长度

// Call the next filter if there is one

if (pos < n) {

//对节点进行自增 pos++

ApplicationFilterConfig filterConfig = filters[pos++];

try {

//当前节点小于总长度后,从filter配置类中取出filter

Filter filter = filterConfig.getFilter();

if (request.isAsyncSupported() && "false”.equalsIgnoreCase(

filterConfig.getFilterDef().getAsyncSupported())) {

request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR, Boolean.FALSE);

}

if( Globals.IS_SECURITY_ENABLED ) {

final ServletRequest req = request;

final ServletResponse res = response;

Principal principal =

((HttpServletRequest) req).getUserPrincipal();

Object[] args = new Object[]{req, res, this};

SecurityUtil.doAsPrivilege ("doFilter", filter, classType, args, principal);

} else {

//真正的filter

filter.doFilter(request, response, this);

}

} catch (IOException | ServletException | RuntimeException e) {

throw e;

} catch (Throwable e) {

e = ExceptionUtils.unwrapInvocationTargetException(e);

ExceptionUtils.handleThrowable(e);

throw new ServletException(sm.getString("filterChain.filter"), e);

}

return;

}

// We fell off the end of the chain -- call the servlet instance

//到了调用链结尾处,就真正调用servlet实例的servlet.service(request, response);

try {

if (ApplicationDispatcher.WRAP_SAME_OBJECT) {

lastServicedRequest.set(request);

lastServicedResponse.set(response);

}

if (request.isAsyncSupported() && !servletSupportsAsync) {

request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR,

Boolean.FALSE);

}

// Use potentially wrapped request from this point

if ((request instanceof HttpServletRequest) &&

(response instanceof HttpServletResponse) &&

Globals.IS_SECURITY_ENABLED ) {

final ServletRequest req = request;

final ServletResponse res = response;

Principal principal =

((HttpServletRequest) req).getUserPrincipal();

Object[] args = new Object[]{req, res};

SecurityUtil.doAsPrivilege(“service”,

servlet,

classTypeUsedInService,

args,

principal);

} else {

servlet.service(request, response);

}

} catch (IOException | ServletException | RuntimeException e) {

throw e;

} catch (Throwable e) {

e = ExceptionUtils.unwrapInvocationTargetException(e);

ExceptionUtils.handleThrowable(e);

throw new ServletException(sm.getString("filterChain.servlet"), e);

} finally {

if (ApplicationDispatcher.WRAP_SAME_OBJECT) {

lastServicedRequest.set(null);

lastServicedResponse.set(null);

}

}

}

/**

* Prepare for reuse of the filters and wrapper executed by this chain.

* 重复使用filter调用链,pos重设为0

*/

void reuse() {

pos = 0;

}

重点从ApplicationFilterChain中挑出几个重要的方法拿出来分析下Filter的调用链,其实还有几处没有具体讲到,ApplicationFilterChain是合适创建的,Filter是怎么加入到ApplicationFilterChain中的。这涉及到Tomcat是怎样加载Content的,下次分析Tomcat的时候,再来具体分析,它是如何运作的,如何加载web.xml。

参考

维基的责任链模式

责任链模式|菜鸟教程

Filter、FilterConfig、FilterChain|菜鸟教程

南乡清水的实际项目运用之Responsibility-Chain模式

一起学设计模式 - 责任链模式

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 195,980评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,422评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,130评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,553评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,408评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,326评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,720评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,373评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,678评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,722评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,486评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,335评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,738评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,283评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,692评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,893评论 2 335