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<MyFilter> list = new ArrayList<MyFilter>();
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<MyFilter> list = new ArrayList<MyFilter>();
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<MyFilter> list = new ArrayList<MyFilter>();
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<Void>() {
@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方法中判斷是否已經是超過其閾值,超過則不允許該鏈建立,避免無意識地破壞系統性能。