1. 程式人生 > >基礎設計模式-03 從過濾器(Filter)校驗鏈學習職責鏈模式

基礎設計模式-03 從過濾器(Filter)校驗鏈學習職責鏈模式

# 1.職責鏈路模式 ## 1.1UML圖 ## 1.2 職責鏈路模式的概念 為了解耦的任務校驗,將校驗物件連成一個鏈,沿著這個鏈進行訪問,直到有一個物件處理位置; ## 1.3 優點 1.按照一定的順序執行判斷; 2.避免校驗物件之間耦合關係; 3.不用擔心沒有程式碼沒有執行到; # 2.職責鏈路模式在過濾器(Filter)中的使用 ## 1.原始碼檢視 ### 1.ApplicationDispatcher 這段程式碼總共做了**三件事:**1.過濾器鏈建立;2.過濾鏈逐個過濾;3.釋放過濾鏈資源; ``` private void invoke(ServletRequest request, ServletResponse response, State state) throws IOException, ServletException { //。。。。。。。。前面的程式碼省略 // Get the FilterChain Here ApplicationFilterChain filterChain = ApplicationFilterFactory.createFilterChain(request, wrapper, servlet); //建立過濾器校驗鏈條 // Call the service() method for the allocated servlet instance try { // for includes/forwards if ((servlet != null) && (filterChain != null)) { filterChain.doFilter(request, response); //進行過濾器校驗 } // Servlet Service Method is called by the FilterChain } catch (ClientAbortException e) { //。。。。。。。省略中間錯誤判斷程式碼 } // Release the filter chain (if any) for this request try { if (filterChain != null) filterChain.release();//釋放過濾器資源 } catch (Throwable e) { ExceptionUtils.handleThrowable(e); wrapper.getLogger().error(sm.getString("standardWrapper.releaseFilters", wrapper.getName()), e); // FIXME: Exception handling needs to be similar to what is in the StandardWrapperValue } //。。。。。。。。。後面的程式碼省略 } ``` ### 2.ApplicationFilterFactory(過濾鏈條建立過程) 從下面可以看出主要是一下操作:   1.初始化ApplicatFilterChain 過濾器校驗鏈;   2.從上下文環境中,獲取之前配置的過濾器資料;   3.將符合URL,serveletName的過濾器配置到ApplicationFilterChain中 ``` public static ApplicationFilterChain createFilterChain(ServletRequest request, Wrapper wrapper, Servlet servlet) { // If there is no servlet to execute, return null if (servlet == null) return null; // Create and initialize a filter chain object 初始化鏈式物件 ApplicationFilterChain filterChain = null; if (request instanceof Request) { Request req = (Request) request; if (Globals.IS_SECURITY_ENABLED) { // Security: Do not recycle filterChain = new ApplicationFilterChain(); } else { filterChain = (ApplicationFilterChain) req.getFilterChain(); if (filterChain == null) { filterChain = new ApplicationFilterChain(); req.setFilterChain(filterChain); } } } else { // Request dispatcher in use      filterChain = new ApplicationFilterChain(); } filterChain.setServlet(servlet); filterChain.setServletSupportsAsync(wrapper.isAsyncSupported()); // Acquire the filter mappings for this Context 獲取過濾器配置的上下文 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; // Acquire the information we will need to match filter mappings 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(); // Add the relevant path-mapped filters to this filter chain 將符合需求的過濾器加入到過濾鏈中 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); } // Add filters that match on servlet name second 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 the completed filter chain return filterChain; } ``` ### 3.ApplicationFilterChain(過濾鏈增加的具體過程) 這個方法比較簡單:1.陣列擴容;2.增加新的過濾器; ``` private ApplicationFilterConfig[] filters = new ApplicationFilterConfig[0];//過濾器儲存的實體類 private int pos = 0;//當前過濾位置 private int n = 0;//儲存的過濾器的總數 public static final int INCREMENT = 10; void addFilter(ApplicationFilterConfig filterConfig) { // Prevent the same filter being added multiple times for(ApplicationFilterConfig filter:filters) if(filter==filterConfig) return; if (n == filters.length) { ApplicationFilterConfig[] newFilters = new ApplicationFilterConfig[n + INCREMENT]; System.arraycopy(filters, 0, newFilters, 0, n); filters = newFilters; } filters[n++] = filterConfig; } ``` ### 4.ApplicationFilterChain 的doFilter方法 處理過程:   1.獲取pos位置的過濾器;   2.Filter執行,將當前過濾鏈物件,作為引數進行傳遞;   3.pos過濾器後移1位進行呼叫,直到pos大於總過濾器位置; ``` @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 { // Call the next filter if there is one if (pos < n) { ApplicationFilterConfig filterConfig = filters[pos++];//pos預設是從0開始的,呼叫後+1 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(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; } //。。。。。。。。。。省略部分程式碼 } ``` ## 2.UML圖(上面的鏈式呼叫的圖,如有錯誤還請指出)
# 3.手寫一個通用校驗鏈 **業務需求:前端傳來資料,動態SQL拼接,判斷SQL各個部分是否資料有問題;** ## 1.定義介面 ``` /** * 引數校驗鎖鏈管理 * @param * @author lpf */ public interface CheckChain{ public abstract void doCheck(Param param) throws Exception; } ``` ## 2.對過濾引數進行約束 ``` public interface Param { public abstract T get(); } ``` ## 3.定義過濾介面 ``` @Service public interface CheckFilter { /** * 引數校驗方法 * @param chain * @return */ public abstract void checkParam(Param param, CheckChain chain) throws Exception; } ``` ## 4.預設鏈式校驗實現類 ``` /** * 預設鏈式檢查 */ public class DefaultCheckChain implements CheckChain { /** * */ private ParamCheckWapper[] wappers = new ParamCheckWapper[0]; private static final int INCREMENT = 10; private int n = 0; private int pos = 0; //進行鏈式檢查 @Override public void doCheck(Param filed) throws Exception { if(pos < n){ ParamCheckWapper wapper = wappers[pos++]; CheckFilter paramCheck = wapper.getParamCheck(); Assert.notNull(paramCheck,"鏈式類不能為空"); paramCheck.checkParam(filed,this); } } /** * 增加要進行過濾處理的類 * @param checkWapper */ public void addCheck(ParamCheckWapper checkWapper){ for(ParamCheckWapper wapper : wappers){ if(wapper == checkWapper){return;} ; } if(n == wappers.length){ ParamCheckWapper[] newWappers = new ParamCheckWapper[n + INCREMENT]; System.arraycopy(wappers, 0, newWappers, 0, n); wappers = newWappers; } wappers[n++] = checkWapper; } } ``` ## 5.過濾實現類(可以有多個) ``` /** * select引數校驗 * @author lpf * @since 2019-11-08 */ public class SelectParamCheck implements CheckFilter { /** * 引數校驗 * @param param * @param chain */ @Override public void checkParam(Param param, CheckChain chain) throws Exception{ CheckParam checkParam = param.get(); List selects = checkParam.getSelect(); List columns = checkParam.getColumnList(); //對select引數進行校驗 selects.forEach(select -> { String filed = select.getFiled().toLowerCase(); boolean flag = columns.contains(filed); if(!flag) throw new RuntimeException(select.getFiled()+"不存在,請重新整理頁面重新選擇查詢欄位!!!"); }); } ``` ## 6.過濾類註冊(可以通過yml配置反射生成,或者通過手動註冊) ``` @Service public class SearchConfigService { /**預設檢查鏈*/ private static DefaultCheckChain checkChain ; /**過濾鏈路表配置*/ static{ checkChain = new DefaultCheckChain(); //引數檢查器 ParamCheckWapper selectParamCheck = new ParamCheckWapper(new SelectParamCheck(),"SelectParamCheck"); ParamCheckWapper groupParamCheck = new ParamCheckWapper(new GroupbyParamCheck(), "groupParamCheck"); ParamCheckWapper conditionParamCheck = new ParamCheckWapper(new ConditionParamCheck(), "conditionParamCheck"); ParamCheckWapper orderbyParamCheck = new ParamCheckWapper(new OrderbyParamCheck(), "orderbyParamCheck"); //引數連結串列增加過濾類 checkChain.addCheck(selectParamCheck); checkChain.addCheck(groupParamCheck); checkChain.addCheck(conditionParamCheck); checkChain.addCheck(orderbyParamCheck); } /** * 引數校驗 */ public void doCheck(Param param) throws Exception { checkChain.doCheck(param); } ```   **以上,就是職責鏈路模式的簡單使用,可以通過泛型進行程式碼剝離,後續涉及到鏈式校驗的時候就可以通過限制引數進行多樣使用。降低程式碼的耦合度;** **至此,職責鏈路設計模式的介紹就結束了;**