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

Struts2 源碼分析——調結者(Dispatcher)之執行action

eval namespace ges else 準備工作 常量 final 當前 create

章節簡言

上一章筆者寫關於Dispatcher類如何處理接受來的request請求。當然讀者們也知道他並非正真的執行action操作。他只是在執行action操作之前的準備工作。那麽誰才是正真的執行action呢?本章筆者就帶大家來看看StrutsExecuteFilter類的工作。在理解StrutsExecuteFilter類的工作之前,筆者還是希望大家回顧一下前一章講到的request請求工作。為什麽這樣子講呢?可以說StrutsExecuteFilter類的工作是建立在StrutsPrepareFilter類基礎上運行的。先相信這一點筆者不需要聲明了。筆者為了更好的理解小小的做一個張圖片。如下

技術分享

從上面的圖片我們就是很清楚StrutsPrepareFilter類做了哪些工作。而圖上的上五點對於後面的StrutsExecuteFilter類來講是非常重要的。雖然我在前面幾章也提過StrutsExecuteFilter類的知識。《Struts2 源碼分析——過濾器(Filter)》章節裏面也講過。只是很簡單的略講一下。並沒有對他進特別的講。主要是筆者認為不了解StrutsPrepareFilter類的工作的情況下,去了解StrutsExecuteFilter類的話。是一件比較吃力的事情。好了。筆者就不多說了。讓我們進入本章的內容吧。

調結者的執行action

StrutsExecuteFilter類的工作就是執行對應的action請求。StrutsExecuteFilter類的工作還需要有一個叫ExecuteOperations類的幫助。如果看過源碼的朋友都知道,StrutsExecuteFilter類的代碼裏用了ExecuteOperations類的倆個方法。一個是:executeStaticResourceRequest方法。一個是:executeAction方法。光從字名面上我就知道他們的功能。executeStaticResourceRequest是執行靜態資源請求。如JS文件,css文件等。而executeAction就是執行action請求。即是筆者想要講的重點。好了。還是讓我們先看一下StrutsExecuteFilter類代碼吧。如下部分代碼

StrutsExecuteFilter類:

技術分享
 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         if (excludeUrl(request)) {//用於判斷是否在排除的action之內。如果是就跳過。
 7             chain.doFilter(request, response);
 8             return;
 9         }
10 
11         if (execute == null) {
12             lazyInit();//初始化相關的信息類。
13         }
14 
15         ActionMapping mapping = prepare.findActionMapping(request, response);//找到ActionMapping實例
16 
17   
18         Integer recursionCounter = (Integer) request.getAttribute(PrepareOperations.CLEANUP_RECURSION_COUNTER);
19 
20         if (mapping == null || recursionCounter > 1) {
21             boolean handled = execute.executeStaticResourceRequest(request, response);//執行請求css,js文件。並返回是否成功。
22             if (!handled) {
23                 chain.doFilter(request, response);
24             }
25         } else {
26             execute.executeAction(request, response, mapping);//執行action請求,重要部分
27         }
28     }
技術分享

根據上面的紅色的代碼,讓筆者講一下總共做了幾件事件。

1.判斷當前的request請求是不是被排在外。如果就跳過去。(筆者不想過講,太簡單了)

2.判斷是否存在ExecuteOperations類的實例。如果沒有就初始化。相關的代碼如下。

StrutsExecuteFilter類:

技術分享
 1  /**
 2      * 加載並初始化
 3      */
 4     protected synchronized void lazyInit() {
 5         if (execute == null) {
 6             InitOperations init = new InitOperations();//用於初始化的功能類
 7             Dispatcher dispatcher = init.findDispatcherOnThread();//StrutsPrepareFilter類的時候,就把Dispatcher實例存放在本地線程裏面。這是只是把他拿出來。
 8             init.initStaticContentLoader(new FilterHostConfig(filterConfig), dispatcher);//初始化用於加載css,js文件的加載類。
 9             
10             prepare = new PrepareOperations(dispatcher);
11             execute = new ExecuteOperations(dispatcher);
12         }
13 
14     }
技術分享

