1. 程式人生 > >tomcat責任鏈設計模式 FilterChain原理解析

tomcat責任鏈設計模式 FilterChain原理解析

轉自:http://javapolo.iteye.com/blog/1287747

今天晚上花了些時間debug了下tomcat,注意觀察了下tomcat內部過濾器的實現,其實tomcat內部過濾器採用了責任鏈的設計模式,

(其實struts2攔截器那一塊採用了相似的設計模式),以下是個人對原始碼的解讀,

ApplicationFilterChain詳解

首先是對該類的定義的介紹

/**
 * Implementation of <code>javax.servlet.FilterChain</code> used to manage
 * the execution of a set of filters for a particular request.  When the
 * set of defined filters has all been executed, the next call to
 * <code>doFilter()</code> will execute the servlet's <code>service()</code>
 * method itself.
 *
 * @author Craig R. McClanahan
 * @version $Id: ApplicationFilterChain.java 1078022 2011-03-04 15:52:01Z markt $
 */

final class ApplicationFilterChain implements FilterChain, CometFilterChain

第一個疑問是該過濾器鏈裡面的過濾器源於哪裡?

答案是該類裡面包含了一個ApplicationFilterConfig物件,而該物件則是個filter容器

    /**
     * Filters.
     */
    private ApplicationFilterConfig[] filters =
        new ApplicationFilterConfig[0];

 


以下是ApplicationFilterConfig類的宣告
org.apache.catalina.core.ApplicationFilterConfig

Implementation of a javax.servlet.FilterConfig useful in managing the filter instances instantiated when a web application is first started.
當web容器啟動是ApplicationFilterConfig自動例項化,它會從該web工程的web.xml檔案中讀取配置的filter資訊,然後裝進該容器
 下個疑問是它如何執行該過濾器容器裡面的filter呢?

答案是通過pos它來標識當前ApplicationFilterChain(當前過濾器鏈)執行到哪個過濾器

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


通過n來記錄當前過濾器鏈裡面擁有的過濾器數目

   /**
     * The int which gives the current number of filters in the chain.
     */
    private int n = 0;



通過addFilter方法向容器中新增一個filter(引數即為容器初始化生成的filterConfig物件)

   /**
     * Add a filter to the set of filters that will be executed in this chain.
     *
     * @param filterConfig The FilterConfig for the servlet to be executed
     */
    void addFilter(ApplicationFilterConfig filterConfig)


ApplicationFilterChain採用責任鏈設計模式達到對不同過濾器的執行
首先ApplicationFilterChain 會呼叫它重寫FilterChain的doFilter方法,然後doFilter裡面會呼叫
 internalDoFilter(request,response)方法;該方法使過濾器容器拿到每個過濾器,然後呼叫它們重寫Filter接口裡面的dofilter方法

以下是ApplicationFilterChain 裡面重寫FilterChain裡面的doFilter方法的描述

    /**
     * Invoke the next filter in this chain, passing the specified request
     * and response.  If there are no more filters in this chain, invoke
     * the <code>service()</code> method of the servlet itself.
     *
     * @param request The servlet request we are processing
     * @param response The servlet response we are creating
     *
     * @exception IOException if an input/output error occurs
     * @exception ServletException if a servlet exception occurs
     */
    @Override
    public void doFilter(ServletRequest request, ServletResponse response)


以下是internalDoFilter的部分程式碼

// Call the next filter if there is one
        if (pos < n) {
            //先拿到下個過濾器,將指標向下移動一位
            ApplicationFilterConfig filterConfig = filters[pos++];
            Filter filter = null;
            try {
                //獲取當前指向的filter例項
                filter = filterConfig.getFilter();
                support.fireInstanceEvent(InstanceEvent.BEFORE_FILTER_EVENT,
                                          filter, request, response);
               
                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)方法
                    //ApplicationFilterChain裡面的filter都實現了filter                   //介面
                    filter.doFilter(request, response, this);
                }


以下是Filter介面doFilter定義如下
 public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)

過濾器鏈裡面的filter在呼叫dofilter完成後,會繼續呼叫chain.doFilter(request,response)方法,而這個chain其實就是applicationfilterchain,所以呼叫過程又回到了上面呼叫dofilter和呼叫internalDoFilter方法,這樣執行直到裡面的過濾器全部執行

當filte都呼叫完成後,它就會初始化相應的servlet,(例如jsp資源,預設它會開啟一個 JspServlet物件)

     // We fell off the end of the chain -- call the servlet instance
        try {
            if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
                lastServicedRequest.set(request);
                lastServicedResponse.set(response);
            }

            support.fireInstanceEvent(InstanceEvent.BEFORE_SERVICE_EVENT,
                                      servlet, request, response);


舉個例子
假如訪問的是個jsp,首先開啟一個 JspServlet物件,然後該JspServlet物件會呼叫它的service方法

以下是個JspServlet的定義以及service方法的描述

public class JspServlet extends HttpServlet implements PeriodicEventListener

 public void service (HttpServletRequest request,
                             HttpServletResponse response)

 //jspFile may be configured as an init-param for this servlet instance     //該jspUri代表訪問jsp的相對路徑 
        String jspUri = jspFile;


拿到該路徑後它就會去判斷該jsp是否已經預編譯過
            boolean precompile = preCompile(request);

           serviceJspFile(request, response, jspUri, null, precompile);
如果已經編譯則直接呼叫該jsp對應的servlet的_jspService方法
否則先編譯在呼叫

假如是個html頁面的訪問則直接呼叫DefaultServlet做相應的處理

簡單的分析了下,作為一個學習筆記吧