請求轉發器實現原理

    一個好的 Web 開發框架必然少不了接收 Action 請求這個重要的環節。咱們也能夠看到市面上不一樣的框架都有着本身特點的處理方式。固然 Hasor 在處理 Web 請求時也會有本身獨特的處理方式。看過以後相信你必定會很是熟悉。java

    說了這麼多,讓咱們來看看如何使用 Hasor 來處理連入的請求吧。緩存

    根據上面圖能夠看出,Hasor 在處理 Web 請求的時候採用了通用的處理方式。請求要首先進入口 Filter,而後 入口 Filter 負責調用 Dispatcher 把請求派發到對應的 Controller 上從而完成真個請求接入的工做。併發

1、入口

    那麼 Dispatcher 是如何工做的呢?app

    在 Hasor 的入口 RuntimeFilter 中能夠看到除了作的最多的 init 初始化工做以外,最重要的就是下面這個方法,調用 Dispatcher 進行請求派發。框架

public class RuntimeFilter implements Filter {
    ...
    public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws IOException, ServletException {
        ...
        this.processFilterPipeline(httpReq, httpRes, chain);
        ...
    }
    ...
    private void processFilterPipeline(final HttpServletRequest httpReq, final HttpServletResponse httpRes, final FilterChain chain) throws IOException, ServletException {
        this.filterPipeline.dispatch(httpReq, httpRes, chain);
    }

}

 

2、Dispatcher派發器

-請求攔截器

    FilterPipeline 派發器是一個接口,它的實現類是 ManagedFilterPipeline。派發器最重要的工做就是根據當前請求路徑找到最適合的那個 Controller 而後把請求交給這個  Controller。性能

    ManagedFilterPipeline,有兩個特別重要的過程一個是 initPipeline ,負責初始化整個基礎數據。有了基礎數據,接下來在匹配 Controller 時纔有的選。另一個重要的過程就是 dispatch 這個方法負責從衆多 Controller 中找到須要的那一個而後執行調用。單元測試

    先說 init 過程,下面這個就是 init 過程的關鍵代碼。其中咱們能夠看到 collectFilterDefinitions 方法負責把全部的 Controller 都收集到。而後執行對應的 init 方法。測試

public class ManagedFilterPipeline implements FilterPipeline {
    ...
    public synchronized void initPipeline(final WebAppContext appContext, final Map<String, String> filterConfig) throws ServletException {
        ...
        this.filterDefinitions = this.collectFilterDefinitions(appContext);
        for (FilterDefinition filterDefinition : this.filterDefinitions) {
            filterDefinition.init(appContext, filterConfig);
        }
        ...
    }
    ...
}

    dispatch,下面這個是主要邏輯。咱們能夠看到在初始化中獲得的 filterDefinitions 並無按照咱們想一想中的先篩選出一個子集合,而後在去執行調用。而是直接把請求送進了全部 Controller。其中 FilterChainInvocation 類的做用是充當過濾器鏈調度器。this

public class ManagedFilterPipeline implements FilterPipeline {
    ...
    public void dispatch(HttpServletRequest request, HttpServletResponse response, FilterChain defaultFilterChain) {
        ...
        FilterChainInvocation invocation = new FilterChainInvocation(this.filterDefinitions, this.servletPipeline, defaultFilterChain);
        invocation.doFilter(dispatcherRequest, response);
        ...
    }
    ...
}

    dispatch 的入口方法中既然沒有作篩選,那麼必定是在 doFilter 中在執行時作了判斷。這樣的設計有一個很大的好處。就是節省資源開銷。spa

    讓咱們想象一下,若是每一個請求在進入 dispatch 的時都要對全部 Controller 遍歷一遍求出最重須要的那一個。最後咱們的到了想要的 Controller List 在循環執行一次調用。假設咱們有 10 個 Controller 那麼最多就要執行 20 次循環。這種方式實在是笨拙既浪費 CPU 不說還浪費內存,若是併發請求量很大的話。這種方式還會成爲框架性能的瓶頸。

    所以 Hasor 在設計 dispatch 的時候充考慮到了這種狀況,爲了提高性能把遍歷和判斷改成一次循環一次判斷。既省 CPU 還省內存,每次執行 dispatch 最多產生 10次循環。這樣一來即環保又高效。

    下面代碼中能夠看到 matchesUri 方法的返回值直接影響到了 Controller 是否會被執行,若是當前的 Controller 沒有命中就會經過 FilterChain 前往下一個。

class FilterDefinition extends AbstractServletModuleBinding {
    ...
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain){
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        String path = httpRequest.getRequestURI().substring(httpRequest.getContextPath().length());
        boolean serve = this.matchesUri(path);
        //
        Filter filter = this.getTarget();
        if (serve && filter != null) {
            filter.doFilter(request, response, chain);
        } else {
            chain.doFilter(httpRequest, response);
        }
    }
    ...
}

 

- 調用Controller

    前面咱們一致都忽略了 Servlet 的處理,這裏咱們在討論一下 Servlet。 Hasor 的 Servlet 處理都是封裝到 ManagedServletPipeline 類裏與 ManagedFilterPipeline 具備同等地位。

    不一樣的是 ManagedServletPipeline 接受的請求是在 ManagedFilterPipeline 執行過程的最後纔會調用到它,若是在 Filter 執行過程當中有任何一個環節沒有繼續執行 「chain.doFilter(httpRequest, httpResponse);」 那就表示後面不會執行到 ManagedServletPipeline。

    這一點也很好理解,畢竟 Filter 已經攔截了請求並作了相應的處理。後續可能就不須要在執行了。

 

3、爲何要用循環判斷而不用Mapping作映射?

    這一節咱們從一個小問題出發論一論正確性。

    這個問題應該是你們都會廣泛問到的問題,首先從功能實現上來說循環判斷和 Mapping 映射均可以實現相同的功能。循環判斷要比 Mapping 映射覆雜一點,並且要稍微耗時一些。

    Mapping 一般是利用 HashMap 或者其它一些 Map 來實現,而這些 Map 都有一個共同的問題那就是 Hash碰撞。

    接下來我說一個實際的 Case。記得在開發 RSF 項目中,採用了 String -> Method  這種映射機制作緩存。省了每次都到 Class 中反射查詢方法。本來上想增長運行效率,不料在單元測試中,發生了找不到方法的異常。幾經排查,最後鎖定了問題的元兇。就是那個 String -> Method 的 cache。

    在這個 case 中雖然沒有產生什麼後果,可是若是在線上環境上遇到剛好兩遍方法入參、出參又一致。那麼就會在你不知情的狀況下,調用了一個錯誤的方法。而這個錯誤的方法卻給出了你一個你認爲正確的結果。

    進一步想象一下,若是是某個交易場景呢?

    可能有同窗說了,機率過小了。不要大驚小怪。可是實際中咱們不能說風險小就不去管它,一旦遇到風險怎麼辦呢?

    這就比如生一個健康的孩子,醫院跟你說你的孩子 99.999% 是沒有問題的。換句話說 10萬個孩子裏只會有一個有問題。 10萬分之一的機率是多麼小的機率。

    可是我要問的是你想作這個 10萬分之一麼?,又或者你想作 100萬分之一那我的麼?

    程序也是一個道理。風險在小隻要它存在,就不能漠視。正確性高於一切!!!

相關文章
相關標籤/搜索