1. 程式人生 > >Struts2 源碼分析——調結者(Dispatcher)之action請求

Struts2 源碼分析——調結者(Dispatcher)之action請求

ipp 開發人員 || shm body itl zed 工廠 一次

章節簡言

上一章筆者講到關於struts2啟動的時候加載對應的準備工作。如加載配置文件struts.xml之類的信息。而相應的這些操作都離不開Dispatcher類的幫助。如果讀者只是認為Dispatcher類的作用只有這些。那真的是大錯特錯了。所以本章筆者將繼續講到關於Dispatcher類的另一個功能。即是StrutsPrepareFilter類倆項工作中的處理request請求相關信息。在講解之前,筆者還是想把相關的信息回想一下:當項目啟動的時候,strtus2也就啟動了。然後就會去初始化對應需要的信息(這部分內容上一章已經講到了)。之後當用戶在網址上輸入訪問的URL的時候。就會進入StrutsPrepareFilter類處理request請求的功能。好了。明白是什麽到這一步就可以了。因為下面就是要講到關於這一部分的內容。

調結者的action請求

StrutsPrepareFilter類在處理request請求的時候,需要用到一個叫PrepareOperations類的幫忙。PrepareOperations類可以說是StrutsPrepareFilter類和Dispatcher類的中間人。PrepareOperations類大部分的工作都是通過Dispatcher類完成的。先讓我們看一段代碼。如下

StrutsPrepareFilter類:

技術分享
  public void init(FilterConfig filterConfig) throws ServletException {
        InitOperations init = new InitOperations();//用於初始化相關的功能操作。你可以理解為工具類一樣子。
        Dispatcher dispatcher = null;//這個類相當的重要。他的作用連接著StrutsExecuteFilter。這裏可以命名為調結者。
        try {
            FilterHostConfig config = new FilterHostConfig(filterConfig);//這裏可以理解為把filterConfig在進行封裝FilterHostConfig更為主便操作和理解。
            init.initLogging(config);//獲取名為loggerFactory的參數,並實例化這個類。一般為去用戶自定義日誌。
            dispatcher = init.initDispatcher(config);//初化調結者。這裏是重要。

            prepare = new PrepareOperations(dispatcher);
            this.excludedPatterns = init.buildExcludedPatternsList(dispatcher);//加載排除在內的action的正則表達式

            postInit(dispatcher, filterConfig);
        } finally {
            if (dispatcher != null) {
                dispatcher.cleanUpAfterInit();
            }
            init.cleanup();
        }
    }
技術分享

從上面的紅色代碼我們可以看出來,PrepareOperations類在實例化時候,接受Dispatcher類作為構造函數的參數。即是在struts2啟動加載準備工作之後初始化。那麽PrepareOperations類到底又做哪些工作呢?讓我們在看一下下面的代碼。如下

StrutsPrepareFilter類:

技術分享
 1  public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
 2 
 3         HttpServletRequest request = (HttpServletRequest) req;
 4         HttpServletResponse response = (HttpServletResponse) res;
 5 
 6         try {
 7             if (excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) {
 8                 request.setAttribute(REQUEST_EXCLUDED_FROM_ACTION_MAPPING, new Object());
 9             } else {
10                 prepare.setEncodingAndLocale(request, response);//設置請求的格式編碼。
11                 prepare.createActionContext(request, response);//action的上下文
12                 prepare.assignDispatcherToThread();//把Dispatcher放入本地線程裏面。
13                 request = prepare.wrapRequest(request);
14                 prepare.findActionMapping(request, response);//找到action映射的信息
15             }
16             chain.doFilter(request, response);
17         } finally {
18             prepare.cleanupRequest(request);
19         }
20     }
技術分享

上面代碼我們可以看出PrepareOperations類總共做了五件事情。先讓筆者簡單的講解一下:當用戶的request請求過來的時候,會判斷一下request請求是不是被排除之外的。如果是,則把REQUEST_EXCLUDED_FROM_ACTION_MAPPING常量作為KEY,object實例作為值存放在request的Attrbute裏面。這是為後面的StrutsExecuteFilter類作準備(下一章筆者會講到)。如果不是,則進行request請求處理。如下

1.設置request請求的本地化和格式編碼。實現上還是Dispatcher類在做工作。代碼如下

PrepareOperations類:

技術分享
1   /**
2      * 設置本地化和請求格式編碼
3      *
4      * @param request servlet request
5      * @param response servlet response
6      */
7     public void setEncodingAndLocale(HttpServletRequest request, HttpServletResponse response) {
8         dispatcher.prepare(request, response);
9     }
技術分享

Dispatcher類:

