Struts2 源碼分析——調結者(Dispatcher)之執行action
章節簡言 |
上一章筆者寫關於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