1. 程式人生 > >折騰Java設計模式之責任鏈模式

折騰Java設計模式之責任鏈模式

責任鏈模式

顧名思義,責任鏈模式(Chain of Responsibility Pattern)為請求建立了一個接收者物件的鏈。這種模式給予請求的型別,對請求的傳送者和接收者進行解耦。這種型別的設計模式屬於行為型模式。在這種模式中,通常每個接收者都包含對另一個接收者的引用。如果一個物件不能處理該請求,那麼它會把相同的請求傳給下一個接收者,依此類推。

簡介

意圖 避免請求傳送者與接收者耦合在一起,讓多個物件都有可能接收請求,將這些物件連線成一條鏈,並且沿著這條鏈傳遞請求,直到有物件處理它為止。

主要解決 職責鏈上的處理者負責處理請求,客戶只需要將請求傳送到職責鏈上即可,無須關心請求的處理細節和請求的傳遞,所以職責鏈將請求的傳送者和請求的處理者解耦了。

何時使用 在處理訊息的時候以過濾很多道。

如何解決 攔截的類都實現統一介面。

關鍵程式碼 Handler 裡面聚合它自己,在 handleRequest 裡判斷是否合適,如果沒達到條件則向下傳遞。

純責任鏈與不純責任鏈

  • 純:純責任鏈中的節點只有兩種行為,一處理責任,二將責任傳遞到下一個節點。不允許出現某一個節點處理部分或全部責任後又將責任向下傳遞的情況。
  • 不純:允許某個請求被一個節點處理部分責任後再向下傳遞,或者處理完後其後續節點可以繼續處理該責任,而且一個責任可以最終不被任何節點所處理。

主要角色

  • Handler(抽象處理者): 定義一個處理請求的介面,提供對後續處理者的引用

  • ConcreteHandler(具體處理者): 抽象處理者的子類,處理使用者請求,可選將請求處理掉還是傳給下家;在具體處理者中可以訪問鏈中下一個物件,以便請求的轉發

應用例項

1、紅樓夢中的"擊鼓傳花"。

2、JS 中的事件冒泡。

3、JAVA WEB 中 Apache Tomcat 對 Encoding 的處理,Struts2 的攔截器,jsp servlet 的 Filter。

優點

1、降低耦合度。它將請求的傳送者和接收者解耦。

2、簡化了物件。使得物件不需要知道鏈的結構。

3、增強給物件指派職責的靈活性。通過改變鏈內的成員或者調動它們的次序,允許動態地新增或者刪除責任。

4、增加新的請求處理類很方便。

缺點

1、不能保證請求一定被接收。

2、系統性能將受到一定影響,而且在進行程式碼除錯時不太方便,可能會造成迴圈呼叫。

3、可能不容易觀察執行時的特徵,有礙於除錯。

使用場景

1、有多個物件可以處理同一個請求,具體哪個物件處理該請求由執行時刻自動確定。

2、在不明確指定接收者的情況下,向多個物件中的一個提交一個請求。

3、可動態指定一組物件處理請求。

Github專案描述

跳轉到我的責任鏈設計模式原始碼

1.出行方式

travel包裡主要對出行方式的責任鏈模式。跟進使用者身上的錢,在優先順序如飛機->火車->大巴的順序下選擇對應的出行模式。

public class Application {

    public static void main(String[] args) {
        Handler planeHandler = new PlaneHandler();
        Handler trainHandler = new TrainHandler();
        Handler busHandler = new BusHandler();
        planeHandler.setNext(trainHandler);
        trainHandler.setNext(busHandler);

        planeHandler.handleRequest("老王", 40d);
        planeHandler.handleRequest("張三", 140d);
        planeHandler.handleRequest("李四", 240d);
        planeHandler.handleRequest("吳老五", 340d);
    }
}
複製程式碼

img

抽象處理

@Data
public abstract class Handler {

    /**
     * 下一個鏈節點
     */
    protected Handler next;

    public abstract void handleRequest(String name, Double wallet);
}
複製程式碼

具體的處理者(飛機、火車、大巴)

@Slf4j
public class PlaneHandler extends Handler {

    private double price = 280d;

    @Override
    public void handleRequest(String name, Double wallet) {
        if (price <= wallet) {
            log.info("{}身上的錢可以坐飛機。", name);
            return;
        }
        if (Objects.nonNull(next)) {
            next.handleRequest(name, wallet);
            return;
        }
        log.info("{}錢不夠,只能徒步啦", name);
    }
}
複製程式碼
@Slf4j
public class TrainHandler extends Handler {

    private double price = 149.99d;

    @Override
    public void handleRequest(String name, Double wallet) {
        if (price <= wallet) {
           log.info("{}身上的錢只能坐火車。", name);
           return;
        }
        if (Objects.nonNull(next)) {
            next.handleRequest(name, wallet);
            return;
        }
        log.info("{}錢不夠,只能徒步啦", name);
    }
}
複製程式碼
@Slf4j
public class BusHandler extends Handler {

    private double price = 59.99d;

    @Override
    public void handleRequest(String name, Double wallet) {
        if (price <= wallet) {
            log.info("{}身上的錢只能坐大巴。", name);
            return;
        }
        if (Objects.nonNull(next)) {
            next.handleRequest(name, wallet);
            return;
        }
        log.info("{}錢不夠,只能徒步啦", name);
    }
}
複製程式碼

2.出行方式2,參考Filter鏈的寫法

travel2包是對travel包的重新寫法。

public class Application {