看了代碼我們就知道StrutsExecuteFilter類的lazyInit方法做了什麽。

1).找到對應的Dispatcher實例。那麽Dispatcher實例在哪裏初始化呢?這就是StrutsPrepareFilter類的裏面。(如果不理解的讀者,請轉至Struts2 源碼分析——調結者(Dispatcher)之action請求的章節)

2).初始化StaticContentLoader類。即是用於加載JS,CSS文件等類似的加載類。

3).初始化相關對應的PrepareOperations類和ExecuteOperations類。為了下面執行action請求準備。其中ExecuteOperations類很重要。用於執行action和加載JS,CSS文件類似的調動者。

3.找到對應的action映射(ActionMapping類)。可以說沒有action映射就沒有辦法執行相關的action操作。讓我們看一下findActionMapping方法的代碼吧。

PrepareOperations類:

技術分享
 public ActionMapping findActionMapping(HttpServletRequest request, HttpServletResponse response, boolean forceLookup) {
        ActionMapping mapping = (ActionMapping) request.getAttribute(STRUTS_ACTION_MAPPING_KEY);
        if (mapping == null || forceLookup) {
            try {
                mapping = dispatcher.getContainer().getInstance(ActionMapper.class).getMapping(request, dispatcher.getConfigurationManager());
                if (mapping != null) {
                    request.setAttribute(STRUTS_ACTION_MAPPING_KEY, mapping);
                }
            } catch (Exception ex) {
                dispatcher.sendError(request, response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex);
            }
        }

        return mapping;
    }
技術分享

先從request請求中找到以STRUTS_ACTION_MAPPING_KEY常量為Key的ActionMapping值。如果不存在,則通過Container容器中找到的ActionMapper實例,並通過ActionMapper實例找到對應的Action映射,並存於request請求。其Key值為STRUTS_ACTION_MAPPING_KEY常量。相信讀者又看Dispatcher類的實例了。又跟他有關系。關於這一步其實在StrutsPrepareFilter類工作的時候就已經做過一次了。(在這裏用到ActionMapper類。關於他的作用讀者目前只要知道所有的struts.xml上的配置action信息都在裏面。筆者後面說找一個章節講他)

4.如果沒有找到對應的action映射(ActionMapping類)或action跳越的數量>1就是執行加載JS,CSS文件的加載類。否則就是執行action。實話實說筆者真不知道recursionCounter > 1是什麽個意思。我只能把他理解為跳轉的action數。筆者也做了相關通的實驗就是希望看出一些事端。可惜失敗了。

先看一下executeStaticResourceRequest方法吧。對於executeStaticResourceRequest方法。筆者在上面就講到了。他是用於加載相關的靜態資源。如CSS文件,JS文件。這些文件是在JAR裏面的。我們有時候struts2相關的UI的TAG的時候,就要加載對應的CSS文件,和JS文件吧。這個時候他就啟作用了。讓我們看一下代碼吧。

ExecuteOperations類:

技術分享
 1     public boolean executeStaticResourceRequest(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
 2         // 如果沒有找到對應的action,我們應該看一下是不是請求靜態資源
 3         String resourcePath = RequestUtils.getServletPath(request);
 4 
 5         if ("".equals(resourcePath) && null != request.getPathInfo()) {
 6             resourcePath = request.getPathInfo();
 7         }
 8 
 9         StaticContentLoader staticResourceLoader = dispatcher.getContainer().getInstance(StaticContentLoader.class);
10         if (staticResourceLoader.canHandle(resourcePath)) {
11             staticResourceLoader.findStaticResource(resourcePath, request, response);
12             return true;
13 
14         } else {
15             // 如果不是的話,就表示他是一個普通的請求
16             return false;
17         }
18     }
技術分享

因為這部分不是筆者這系列要講的重點。如果有興趣的讀者可以自行繼續研發下去。我們可以看又是跟Dispatcher類的實例有關系。相信讀者這個時候很能明白筆者為什麽說Dispatcher類很重要。很能做很多事情。

