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

wuzhiaite發表於2020-07-13

1.職責鏈路模式

1.1UML圖

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

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<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 {

        // 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圖(上面的鏈式呼叫的圖,如有錯誤還請指出)

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

3.手寫一個通用校驗鏈

業務需求:前端傳來資料,動態SQL拼接,判斷SQL各個部分是否資料有問題;

1.定義介面

/**
 * 引數校驗鎖鏈管理
 * @param
 * @author lpf
 */
public interface CheckChain{
    public abstract void doCheck(Param param) throws Exception;
}

2.對過濾引數進行約束

public interface Param<T extends Param> {
    public abstract <T> T get();
}

3.定義過濾介面

@Service
public interface CheckFilter<T extends Param> {
    /**
     * 引數校驗方法
     * @param chain
     * @return
     */
    public abstract void checkParam(Param<T> 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<CheckParam> {

    /**
     * 引數校驗
     * @param param
     * @param chain
     */
    @Override
    public void checkParam(Param<CheckParam> param, CheckChain chain) throws Exception{
        CheckParam checkParam = param.get();
        List<SelectField> selects = checkParam.getSelect();
        List<String> 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);
    }

  以上,就是職責鏈路模式的簡單使用,可以通過泛型進行程式碼剝離,後續涉及到鏈式校驗的時候就可以通過限制引數進行多樣使用。降低程式碼的耦合度;

至此,職責鏈路設計模式的介紹就結束了;

相關文章