技術分享
 1 /**
 2      * 設置請求的本地化和格式編碼
 3      *
 4      * @param request
 5      *            The request
 6      * @param response
 7      *            The response
 8      */
 9     public void prepare(HttpServletRequest request, HttpServletResponse response) {
10         String encoding = null;
11         if (defaultEncoding != null) {
12             encoding = defaultEncoding;
13         }
14 
15         if ("XMLHttpRequest".equals(request.getHeader("X-Requested-With"))) {
16             encoding = "UTF-8";
17         }
18 
19         Locale locale = null;
20         if (defaultLocale != null) {
21             locale = LocalizedTextUtil.localeFromString(defaultLocale, request.getLocale());
22         }
23 
24         if (encoding != null) {
25             applyEncoding(request, encoding);
26         }
27 
28         if (locale != null) {
29             response.setLocale(locale);
30         }
31 
32         if (paramsWorkaroundEnabled) {
33             request.getParameter("foo"); // simply read any parameter (existing
34                                             // or not) to "prime" the request
35         }
36     }
技術分享

2.創建Action上下文(ActionContext類),並把上下文存放在本地線程(ThreadLocal)。所謂的上下文可以理解為把相同性質的業務歸為一類。而上下文就是這一類和外部相交處。所有的數據操作都可以通過他還完成。當然上下文的定義在不同的地方有不同的意思。請讀者自行找閱資料。先讓我們看一下代碼。如下

PrepareOperations類:

技術分享
 1  /**
 2      * 創建Action的上下文 ,並存在到本地線程
 3      *
 4      * @param request servlet request
 5      * @param response servlet response
 6      *
 7      * @return the action context
 8      */
 9     public ActionContext createActionContext(HttpServletRequest request, HttpServletResponse response) {
10         ActionContext ctx;
11         Integer counter = 1;
12         Integer oldCounter = (Integer) request.getAttribute(CLEANUP_RECURSION_COUNTER);
13         if (oldCounter != null) {
14             counter = oldCounter + 1;
15         }
16         
17         ActionContext oldContext = ActionContext.getContext();
18         if (oldContext != null) {
19             // 有舊的action上下文,我們可以認為有可能是跳轉。
20             ctx = new ActionContext(new HashMap<>(oldContext.getContextMap()));
21         } else {
22             ValueStack stack = dispatcher.getContainer().getInstance(ValueStackFactory.class).createValueStack();//從容器中獲得值棧工廠,並新建值棧。
23             stack.getContext().putAll(dispatcher.createContextMap(request, response, null));//創建上下MAP並合到值棧裏面去。
24             ctx = new ActionContext(stack.getContext());//用值棧的上下MAP來新建上下文
25         }
26         request.setAttribute(CLEANUP_RECURSION_COUNTER, counter);
27         ActionContext.setContext(ctx);
28         return ctx;
29     }
技術分享

從上面的紅色代碼我們就能夠看出第一次request請求就會創建一個新的action上下文(ActionContext)。同時也能明白ActionContext裏面存放大量的關於request請求對應的數據。而這些操作又離不開Dispatcher類的幫忙。最後把上下文(ActionContext類)存放到ActionContext類內部的本地線程(ThreadLocal)。代碼ActionContext.setContext(ctx)就是最好的說明。另外,上面有一點讓筆者一直不明白為什麽這麽做。覺得沒有什麽意義。即是獲得request的屬性為CLEANUP_RECURSION_COUNTER的值,然後進行計算操作的功能。筆者不得不將他理解為:用於計算request請求跳轉action的次數。就是一個request請求的生命周期通過了幾個action請求。如果不對的話,請讀者自行屏蔽。

3.把Dispatcher實例分配置到他內部的本地線程(ThreadLocal)。這一步主要是為了後面的StrutsExecuteFilter類的工作。讓我們看一下代碼吧。如下

PrepareOperations類:

1   /**
2      * dispatcher分配到Dispatcher類的本地線程
3      */
4     public void assignDispatcherToThread() {
5         Dispatcher.setInstance(dispatcher);
6     }

4.把HttpServletRequest包裝為對應的StrutsRequestWrapper或是MultiPartRequestWrapper。主要是為了方便開發人員操作處理multipart而以。這裏比較簡單。筆者不想過的解釋。

5.找到對應action映射信息(ActionMapping類)。這部分的工作筆者認為是比較重要的。因為他將是StrutsExecuteFilter類工作的核心點。那麽ActionMapping類又是什麽呢?他是struts.xml配置文件上的action信息。有了他struts2才能知道當前請求是哪一個action類。那麽相關的知識筆者會在後面的章節講到。讓我們看一下代碼。如下

PrepareOperations類:

技術分享
 1  public ActionMapping findActionMapping(HttpServletRequest request, HttpServletResponse response, boolean forceLookup) {
 2         ActionMapping mapping = (ActionMapping) request.getAttribute(STRUTS_ACTION_MAPPING_KEY);
 3         if (mapping == null || forceLookup) {
 4             try {
 5                 mapping = dispatcher.getContainer().getInstance(ActionMapper.class).getMapping(request, dispatcher.getConfigurationManager());
 6                 if (mapping != null) {
 7                     request.setAttribute(STRUTS_ACTION_MAPPING_KEY, mapping);
 8                 }
 9             } catch (Exception ex) {
10                 dispatcher.sendError(request, response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex);
11             }
12         }
13 
14         return mapping;
15     }
技術分享

