1. 简介
责任链模式(Chain of Responsibility):使多个对象
都有机会处理请求,从而避免了请求的发送者
和接受者
之间的耦合
关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有对象处理它为止。
2. 图解
商城新开张,每个订单,可以享受多张优惠券叠加减免
责任链模式
3. 案例实现
类图如下
- 定义一个优惠券打折
抽象类
; - 抽象类包含一个指向自身的引用
nextDiscountFilter
,用来把对象串成链,原价计算优惠后的价格方法calculateBySourcePrice
;
实现类
FullDistcountFliter
满200减20元;FirstPurchaseDiscount
首次购买减20元;SecondPurchaseDiscountFilter
第二件打9折;HolidayDiscountFilter
节日一律减5元.
接口与实现类
package com.wzj.chainOfResponsibility.example2;
/**
* @Author: wzj
* @Date: 2019/9/8 9:06
* @Desc: 折扣优惠接口
*/
public abstract class DiscountFilter {
// 下一个责任链成员
protected DiscountFilter nextDiscountFilter;
// 根据原价计算优惠后的价格
public abstract int calculateBySourcePrice(int price);
}
package com.wzj.chainOfResponsibility.example2;
/**
* @Author: wzj
* @Date: 2019/9/8 9:07
* @Desc: 满200减20元
*/
public class FullDiscountFilter extends DiscountFilter{
public int calculateBySourcePrice(int price) {
if (price > 200){
System.out.println("优惠满减20元");
price = price - 20;
}
if(this.nextDiscountFilter != null) {
return super.nextDiscountFilter.calculateBySourcePrice(price);
}
return price;
}
}
package com.wzj.chainOfResponsibility.example2;
/**
* @Author: wzj
* @Date: 2019/9/8 16:06
* @Desc: 首次购买减20元
*/
public class FirstPurchaseDiscount extends DiscountFilter {
public int calculateBySourcePrice(int price) {
if (price > 100){
System.out.println("首次购买减20元");
price = price - 20;
}
if(this.nextDiscountFilter != null) {
return super.nextDiscountFilter.calculateBySourcePrice(price);
}
return price;
}
}
package com.wzj.chainOfResponsibility.example2;
/**
* @Author: wzj
* @Date: 2019/9/8 16:09
* @Desc: 第二件打9折
*/
public class SecondPurchaseDiscountFilter extends DiscountFilter{
public int calculateBySourcePrice(int price) {
System.out.println("第二件打9折");
Double balance = price * 0.9;
if(this.nextDiscountFilter != null) {
return super.nextDiscountFilter.calculateBySourcePrice(balance.intValue());
}
return price;
}
}
package com.wzj.chainOfResponsibility.example2;
/**
* @Author: wzj
* @Date: 2019/9/8 16:02
* @Desc: 节日一律减5元
*/
public class HolidayDiscountFilter extends DiscountFilter{
public int calculateBySourcePrice(int price) {
if (price > 20){
System.out.println("节日一律减5元");
price = price - 5;
}
if(this.nextDiscountFilter != null) {
return super.nextDiscountFilter.calculateBySourcePrice(price);
}
return price;
}
}
测试类
package com.wzj.chainOfResponsibility.example2;
/**
* @Author: wzj
* @Date: 2019/9/8 16:17
* @Desc:
*/
public class TestDiscountFilter {
public static void main(String[] args) {
int price = 240;
String productStr = String.format("商品清单:苹果、香蕉、桔子, 商品总金额为:%d元.", price);
System.out.println(productStr);
//声明责任链上的所有节点
FullDiscountFilter fulDF = new FullDiscountFilter();
FirstPurchaseDiscount firstDF = new FirstPurchaseDiscount();
SecondPurchaseDiscountFilter secDF = new SecondPurchaseDiscountFilter();
HolidayDiscountFilter holDF = new HolidayDiscountFilter();
//设置链中的顺序:满减->首购减->第二件减->假日减
fulDF.nextDiscountFilter = firstDF;
firstDF.nextDiscountFilter = secDF;
secDF.nextDiscountFilter = holDF;
holDF.nextDiscountFilter = null;
int total = fulDF.calculateBySourcePrice(price);
System.out.println(String.format("所有商品优惠价后金额为:%d", total));
}
}
执行结果
商品清单:苹果、香蕉、桔子, 商品总金额为:240元.
优惠满减20元
首次购买减20元
第二件打9折
节日一律减5元
所有商品优惠价后金额为:175
4. 应用责任链模式手写过滤器
现在有这样一个场景,程序员张三在某相亲节目中找对象,在第一关和第二关分别被女孩灭灯pass掉,下面分别看看俩姑娘是如何过滤张三的。
小美对话张三
小静对话张三
此场景可以模拟一个http请求如何经过过滤器到服务端,经过每一个过滤器,可以想象为张三相亲过程中经过某个关卡,由于不符合该过滤器的条件被姑娘过滤掉。
第一个版本v1
- 定义一个请求类和一个响应类
MyRequest
、MyResponse
; - 定义过滤器
MyFilter
; - 定义两个过滤器的实现类
HeightFliter
、EducationalBackGroundFilter
,分别实现身高过滤和学历过滤; - 定义链条
MyFilterChain
,实现接口MyFilter
,add
方法实现往过滤链条里添加过滤对象;doFilter
方法实现所有过滤对象的过滤操作;
实现代码如下
package com.wzj.chainOfResponsibility.example1.v1;
/**
* @Author: wzj
* @Date: 2019/9/7 20:14
* @Desc: 请求
*/
public class MyRequest {
String str;
}
package com.wzj.chainOfResponsibility.example1.v1;
/**
* @Author: wzj
* @Date: 2019/9/7 20:15
* @Desc: 响应
*/
public class MyResponse {
String str;
}
package com.wzj.chainOfResponsibility.example1.v1;
/**
* @Author: wzj
* @Date: 2019/9/7 20:00
* @Desc: 模拟实现过滤器
*/
public interface MyFilter {
void doFilter(MyRequest myRequest, MyResponse myResponse);
}
package com.wzj.chainOfResponsibility.example1.v1;
import java.util.ArrayList;
import java.util.List;
/**
* @Author: wzj
* @Date: 2019/9/7 20:36
* @Desc: 责任链
*/
public class MyFilterChain implements MyFilter {
List list = new ArrayList();
public MyFilterChain add(MyFilter myFilter) {
list.add(myFilter);
return this;
}
public void doFilter(MyRequest myRequest, MyResponse myResponse) {
for(MyFilter f : list ){
f.doFilter(myRequest, myResponse);
}
}
}
package com.wzj.chainOfResponsibility.example1.v1;
/**
* @Author: wzj
* @Date: 2019/9/7 20:20
* @Desc: 身高过滤器
*/
public class HeightFliter implements MyFilter {
public void doFilter(MyRequest myRequest, MyResponse myResponse) {
myRequest.str = myRequest.str.replace("170", "个子有点矮");
myResponse.str += "【妹子挑剔,需要过滤身高】";
}
}
package com.wzj.chainOfResponsibility.example1.v1;
/**
* @Author: wzj
* @Date: 2019/9/7 20:33
* @Desc: 教育背景过滤器
*/
public class EducationalBackGroundFilter implements MyFilter {
public void doFilter(MyRequest myRequest, MyResponse myResponse) {
myRequest.str = myRequest.str.replace("学历大专", "学历不高");
myResponse.str += "【妹子挑剔,需要过滤学历】";
}
}
测试类
package com.wzj.chainOfResponsibility.example1.v1;
/**
* @Author: wzj
* @Date: 2019/9/7 20:42
* @Desc: 直观的方式处理response,
* 并将response的处理放在request的下面
*/
public class TestV1 {
public static void main(String[] args) {
MyRequest myRequest = new MyRequest();
myRequest.str = "张三身高170,学历大专,跪求妹子给个机会认识";
System.out.println("request:" + myRequest.str);
MyResponse myResponse = new MyResponse();
myResponse.str = "";
MyFilterChain chain = new MyFilterChain();
chain.add(new HeightFliter()).add(new EducationalBackGroundFilter());
chain.doFilter(myRequest, myResponse);
System.out.println("response:" + myResponse.str);
}
}
结果
request:张三身高170,学历大专,跪求妹子给个机会认识
response:【妹子挑剔,需要过滤身高】【妹子挑剔,需要过滤学历】
现在有如下需求,为更好的模拟一次完整请求,在过滤请求时顺序,响应请求时逆序
,有何办法可以做到呢?
第二个版本v2
MyRequest
类和MyResponse
同v1- 在
MyFilterChain
中加入记录具体责任链对象的下标index
,实现记录位置功能; doFilter
方法里面实现递归调用,并把当前的链条MyFilterChain
作为参数一直向后传递;
具体实现如下
package com.wzj.chainOfResponsibility.example1.v2;
/**
* @Author: wzj
* @Date: 2019/9/7 20:00
* @Desc: 模拟实现过滤器
*/
public interface MyFilter {
void doFilter(MyRequest myRequest, MyResponse myResponse, MyFilterChain myFilterChain);
}
package com.wzj.chainOfResponsibility.example1.v2;
import java.util.ArrayList;
import java.util.List;
/**
* @Author: wzj
* @Date: 2019/9/7 20:36
* @Desc: 在MyFilterChain中处理加入位置的记录index
*/
public class MyFilterChain implements MyFilter {
List list = new ArrayList();
int index = 0;
public MyFilterChain add(MyFilter myFilter) {
list.add(myFilter);
return this;
}
public void doFilter(MyRequest myRequest, MyResponse myResponse, MyFilterChain myFilterChain) {
if(index == list.size())
return;
MyFilter myFilter = list.get(index);
index ++;
myFilter.doFilter(myRequest, myResponse, myFilterChain);
}
}
package com.wzj.chainOfResponsibility.example1.v2;
/**
* @Author: wzj
* @Date: 2019/9/7 20:20
* @Desc: 身高过滤器
*/
public class HeightFliter implements MyFilter {
public void doFilter(MyRequest myRequest, MyResponse myResponse, MyFilterChain myFilterChain) {
myRequest.str = myRequest.str.replace("170", "个子有点矮");
myFilterChain.doFilter(myRequest, myResponse, myFilterChain);
myResponse.str += "【妹子挑剔,需要过滤身高】";
}
}
package com.wzj.chainOfResponsibility.example1.v2;
/**
* @Author: wzj
* @Date: 2019/9/7 20:33
* @Desc: 教育背景过滤器
*/
public class EducationalBackGroundFilter implements MyFilter {
public void doFilter(MyRequest myRequest, MyResponse myResponse, MyFilterChain myFilterChain) {
myRequest.str = myRequest.str.replace("学历大专", "学历不高");
myFilterChain.doFilter(myRequest, myResponse, myFilterChain);
myResponse.str += "【妹子挑剔,需要过滤学历】";
}
}
测试类
package com.wzj.chainOfResponsibility.example1.v2;
/**
* @Author: wzj
* @Date: 2019/9/7 20:42
* @Desc: 过滤请求时顺序,响应请求时逆序,在MyFilterChain中处理加入位置的记录,
* 同时在MyFilter中加入第三个参数MyFilterChain,让链条递归实现倒序
*/
public class TestV2 {
public static void main(String[] args) {
MyRequest myRequest = new MyRequest();
myRequest.str = "张三身高170,学历大专,跪求妹子给个机会认识";
System.out.println("request:" + myRequest.str);
MyResponse myResponse = new MyResponse();
myResponse.str = "";
MyFilterChain chain = new MyFilterChain();
chain.add(new HeightFliter()).add(new EducationalBackGroundFilter());
chain.doFilter(myRequest, myResponse, chain);
System.out.println("response:" + myResponse.str);
}
}
结果中显示先过滤学历,后过滤身高。
request:张三身高170,学历大专,跪求妹子给个机会认识
response:【妹子挑剔,需要过滤学历】【妹子挑剔,需要过滤身高】
由于MyFilterChain
在做doFilter
时,始终传递的是当前MyFilterChain
的同一个实例,故可以简化MyFilterChain
。
第三个版本v3
MyFilterChain
去除MyFilter
接口;doFilter
方法去除第三个参数MyFilterChain
;
具体实现如下
package com.wzj.chainOfResponsibility.example1.v3;
import java.util.ArrayList;
import java.util.List;
/**
* @Author: wzj
* @Date: 2019/9/7 20:36
* @Desc: 过滤器完全模式,去掉实现接口,并将doFilter方法中的chain
*/
public class MyFilterChain{
List list = new ArrayList();
int index = 0;
public MyFilterChain add(MyFilter myFilter) {
list.add(myFilter);
return this;
}
public void doFilter(MyRequest myRequest, MyResponse myResponse) {
if(index == list.size())
return;
MyFilter myFilter = list.get(index);
index ++;
myFilter.doFilter(myRequest, myResponse, this);
}
}
package com.wzj.chainOfResponsibility.example1.v3;
/**
* @Author: wzj
* @Date: 2019/9/7 20:20
* @Desc: 身高过滤器
*/
public class HeightFliter implements MyFilter {
public void doFilter(MyRequest myRequest, MyResponse myResponse, MyFilterChain myFilterChain) {
myRequest.str = myRequest.str.replace("170", "个子有点矮");
myFilterChain.doFilter(myRequest, myResponse);
myResponse.str += "【妹子挑剔,需要过滤身高】";
}
}
package com.wzj.chainOfResponsibility.example1.v3;
/**
* @Author: wzj
* @Date: 2019/9/7 20:33
* @Desc: 教育背景过滤器
*/
public class EducationalBackGroundFilter implements MyFilter {
public void doFilter(MyRequest myRequest, MyResponse myResponse, MyFilterChain myFilterChain) {
myRequest.str = myRequest.str.replace("学历大专", "学历不高");
myFilterChain.doFilter(myRequest, myResponse);
myResponse.str += "【妹子挑剔,需要过滤学历】";
}
}
测试类
package com.wzj.chainOfResponsibility.example1.v3;
/**
* @Author: wzj
* @Date: 2019/9/7 20:42
* @Desc: 过滤器完全模式
*/
public class TestV3 {
public static void main(String[] args) {
MyRequest myRequest = new MyRequest();
myRequest.str = "张三身高170,学历大专,跪求妹子给个机会认识";
System.out.println("request:" + myRequest.str);
MyResponse myResponse = new MyResponse();
myResponse.str = "";
MyFilterChain chain = new MyFilterChain();
chain.add(new HeightFliter()).add(new EducationalBackGroundFilter());
chain.doFilter(myRequest, myResponse);
System.out.println("response:" + myResponse.str);
}
}
结果
request:张三身高170,学历大专,跪求妹子给个机会认识
response:【妹子挑剔,需要过滤学历】【妹子挑剔,需要过滤身高】
5. 过滤器源码分析
在web项目中经常需要配置满足我们需要的各种过滤器filter,来过滤满足我们自定义信息,不烦看一下tomcat中的源码中是如何做到的,下面以tomcat8.5.37
的源码来分析具体实现。
找到ApplicationFilterFactory
类,其中的createFilterChain(ServletRequest request, Wrapper wrapper, Servlet servlet)
方法创建了责任链,
public static ApplicationFilterChain createFilterChain(ServletRequest request,
Wrapper wrapper, Servlet servlet) {
// 如何没有servlet执行,返回null
if (servlet == null)
return null;
// 创建和初始化过滤器链对象
ApplicationFilterChain filterChain = null;
if (request instanceof Request) {
Request req = (Request) request;
if (Globals.IS_SECURITY_ENABLED) {
filterChain = new ApplicationFilterChain();
} else {
filterChain = (ApplicationFilterChain) req.getFilterChain();
if (filterChain == null) {
filterChain = new ApplicationFilterChain();
req.setFilterChain(filterChain);
}
}
} else {
filterChain = new ApplicationFilterChain();
}
filterChain.setServlet(servlet);
filterChain.setServletSupportsAsync(wrapper.isAsyncSupported());
// 获取上下中的过滤器的映射集合
StandardContext context = (StandardContext) wrapper.getParent();
FilterMap filterMaps[] = context.findFilterMaps();
// If there are no filter mappings, we are done
if ((filterMaps == null) || (filterMaps.length == 0))
return (filterChain);
// 获取匹配的过滤器映射信息
DispatcherType dispatcher =
(DispatcherType) request.getAttribute(Globals.DISPATCHER_TYPE_ATTR);
String requestPath = null;
Object attribute = request.getAttribute(Globals.DISPATCHER_REQUEST_PATH_ATTR);
if (attribute != null){
requestPath = attribute.toString();
}
String servletName = wrapper.getName();
// 对过滤器链添加对应的路径匹配过滤器
for (int i = 0; i < filterMaps.length; i++) {
if (!matchDispatcher(filterMaps[i] ,dispatcher)) {
continue;
}
if (!matchFiltersURL(filterMaps[i], requestPath))
continue;
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
context.findFilterConfig(filterMaps[i].getFilterName());
if (filterConfig == null) {
// FIXME - log configuration problem
continue;
}
filterChain.addFilter(filterConfig);
}
// 添加匹配servlet name的过滤器
for (int i = 0; i < filterMaps.length; i++) {
if (!matchDispatcher(filterMaps[i] ,dispatcher)) {
continue;
}
if (!matchFiltersServlet(filterMaps[i], servletName))
continue;
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
context.findFilterConfig(filterMaps[i].getFilterName());
if (filterConfig == null) {
// FIXME - log configuration problem
continue;
}
filterChain.addFilter(filterConfig);
}
// 返回过滤器责任链
return filterChain;
}
执行过滤器中的doFilter
方法,会调用一个 internalDoFilter() 方法
public final class ApplicationFilterChain implements FilterChain {
......
/**
* 维护当前过滤器链的位置信息
*/
private int pos = 0;
/**
* 当前过滤器的长度
*/
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 {
internalDoFilter(request,response);
}
}
private void internalDoFilter(ServletRequest request,
ServletResponse response)
throws IOException, ServletException {
// 如果有下一个,则调用下一个过滤器
if (pos < n) {
ApplicationFilterConfig filterConfig = filters[pos++];
try {
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的doFilter()方法 , doFilter 又会调用 internalDoFilter,一直递归下去, 直到调用完所有的过滤器
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;
}
// 从最后一个过滤器开始调用
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);
}
}
}
}
6. 责任链模式总结
优点
- 将请求发送者和接收处理者分开。请求者可以不用知道是谁处理的,处理者可以不用知道请求的全貌,两者解耦,提高系统的灵活性。
缺点
- 性能问题,每个请求都是从链头遍历到链尾,特别是在链比较长的时候,性能是一个非常大的问题;
- 调试不很方便,特别是链条比较长,类似递归的方式,调试的时候逻辑可能比较复杂。
注意事项
- 链中节点数量需要控制,避免出现超长链的情况,一般的做法是在Handler中设置一个最大节点数量,在setNext方法中判断是否已经是超过其阈值,超过则不允许该链建立,避免无意识地破坏系统性能。