Struts2 源碼分析——Result類實例


本章簡言

上一章筆者講到關於DefaultActionInvocation類執行action的相關知識。我們清楚的知道在執行action類實例之后會相關處理返回的結果。而這章筆者將對處理結果相關的內容進行講解。筆者叫他們為Result類實例。如果還記得在上一章最后筆者說可以把處理action執行的結果簡單的理解處理網頁。而且還用紅色標識。實際是處理跟表現層有關的內容。而不是頁面上的內容。如HTML。即是MVC里面的C到V的內容。當然這還關系到配置文件里面的result元素節點信息。如strtus.xml文件等里面的action元素節點下的result信息。

Result類實例的來源

我們都知道action節點下會很多result節點。在筆者的腦中好像result節點常用的有倆個屬性節點:name和type。那么result元素節點下到底有些什么呢?這就不得不去看一下相關的DTD文件。請找到對應的struts-2.5.dtd文件。查看一下下面的內容。

<!ELEMENT result (#PCDATA|param)*>
<!ATTLIST result
    name CDATA #IMPLIED
    type CDATA #IMPLIED
>

好了。好像只有上面講到的倆個屬性節點。但是可以看到他還有一個子節點param。那么為什么要講到result節點呢?主要是上一章出筆者講到action類實例執行之后,會去處理結果。哪怕是返回String類型的結果,最終也會獲得對應的Result類實例並執行處理結果。關於Result接口的源碼卻顯的非常的簡單。如下

1 public interface Result extends Serializable {
2 
3     public void execute(ActionInvocation invocation) throws Exception;
4 
5 }

使用過struts2的人都知道result節點的type值有很多種。type值更是用來表示返回結果的類型。即是type值有多少種,返回的結果就有多少種。如果讓筆者來記的話。說真的筆者輸了。筆者主要是通過源碼文件才能知道到底有多少種。找到源碼文件struts-default.xml查看里面的package元素節點下的result-types節點。如下

<result-types>
            <result-type name="chain" class="com.opensymphony.xwork2.ActionChainResult"/>
            <result-type name="dispatcher" class="org.apache.struts2.result.ServletDispatcherResult" default="true"/>
            <result-type name="freemarker" class="org.apache.struts2.views.freemarker.FreemarkerResult"/>
            <result-type name="httpheader" class="org.apache.struts2.result.HttpHeaderResult"/>
            <result-type name="redirect" class="org.apache.struts2.result.ServletRedirectResult"/>
            <result-type name="redirectAction" class="org.apache.struts2.result.ServletActionRedirectResult"/>
            <result-type name="stream" class="org.apache.struts2.result.StreamResult"/>
            <result-type name="velocity" class="org.apache.struts2.result.VelocityResult"/>
            <result-type name="xslt" class="org.apache.struts2.views.xslt.XSLTResult"/>
            <result-type name="plainText" class="org.apache.struts2.result.PlainTextResult" />
            <result-type name="postback" class="org.apache.struts2.result.PostbackResult" />
        </result-types>

好吧。struts2為我們提供了11種。認真的查看源碼的人會看到一個屬性default。如上面的name為“dispatcher”的result-types的default屬性值為true。實際這個值表示為默認使用dispatcher類型的返回結果。同時我們也看到每一個類型對應的類。這個時候筆者就會有一個問題。action執行之后,他是什么樣根據String類型的結果值找到對應的Result類實例。筆者就不得不去找上一章解了到的源碼。即是createResult方法的源碼。如果沒有記錯的話。從createResult方法我們知道Result類實例是通過ObjectFactory類的buildResult方法得到的。對應的源碼如下

ObjectFactory類:

public Result buildResult(ResultConfig resultConfig, Map<String, Object> extraContext) throws Exception {
        return resultFactory.buildResult(resultConfig, extraContext);
    }

 看到上面的代碼我們就清楚要找ResultFactory接口的實例類。那么實現ResultFactory接口的類有StrutsResultFactory類和DefaultResultFactory類。到底是哪一個呢?這個問題當年筆者找了很久就是不明白他到底是什么知道用哪一個。當年我去查看了初始化Container容器時,發現好像關於了ResultFactory接口對應的類,只有注入一個DefaultResultFactory類。如下源碼。可是項目運行起來跟蹤下去。發現用的是StrutsResultFactory類實例。筆者悶了很久。按道理應該是DefaultResultFactory類實例。可是為什么用的是StrutsResultFactory類呢?后來又在struts-default.xml文件里面找到一個。即是StrutsResultFactory類。如下源嗎。

DefaultConfiguration類的createBootstrapContainer方法。

builder.factory(ResultFactory.class, DefaultResultFactory.class, Scope.SINGLETON);

struts-default.xml文件:

<bean type="com.opensymphony.xwork2.factory.ResultFactory" name="struts" class="org.apache.struts2.factory.StrutsResultFactory" />

看過XmlConfigurationProvider類的源碼就明白這邊ResultFactory接口注入的名字值是“struts”。而前面初始化Container容器注入的名字值是“default”。所以Container容器里面有倆個關於ResultFactory接口的注冊類。看樣子光是找到了struts-default.xml文件里面ResultFactory接口對應的StrutsResultFactory類。還是不能說明為什么要用StrutsResultFactory類實例。另一個原因是因為筆者認為應該是調用名字值為“default”的注冊類。為什么這樣子講呢?在於ObjectFactory類里面,setResultFactory方法上面用的是@Inject。即是用默認名字值為default的注冊類。筆者也不清楚到底是經過多少時間。最后在DefaultBeanSelectionProvider類的源碼里面找到了原因。原來在Container容器里面,名字值為default的注冊類被名字值為struts的注冊類替換了。源碼請讀者自己行查看吧。

既然ObjectFactory類可以用來生成對應的Result類實例。那么他做了些什么呢?這是筆者想要了解的關鍵點。上面ObjectFactory類的buildResult方法源碼里面我們至少知道他是在調用了StrutsResultFactory類實例。現在我們只要去找StrutsResultFactory類源碼就可以明白他做了什么。如下

 1  public Result buildResult(ResultConfig resultConfig, Map<String, Object> extraContext) throws Exception {
 2         String resultClassName = resultConfig.getClassName();
 3         Result result = null;
 4 
 5         if (resultClassName != null) {
 6             result = (Result) objectFactory.buildBean(resultClassName, extraContext);  7             Map<String, String> params = resultConfig.getParams();
 8             if (params != null) {
 9                 setParameters(extraContext, result, params);
10             }
11         }
12         return result;
13     }

我們可以看出首先他是根據result節點的配置信息來獲得對應的類名。即是上面講到的11種結果類型對應的類。然后生成Result類實例。哎!又跑回ObjectFactory類了。這次卻是生成對象。在把對象轉化為Result類實例。接着就是設置對應的參數了。最后返回Result類實例。

Result類實例的處理

經過上面的過程之后。我們可以得到對應的Result類實例。相信大家都知道Result接口對的類實例就有11種。筆者當然也不可能在這里把11種都哪出來講。筆者只能把一些比較常用的Result類實例拿出來講。

1.ServletDispatcherResult類是strust2默認的返回結果。即是dispatcher類型。ServletDispatcherResult類實現於StrutsResultSupport類。讓我們一下StrutsResultSupport類的源碼吧。知道他做了些什么。如下

1   public void execute(ActionInvocation invocation) throws Exception {
2         lastFinalLocation = conditionalParse(location, invocation);
3         doExecute(lastFinalLocation, invocation);
4     }

上面就是Result類實例執行的入口方法。StrutsResultSupport類把當前location進行了處理。也是把要轉跳到哪一個網頁。其中conditionalParse方法是用於處理表達式。如動態返回結果。不管如何最后是一個跳轉的URL。然后調用doExecute抽象方法。好了。讓我們看一下ServletDispatcherResult類doExecute方法的源碼。

 1  public void doExecute(String finalLocation, ActionInvocation invocation) throws Exception {
 2         LOG.debug("Forwarding to location: {}", finalLocation);
 3 
 4         PageContext pageContext = ServletActionContext.getPageContext();
 5 
 6         if (pageContext != null) {
 7             pageContext.include(finalLocation);
 8         } else {
 9             HttpServletRequest request = ServletActionContext.getRequest();
10             HttpServletResponse response = ServletActionContext.getResponse();
11             RequestDispatcher dispatcher = request.getRequestDispatcher(finalLocation);
12 
13             //處理跳轉URL上面的參數。並把他增加action請求的參數集合里面。
14             if (StringUtils.isNotEmpty(finalLocation) && finalLocation.indexOf("?") > 0) {
15                 String queryString = finalLocation.substring(finalLocation.indexOf("?") + 1);
16                 Map<String, Object> parameters = getParameters(invocation);
17                 Map<String, Object> queryParams = urlHelper.parseQueryString(queryString, true);
18                 if (queryParams != null && !queryParams.isEmpty())
19                     parameters.putAll(queryParams);
20             }
21 
22             // 如果不存在網址的話,就跳出404
23             if (dispatcher == null) {
24                 response.sendError(404, "result '" + finalLocation + "' not found");
25                 return;
26             }
27 
28             //是否是一個action tag 就是網頁里面的一個包含action
29             Boolean insideActionTag = (Boolean) ObjectUtils.defaultIfNull(request.getAttribute(StrutsStatics.STRUTS_ACTION_TAG_INVOCATION), Boolean.FALSE);
30 
31             //最后跳轉
32             if (!insideActionTag && !response.isCommitted() && (request.getAttribute("javax.servlet.include.servlet_path") == null)) {
33                 request.setAttribute("struts.view_uri", finalLocation);
34                 request.setAttribute("struts.request_uri", request.getRequestURI());
35 
36                 dispatcher.forward(request, response);
37             } else {
38                 dispatcher.include(request, response);
39             }
40         }
41     }

這段代碼很簡單,處理相關的參數。最后跳轉配置里面對應的網頁。筆者就不多說了。

2.ServletRedirectResult類是也是Result接口的實例類。即是redirect類型。相信看名字就可以知道用於重定向。也實現於StrutsResultSupport類。筆者也看了源碼。真不知道要講些什么。主要是判斷要重定向的finalLocation是不是完全的URL。如果不是就生成對應的完全URL。然后在去找result節點的參數,並把他的參數一起放到requestParameters集合里面。這樣子下面才可以生成對應的帶參數的URL。如http://xxxx/sss?xxx=xxx。最后重定向。

ServletRedirectResult類:

 1 protected void doExecute(String finalLocation, ActionInvocation invocation) throws Exception {
 2         ActionContext ctx = invocation.getInvocationContext();
 3         HttpServletRequest request = (HttpServletRequest) ctx.get(ServletActionContext.HTTP_REQUEST);
 4         HttpServletResponse response = (HttpServletResponse) ctx.get(ServletActionContext.HTTP_RESPONSE);
 5 
 6         //判斷是否為完全的URL。true不是完全的URL。
 7         //如果不是完全的URL就生成完全的URL
 8         if (isPathUrl(finalLocation)) {
 9             if (!finalLocation.startsWith("/")) {
10                 ActionMapping mapping = actionMapper.getMapping(request, Dispatcher.getInstance().getConfigurationManager());
11                 String namespace = null;
12                 if (mapping != null) {
13                     namespace = mapping.getNamespace();
14                 }
15 
16                 if ((namespace != null) && (namespace.length() > 0) && (!"/".equals(namespace))) {
17                     finalLocation = namespace + "/" + finalLocation;
18                 } else {
19                     finalLocation = "/" + finalLocation;
20                 }
21             }
22 
23             // if the URL's are relative to the servlet context, append the servlet context path
24             if (prependServletContext && (request.getContextPath() != null) && (request.getContextPath().length() > 0)) {
25                 finalLocation = request.getContextPath() + finalLocation;
26             }
27         }
28         
29         //把result節點上的參數一起放到請求參數集合里面,
30         ResultConfig resultConfig = invocation.getProxy().getConfig().getResults().get(invocation.getResultCode());
31         if (resultConfig != null) {
32             Map<String, String> resultConfigParams = resultConfig.getParams();
33 
34             List<String> prohibitedResultParams = getProhibitedResultParams();
35             for (Map.Entry<String, String> e : resultConfigParams.entrySet()) {
36                 if (!prohibitedResultParams.contains(e.getKey())) {
37                     Collection<String> values = conditionalParseCollection(e.getValue(), invocation, suppressEmptyParameters);
38                     if (!suppressEmptyParameters || !values.isEmpty()) {
39                         requestParameters.put(e.getKey(), values);
40                     }
41                 }
42             }
43         }
44 
45         //根據上面的參數來生成帶有參數的URL,
46         StringBuilder tmpLocation = new StringBuilder(finalLocation);
47         urlHelper.buildParametersString(requestParameters, tmpLocation, "&");
48 
49         // add the anchor
50         if (anchor != null) {
51             tmpLocation.append('#').append(anchor);
52         }
53 
54         finalLocation = response.encodeRedirectURL(tmpLocation.toString());
55 
56         LOG.debug("Redirecting to finalLocation: {}", finalLocation);
57 
58         sendRedirect(response, finalLocation);//重定向到對應的URL
59     }

3.ServletActionRedirectResult類是ServletRedirectResult類的一個子類。 即是redirectAction類型。這個類的做法就是把相應的action的URL處理好。在把URL轉給子類ServletRedirectResult去處理。當然要去獲得對應的action名字,空間命名,方法。然后生成完全的action的URL。

 1 public void execute(ActionInvocation invocation) throws Exception {
 2         actionName = conditionalParse(actionName, invocation);
 3         if (namespace == null) {
 4             namespace = invocation.getProxy().getNamespace();
 5         } else {
 6             namespace = conditionalParse(namespace, invocation);
 7         }
 8         if (method == null) {
 9             method = "";
10         } else {
11             method = conditionalParse(method, invocation);
12         }
13 
14         String tmpLocation = actionMapper.getUriFromActionMapping(new ActionMapping(actionName, namespace, method, null));
15 
16         setLocation(tmpLocation);
17 
18         super.execute(invocation);
19     }

筆者就介紹這三個類吧。相信讀者也知道要去找哪里類了。我們可以看到action類實例執行結束之后。就會去獲得對應的Result類實例。當然這里Result類實例具體是哪個類。就有result節點上的配置信息來決定了。然后進行要應的處理。

本章總結

本章講到Result類實例的知識點。知道了struts2內部有幾種的結果類型。每一個類型是什么處理得到的結果信息。是跳轉,還是重定向,一切都是strust.xml文件的result節點的信息了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM