<acronym id="cr5pu"></acronym>
  • <kbd id="cr5pu"><font id="cr5pu"></font></kbd>
  • <li id="cr5pu"><output id="cr5pu"></output></li>
    <del id="cr5pu"><li id="cr5pu"></li></del><center id="cr5pu"></center>
    <output id="cr5pu"><kbd id="cr5pu"></kbd></output>
  • <rp id="cr5pu"></rp>
    <var id="cr5pu"></var>
  • <nav id="cr5pu"></nav>
  • 上善若水
    In general the OO style is to use a lot of little objects with a lot of little methods that give us a lot of plug points for overriding and variation. To do is to be -Nietzsche, To bei is to do -Kant, Do be do be do -Sinatra
    posts - 146,comments - 147,trackbacks - 0

    問題描述

    在服務器編程中,通常需要處理多種不同的請求,在正式處理請求之前,需要對請求做一些預處理,如:
    1. 紀錄每個Client的每次訪問信息。
    2. 對Client進行認證和授權檢查(Authentication and Authorization)。
    3. 檢查當前Session是否合法。
    4. 檢查Client的IP地址是否可信賴或不可信賴(IP地址白名單、黑名單)。
    5. 請求數據是否先要解壓或解碼。
    6. 是否支持Client請求的類型、Browser版本等。
    7. 添加性能監控信息。
    8. 添加調試信息。
    9. 保證所有異常都被正確捕獲到,對未預料到的異常做通用處理,防止給Client看到內部堆棧信息。

    在響應返回給客戶端之前,有時候也需要做一些預處理再返回:

    1. 對響應消息編碼或壓縮。
    2. 為所有響應添加公共頭、尾等消息。
    3. 進一步Enrich響應消息,如添加公共字段、Session信息、Cookie信息,甚至完全改變響應消息等。
    如何實現這樣的需求,同時保持可擴展性、可重用性、可配置、移植性?

    問題解決

    要實現這種需求,最直觀的方法就是在每個請求處理過程中添加所有這些邏輯,為了減少代碼重復,可以將所有這些檢查提取成方法,這樣在每個處理方法中調用即可:
    public Response service1(Request request) {
        validate(request);
        request 
    = transform(request);
        Response response 
    = process1(request);
        
    return transform(response);
    }
    此時,如果出現service2方法,依然需要拷貝service1中的實現,然后將process1換成process2即可。這個時候我們發現很多重復代碼,繼續對它重構,比如提取公共邏輯到基類成模版方法,這種使用繼承的方式會引起子類對父類的耦合,如果要讓某些模塊變的可配置需要有太多的判斷邏輯,代碼變的臃腫;因而可以更進一步,將所有處理邏輯抽象出一個Processor接口,然后使用Decorate模式(即引用優于繼承):
    public interface Processor {
        Response process(Request request);
    }
    public class CoreProcessor implements Processor {
        
    public Response process(Request request) {
            
    // do process/calculation
        }
    }
    public class DecoratedProcessor implements Processor {
        
    private final Processor innerProcessor;
        
    public DecoratedProcessor(Processor processor) {
            
    this.innerProcessor = processor;
        }

        
    public Response process(Request request) {
            request 
    = preProcess(request);
            Response response 
    = innerProcessor.process(request);
            response 
    = postProcess(response);
            
    return response;
        }

        
    protected Request preProcess(Request request) {
            
    return request;
        }
        
    protected Response postProcess(Response response) {
            
    return response;
        }
    }

    public void Transformer extends DecoratedProcessor {
        
    public Transformer(Processor processor) {
            
    super(processor);
        }

        
    protected Request preProcess(Request request) {
            
    return transformRequest(request);
        }
        
    protected Response postProcess(Response response) {
            
    return transformResponse(response);
        }
    }
    此時,如果需要在真正的處理邏輯之前加入其他的預處理邏輯,只需要繼承DecoratedProcessor,實現preProcess或postProcess方法,分別在請求處理之前和請求處理之后橫向切入一些邏輯,也就是所謂的AOP編程:面向切面的編程,然后只需要根據需求構建這個鏈條:
    Processor processor = new MissingExceptionCatcher(new Debugger(new Transformer(new CoreProcessor());
    Response response 
    = processor.process(request);
    ......
    這已經是相對比較好的設計了,每個Processor只需要關注自己的實現邏輯即可,代碼變的簡潔;并且每個Processor各自獨立,可重用性好,測試方便;整條鏈上能實現的功能只是取決于鏈的構造,因而只需要有一種方法配置鏈的構造即可,可配置性也變得靈活;然而很多時候引用是一種靜態的依賴,而無法滿足動態的需求。要構造這條鏈,每個前置Processor需要知道其后的Processor,這在某些情況下并不是在起初就知道的。此時,我們需要引入Intercepting Filter模式來實現動態的改變條鏈。

    Intercepting Filter模式

    在前文已經構建了一條由引用而成的Processor鏈,然而這是一條靜態鏈,并且需要一開始就能構造出這條鏈,為了解決這個限制,我們可以引入一個ProcessorChain來維護這條鏈,并且這條鏈可以動態的構建。

    有多種方式可以實現并控制這個鏈:
    1. 在存儲上,可以使用數組來存儲所有的Processor,Processor在數組中的位置表示這個Processor在鏈條中的位置;也可以用鏈表來存儲所有的Processor,此時Processor在這個鏈表中的位置即是在鏈中的位置。
    2. 在抽象上,可以所有的邏輯都封裝在Processor中,也可以將核心邏輯使用Processor抽象,而外圍邏輯使用Filter抽象。
    3. 在流程控制上,一般通過在Processor實現方法中直接使用ProcessorChain實例(通過參數摻入)來控制流程,利用方法調用的進棧出棧的特性實現preProcess()和postProcess()處理。
    在實際中使用這個模式的有:Servlet的Filter機制、Netty的ChannelPipeline中、Structs2中的Interceptor中都實現了這個模式。

    Intercepting Filter模式在Servlet的Filter中的實現(Jetty版本)

    其中Servlet的Filter在Jetty的實現中使用數組存儲Filter,Filter末尾可以使用Servlet實例處理真正的業務邏輯,在流程控制上,使用FilterChain的doFilter方法來實現。如FilterChain在Jetty中的實現:
    public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException
       
    // pass to next filter
        if (_filter < LazyList.size(_chain)) {
            FilterHolder holder
    = (FilterHolder)LazyList.get(_chain, _filter++);
            Filter filter= holder.getFilter();
            filter.doFilter(request, response, this);                   
           
    return;
        }

       
    // Call servlet
        HttpServletRequest srequest = (HttpServletRequest)request;
       
    if (_servletHolder != null) {
            _servletHolder.handle(_baseRequest,request, response);

        }
    }
    這里,_chain實際上是一個Filter的ArrayList,由FilterChain調用doFilter()啟動調用第一個Filter的doFilter()方法,在實際的Filter實現中,需要手動的調用FilterChain.doFilter()方法來啟動下一個Filter的調用,利用方法調用的進棧出棧的特性實現Request的pre-process和Response的post-process處理。如果不調用FilterChain.doFilter()方法,則表示不需要調用之后的Filter,流程從當前Filter返回,在它之前的Filter的FilterChain.doFilter()調用之后的邏輯反向處理直到第一個Filter處理完成而返回。
    public class MyFilter implements Filter {
        
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
            
    // pre-process ServletRequest
            chain.doFilter(request, response);
            
    // post-process Servlet Response
        }
    }
    整個Filter鏈的處理流程如下:

    Intercepting Filter模式在Netty3中的實現

    Netty3在DefaultChannelPipeline中實現了Intercepting Filter模式,其中ChannelHandler是它的Filter。在Netty3的DefaultChannelPipeline中,使用一個以ChannelHandlerContext為節點的雙向鏈表來存儲ChannelHandler,所有的橫切面邏輯和實際業務邏輯都用ChannelHandler表達,在控制流程上使用ChannelHandlerContext的sendDownstream()和sendUpstream()方法來控制流程。不同于Servlet的Filter,ChannelHandler有兩個子接口:ChannelUpstreamHandler和ChannelDownstreamHandler分別用來請求進入時的處理流程和響應出去時的處理流程。對于Client的請求,從DefaultChannelPipeline的sendUpstream()方法入口:
    public void sendDownstream(ChannelEvent e) {
        DefaultChannelHandlerContext tail 
    = getActualDownstreamContext(this.tail);
       
    if (tail == null) {
           
    try {
                getSink().eventSunk(
    this, e);
               
    return;
            } 
    catch (Throwable t) {
                notifyHandlerException(e, t);
               
    return;
            }
        }
        sendDownstream(tail, e);
    }
    void sendDownstream(DefaultChannelHandlerContext ctx, ChannelEvent e) {
       
    if (e instanceof UpstreamMessageEvent) {
           
    throw new IllegalArgumentException("cannot send an upstream event to downstream");
        }
       
    try {
            ((ChannelDownstreamHandler) ctx.getHandler()).handleDownstream(ctx, e)
         } 
    catch (Throwable t) {
            e.getFuture().setFailure(t);
            notifyHandlerException(e, t);
        }
    }
    如果有響應消息,該消息從DefaultChannelPipeline的sendDownstream()方法為入口:
    public void sendUpstream(ChannelEvent e) {
        DefaultChannelHandlerContext head 
    = getActualUpstreamContext(this.head);
       
    if (head == null) {
            return;
        }
        sendUpstream(head, e);
    }
    void sendUpstream(DefaultChannelHandlerContext ctx, ChannelEvent e) {
       
    try {
            ((ChannelUpstreamHandler) ctx.getHandler()).handleUpstream(ctx, e);
        } 
    catch (Throwable t) {
            notifyHandlerException(e, t);
        }
    }
    在實際實現ChannelUpstreamHandler或ChannelDownstreamHandler時,調用ChannelHandlerContext中的sendUpstream或sendDownstream方法將控制流程交給下一個ChannelUpstreamHandler或下一個ChannelDownstreamHandler,或調用Channel中的write方法發送響應消息。
    public class MyChannelUpstreamHandler implements ChannelUpstreamHandler {
        
    public void handleUpstream(ChannelHandlerContext ctx, ChannelEvent e) throws Exception {
            
    // handle current logic, use Channel to write response if needed.
            
    // ctx.getChannel().write(message);
            ctx.sendUpstream(e);
        }
    }

    public class MyChannelDownstreamHandler implements ChannelDownstreamHandler {
        
    public void handleDownstream(
                ChannelHandlerContext ctx, ChannelEvent e) 
    throws Exception {
            
    // handle current logic
            ctx.sendDownstream(e);
        }
    }
    當ChannelHandler向ChannelPipelineContext發送事件時,其內部從當前ChannelPipelineContext 節點出發找到下一個ChannelUpstreamHandler或ChannelDownstreamHandler實例,并向其發送 ChannelEvent,對于Downstream鏈,如果到達鏈尾,則將ChannelEvent發送給ChannelSink:
    public void sendDownstream(ChannelEvent e) {
        DefaultChannelHandlerContext prev 
    = getActualDownstreamContext(this.prev);
       
    if (prev == null) {
           
    try {
                getSink().eventSunk(DefaultChannelPipeline.
    this, e);
            } 
    catch (Throwable t) {
                notifyHandlerException(e, t);
            }
        } 
    else {
            DefaultChannelPipeline.
    this.sendDownstream(prev, e);
        }
    }

    public void sendUpstream(ChannelEvent e) {
        DefaultChannelHandlerContext next 
    = getActualUpstreamContext(this.next);
       
    if (next != null) {
            DefaultChannelPipeline.
    this.sendUpstream(next, e);
        }
    }
    正是因為這個實現,如果在一個末尾的ChannelUpstreamHandler中先移除自己,在向末尾添加一個新的ChannelUpstreamHandler,它是無效的,因為它的next已經在調用前就固定設置為null了。

    在DefaultChannelPipeline的ChannelHandler鏈條的處理流程為:

    在這個實現中,不像Servlet的Filter實現利用方法調用棧的進出棧來完成pre-process和post-process,而是在進去的鏈和出來的鏈各自調用handleUpstream()和handleDownstream()方法,這樣會引起調用棧其實是兩條鏈的總和,因而需要注意這條鏈的總長度。這樣做的好處是這條ChannelHandler的鏈不依賴于方法調用棧,而是在DefaultChannelPipeline內部本身的鏈,因而在handleUpstream()或handleDownstream()可以隨時將執行流程轉發給其他線程或線程池,只需要保留ChannelPipelineContext引用,在處理完成后用這個ChannelPipelineContext重新向這條鏈的后一個節點發送ChannelEvent,然而由于Servlet的Filter依賴于方法的調用棧,因而方法返回意味著所有執行完成,這種限制在異步編程中會引起問題,因而Servlet在3.0后引入了Async的支持。

    Intercepting Filter模式的缺點

    簡單提一下這個模式的缺點:
    1. 相對傳統的編程模型,這個模式有一定的學習曲線,需要很好的理解該模式后才能靈活的應用它來編程。
    2. 需要劃分不同的邏輯到不同的Filter中,這有些時候并不是那么容易。
    3. 各個Filter之間共享數據將變得困難。在Netty3中可以自定義自己的ChannelEvent來實現自定義消息的傳輸,或者使用ChannelPipelineContext的Attachment字段來實現消息傳輸,而Servlet中的Filter則沒有提供類似的機制,如果不是可以配置的數據在Config中傳遞,其他時候的數據共享需要其他機制配合完成。

    參考

    Core J2EE Pattern - Intercepting Filter
    posted on 2015-09-03 22:14 DLevin 閱讀(5519) 評論(0)  編輯  收藏 所屬分類: Architecture

    只有注冊用戶登錄后才能發表評論。


    網站導航:
     
    久久一级片
    <acronym id="cr5pu"></acronym>
  • <kbd id="cr5pu"><font id="cr5pu"></font></kbd>
  • <li id="cr5pu"><output id="cr5pu"></output></li>
    <del id="cr5pu"><li id="cr5pu"></li></del><center id="cr5pu"></center>
    <output id="cr5pu"><kbd id="cr5pu"></kbd></output>
  • <rp id="cr5pu"></rp>
    <var id="cr5pu"></var>
  • <nav id="cr5pu"></nav>