責任鏈模式(Chain of Responsibility):使多個對象
都有機會處理請求,從而避免了請求的發送者
和接受者
之間的耦合
關係。將這些對象連成一條鏈,並沿着這條鏈傳遞該請求,直到有對象處理它爲止。java
商城新開張,每一個訂單,能夠享受多張優惠券疊加減免
程序員
責任鏈模式
web
類圖以下
tomcat
抽象類
;nextDiscountFilter
,用來把對象串成鏈,原價計算優惠後的價格方法calculateBySourcePrice
;實現類
app
FullDistcountFliter
滿200減20元;FirstPurchaseDiscount
首次購買減20元;SecondPurchaseDiscountFilter
第二件打9折;HolidayDiscountFilter
節日一概減5元.接口與實現類ide
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
如今有這樣一個場景,程序員張三在某相親節目中找對象,在第一關和第二關分別被女孩滅燈pass掉,下面分別看看倆姑娘是如何過濾張三的。
小美對話張三
測試
此場景能夠模擬一個http請求如何通過過濾器到服務端,通過每個過濾器,能夠想象爲張三相親過程當中通過某個關卡,因爲不符合該過濾器的條件被姑娘過濾掉。
this
第一個版本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
同v1MyFilterChain
中加入記錄具體責任鏈對象的下標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:【妹子挑剔,須要過濾學歷】【妹子挑剔,須要過濾身高】
在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); } } } }
優勢
缺點
注意事項