當筆者看到紅色代碼的時候,不得不說一句。看!又離不開Dispatcher類。同時值得註意是的ActionMapper類。所有的struts.xml配置文件的action信息都在這個類上面。即是可以通過ActionMapper類找到對應的action映射信息(ActionMapping)。從而找到對應的action類(用戶定義的action)。另外代碼request.setAttribute(STRUTS_ACTION_MAPPING_KEY, mapping)這邊所做的事情。功能意思大家都能看得出來。那為什麽這麽做。主要還是因為後面的StrutsExecuteFilter類要用到。

從上面的五個事件中。筆者至少知道一點。Dispatcher類的工作真的很重要。而PrepareOperations類大部分只是一個中間人而以。當然這是筆者自己的理解。

Dispatcher的結束處理

筆者本來想把這個知識點做一個章節來講。可是想太少了。為什麽是結束工作呢?不管是struts2啟動的準備工作。還是啟動成功後的action請求工作。struts2都會在工作結束之進行一些處理。先看一下struts2啟動的準備工作成完之後的處理。如下

StrutsPrepareFilter類的init方法:

1  if (dispatcher != null) {
2                 dispatcher.cleanUpAfterInit();
3             }
4             init.cleanup();

Dispatcher類:

1     public void cleanUpAfterInit() {
2         if (LOG.isDebugEnabled()) {
3             LOG.debug("Cleaning up resources used to init Dispatcher");
4         }
5         ContainerHolder.clear();
6     }

InitOperations類:

1  public void cleanup() {
2         ActionContext.setContext(null);
3     }

從上面的代碼中我們可以看一個叫ContainerHolder類。這個類主要是用於存放Container容器。而上面在struts2啟動加載相關信息的準備工作結束之後。把Container容器給冊除了。同時也去掉了上下文(ActionContext類)。

讓我們看一下request請求結束後做了什麽。如下

StrutsPrepareFilter類的doFilter方法:

1 prepare.cleanupRequest(request);

PrepareOperations類:

技術分享
 1 public void cleanupRequest(HttpServletRequest request) {
 2         Integer counterVal = (Integer) request.getAttribute(CLEANUP_RECURSION_COUNTER);
 3         if (counterVal != null) {
 4             counterVal -= 1;
 5             request.setAttribute(CLEANUP_RECURSION_COUNTER, counterVal);
 6             if (counterVal > 0 ) {
 7                 LOG.debug("skipping cleanup counter={}", counterVal);
 8                 return;
 9             }
10         }
11         // always clean up the thread request, even if an action hasn‘t been executed
12         try {
13             dispatcher.cleanUpRequest(request);
14         } finally {
15             ActionContext.setContext(null);
16             Dispatcher.setInstance(null);
17             devModeOverride.remove();
18         }
19     }
技術分享

Dispatcher類:

技術分享
1     public void cleanUpRequest(HttpServletRequest request) {
2         ContainerHolder.clear();
3         if (!(request instanceof MultiPartRequestWrapper)) {
4             return;
5         }
6         MultiPartRequestWrapper multiWrapper = (MultiPartRequestWrapper) request;
7         multiWrapper.cleanUp();
8     }
技術分享

從上面的紅色代碼我們知道他在request請求結束之後,把Container容器給冊除了。本地線程的Dispatcher類的實例刪除了。上下文(ActionContext類)刪除了。

到了這裏筆者就明白了一點:

1.struts2啟動的時候,加載相關的配置信息。然後生成Dispatcher類的實例,初始化Container容器並把Dispatcher類的實例存放在PrepareOperations類裏面。那麽struts2的啟動準備工作結束,啟動成功。這時在把對應生成的Container容器和上下文(ActionContext類)刪除掉。

註意:上下文(ActionContext類)在這個時候可能是沒有生成的。但他做了刪除的工作。

2.啟動成功之後。用戶開始請求action。這個時候struts2會初始化一個新的Container容器和上下文(ActionContext類),分配Dispatcher類的實例到本地線程(ThreadLocal)中,找到對應的request請求的action映射(ActionMapping)並開始處理用戶對應的action請求。action請求成功之後,會刪除對應的Container容器、本地線程的Dispatcher類的實例、上下文(ActionContext類)。即是一個請求,一個Container容器,一個上下文(ActionContext類)。一個本地線程的Dispatcher類的實例。

註意:刪除Dispatcher類的實例是本地線程的。而不是PrepareOperations類的實例。(讀者不要搞錯了。然後一直會去想:刪除了Dispatcher類的實例。又在哪裏創建了。不好意思。啟動的時候就創建,之後就在也沒有了。)

本章總結

關於StrutsPrepareFilter類的工作。還有Dispatcher的作用。相信讀者看到這裏的時候,心裏都會有一個大概念的想法。當然筆者並非會專業的寫書者。所以可能有些讀者或多或少很難去理解筆者的意思。請見諒。關於調結者(Dispatcher)之倆章,主要就是想讓讀者明白Dispatcher做了什麽。和StrutsPrepareFilter類有什麽關系。為後面學習StrutsExecuteFilter類的工作做準備。

Struts2 源碼分析——調結者(Dispatcher)之action請求