關於執行action的部分就在executeAction方法裏面。讓我們看一下代碼吧。

ExecuteOperations類:

1  public void executeAction(HttpServletRequest request, HttpServletResponse response, ActionMapping mapping) throws ServletException {
2         dispatcher.serviceAction(request, response, mapping);
3     }

好吧。我有一種打人的沖動。Dispatcher類的實例又出現。執行request請求的action也是Dispatcher類的實例來完成的。既然如此讓我們看一下代碼吧。如下

技術分享
 1 public void serviceAction(HttpServletRequest request, HttpServletResponse response, ActionMapping mapping)
 2             throws ServletException {
 3 
 4         Map<String, Object> extraContext = createContextMap(request, response, mapping);
 5 
 6         //如果之前就有了值棧,就是新建一個新的值棧,放入extraContext
 7         ValueStack stack = (ValueStack) request.getAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY);
 8         boolean nullStack = stack == null;
 9         if (nullStack) {
10             ActionContext ctx = ActionContext.getContext();
11             if (ctx != null) {
12                 stack = ctx.getValueStack();
13             }
14         }
15         if (stack != null) {
16             extraContext.put(ActionContext.VALUE_STACK, valueStackFactory.createValueStack(stack));
17         }
18 
19         String timerKey = "Handling request from Dispatcher";
20         try {
21             UtilTimerStack.push(timerKey);
22             String namespace = mapping.getNamespace();//獲得request請求裏面的命名空間,即是struts.xml是的package節點元素
23             String name = mapping.getName();//獲得request請求裏面的action名
24             String method = mapping.getMethod();//要執行action的方法
25 
26             ActionProxy proxy = getContainer().getInstance(ActionProxyFactory.class).createActionProxy(namespace, name,
27                     method, extraContext, true, false);//獲得action的代理
28 
29             request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, proxy.getInvocation().getStack());
30 
31             // 如果action映射是直接就跳轉到網頁的話,
32             if (mapping.getResult() != null) {
33                 Result result = mapping.getResult();
34                 result.execute(proxy.getInvocation());
35             } else {
36                 proxy.execute();//這裏就是執行action
37             }
38 
39             
40             
41             if (!nullStack) {
42                 request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, stack);
43             }
44         } catch (ConfigurationException e) {
45             logConfigurationException(request, e);
46             sendError(request, response, HttpServletResponse.SC_NOT_FOUND, e);
47         } catch (Exception e) {
48             if (handleException || devMode) {
49                 sendError(request, response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e);
50             } else {
51                 throw new ServletException(e);
52             }
53         } finally {
54             UtilTimerStack.pop(timerKey);
55         }
56     }
技術分享

從上面的代碼就能看出在執行action的內部還需要用到一個叫ActionProxy類。關於這部分知識,筆者其實這裏不想講的很細。主要這部分的知識太多了。但這裏筆者還是希望為後面的章節做好準備。ActionProxy類可以理解他是一個代理。他的主要目地就是根據action映射得到的信息,尋找對應action類實例,然後執行對應的方法。其中包括加載對應的攔截器,初始化相應的結果。而這段代碼中,在ActionProxy類的execute()方法的時候,還作了相應的判斷。即是是否直接回返結果。其次還有在講到一個關於值棧的知識。這裏在獲得ActionProxy類實例的時候,需要得到對應值棧的信息。但是不管如何,最後一定會把request請求的值棧重新更新一下。ValueStack(值棧)的作用相信大家都懂。我就不做過多的講解了。

本章總結

可以說相關Dispatcher類的知識點,到本章節算是結束了。筆者把Dispatcher類的功能分為三點:一是加載struts2運行的必要條件信息;二是初始化action請求需要的信息;三是執行request請求對應的action。而關於核心機制圖片的橙色部分的工作大部分筆者都有體現出來。而後面的章節都是為了這三個功能點進行的。所以希望讀者能理解這三個功能。

Struts2 源碼分析——調結者(Dispatcher)之執行action