    public static void main(String[] args) {
        HandlerChain chain = new HandlerChain();
        Handler planeHandler = new PlaneHandler();
        Handler trainHandler = new TrainHandler();
        Handler busHandler = new BusHandler();
        chain.addHandler(planeHandler);
        chain.addHandler(trainHandler);
        chain.addHandler(busHandler);

        chain.handle("老王", 40d);
        chain.handle("張三", 140d);
        chain.handle("李四", 240d);
        chain.handle("吳老五", 340d);
    }
}
複製程式碼

image-20181223222027677

抽象處理者

public interface Handler {

    void handleRequest(String name, Double wallet, HandlerChain chain);
}
複製程式碼

具體處理者(飛機、火車、大巴)

@Slf4j
public class PlaneHandler implements Handler {

    private double price = 280d;

    @Override
    public void handleRequest(String name, Double wallet, HandlerChain chain) {
        if (price <= wallet) {
            log.info("{}身上的錢可以坐飛機。", name);
            chain.reuse();
            return;
        }
        chain.handle(name, wallet);
    }
}
複製程式碼
@Slf4j
public class TrainHandler implements Handler {

    private double price = 149.99d;

    @Override
    public void handleRequest(String name, Double wallet, HandlerChain chain) {
        if (price <= wallet) {
            log.info("{}身上的錢只能坐火車。", name);
            chain.reuse();
            return;
        }
        chain.handle(name, wallet);
    }
}
複製程式碼
@Slf4j
public class BusHandler implements Handler {

    private double price = 59.99d;

    @Override
    public void handleRequest(String name, Double wallet, HandlerChain chain) {
        if (price <= wallet) {
            log.info("{}身上的錢只能坐大巴。", name);
            chain.reuse();
            return;
        }
        chain.handle(name, wallet);
    }
}
複製程式碼

責任鏈管理者

@Slf4j
public class HandlerChain {

    private List<Handler> handlerList = new ArrayList<>();

    /**
     * 維護當前鏈上位置
     */
    private int pos;

    /**
     * 鏈的長度
     */
    private int handlerLength;

    public void addHandler(Handler handler) {
        handlerList.add(handler);
        handlerLength = handlerList.size();
    }

    public void handle(String name, double wallet) {
        if (CollectionUtils.isEmpty(handlerList)) {
            log.error("有錢,但沒提供服務,{}也估計就只能步行了。", name);
            return;
        }
        if (pos >= handlerLength) {
            log.error("身上錢不夠,{}也估計就只能步行了。", name);
            reuse();
            return;
        }
        Handler handler = handlerList.get(pos++);
        if (Objects.isNull(handler)) {
            log.error("假服務,{}也估計就只能步行了。", name);
            reuse();
            return;
        }

        handler.handleRequest(name, wallet, this);
    }

    /**
     * 鏈重新使用
     */
    public void reuse() {
        pos = 0;
    }
}
複製程式碼

學習Web專案的Filter

待補充...

補充補充遺留的Filter過濾器中的責任鏈處理。

本次主要是對Tomcat中的Filter處理簡單的梳理,如有不正確的地方,還望指出來,大家互勉,共進。

老專案大家可以在web.xml中配置filter,現使用Springboot後,也有兩種配置filter方式,通過建立FilterRegistrationBean的方式和通過註解@[email protected]的方式。

三個主要的角色

FIlter,不多介紹了。

FilterChain servlet容器提供的開發呼叫鏈的過濾請求的資源。通過呼叫下一個filter實現過濾,在整體鏈上。

FilterConfig filter的配置器,在servlet容器在Filter初始化的時候傳遞資訊。

具體的filter,主要說說Spring中的兩個抽象Filter,GenericFilterBean和OncePerRequestFilter。

前者主要是做init和destroy的操作,重點還是init方法,destroy只是空實現而已。

後者主要是做真正的doFilter操作,也是我們在Spring中建立Filter通常繼承的。

而ApplicationFilterChain就算Tomcat中的FilterChain實現。

    /**
     * The int which is used to maintain the current position
     * in the filter chain.
     */
    private int pos = 0;


    /**
     * The int which gives the current number of filters in the chain.
     */
    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 {
        //真正的doFilter
        internalDoFilter(request,response);
    }
}


private void internalDoFilter(ServletRequest request,
                              ServletResponse response)
    throws IOException, ServletException {
	//pos 呼叫鏈中當前連線點所在的位置
    //n 呼叫鏈總節點長度
    // Call the next filter if there is one
    if (pos < n) {
        //對節點進行自增 pos++
        ApplicationFilterConfig filterConfig = filters[pos++];
        try {
            //當前節點小於總長度後,從filter配置類中取出filter
            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
                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;
    }

    // We fell off the end of the chain -- call the servlet instance
	//到了呼叫鏈結尾處,就真正呼叫servlet例項的servlet.service(request, response);
    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);
        }
    }
}

/**
* Prepare for reuse of the filters and wrapper executed by this chain.
* 重複使用filter呼叫鏈,pos重設為0
*/
void reuse() {
    pos = 0;
}
複製程式碼

重點從ApplicationFilterChain中挑出幾個重要的方法拿出來分析下Filter的呼叫鏈,其實還有幾處沒有具體講到,ApplicationFilterChain是合適建立的,Filter是怎麼加入到ApplicationFilterChain中的。這涉及到Tomcat是怎樣載入Content的,下次分析Tomcat的時候,再來具體分析,它是如何運作的,如何載入web.xml。

參考

維基的責任鏈模式

責任鏈模式|菜鳥教程

Filter、FilterConfig、FilterChain|菜鳥教程

南鄉清水的實際專案運用之Responsibility-Chain模式

一起學設計模式 - 責任鏈模式