posts - 19, comments - 53, trackbacks - 0, articles - 283
            BlogJava :: 首頁(yè) :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理

          Struts2Result列為一個(gè)獨(dú)立的層次,可以說(shuō)是整個(gè)Struts2Action層架構(gòu)設(shè)計(jì)中的另外一個(gè)精華所在。Result之所以成為一個(gè)層次,其實(shí)

          是為了解決
          MVC框架中,如何從Control層轉(zhuǎn)向View層這樣一個(gè)問(wèn)題而存在的

           

          struts2-core.jar/struts-default.xml中,我們可以找到關(guān)于result-type的一些配置信息,從中可以看出struts2組件默認(rèn)為我們提供了這
          result-type

                 <result-types>

                      <result-type name="chain" class="com.opensymphony.xwork2.ActionChainResult"/>

                      <result-type name="dispatcher" class="org.apache.struts2.dispatcher.ServletDispatcherResult" default="true"/>

                      <result-type name="freemarker" class="org.apache.struts2.views.freemarker.FreemarkerResult"/>

                      <result-type name="httpheader" class="org.apache.struts2.dispatcher.HttpHeaderResult"/>

                      <result-type name="redirect" class="org.apache.struts2.dispatcher.ServletRedirectResult"/>

                      <result-type name="redirectAction" class="org.apache.struts2.dispatcher.ServletActionRedirectResult"/>

                      <result-type name="stream" class="org.apache.struts2.dispatcher.StreamResult"/>

                      <result-type name="velocity" class="org.apache.struts2.dispatcher.VelocityResult"/>

                      <result-type name="xslt" class="org.apache.struts2.views.xslt.XSLTResult"/>

                      <result-type name="plainText" class="org.apache.struts2.dispatcher.PlainTextResult" />

                      <!-- Deprecated name form scheduled for removal in Struts 2.1.0. The camelCase versions are preferred. See ww-1707 -->

                      <result-type name="redirect-action" class="org.apache.struts2.dispatcher.ServletActionRedirectResult"/>

                      <result-type name="plaintext" class="org.apache.struts2.dispatcher.PlainTextResult" />

                  </result-types>



          封裝跳轉(zhuǎn)邏輯

            Result的首要職責(zé),是封裝Action層到View層的跳轉(zhuǎn)邏輯。之前我們已經(jīng)反復(fù)提到,Struts2Action是一個(gè)與Web容器無(wú)關(guān)的POJO。所以,在Action執(zhí)行完畢之后,框架需要把代碼的執(zhí)行權(quán)重新交還給Web容器,并轉(zhuǎn)向到相應(yīng)的頁(yè)面或者其他類(lèi)型的View層。而這個(gè)跳轉(zhuǎn)邏輯,就由Result來(lái)完成。這樣,好處也是顯而易見(jiàn)的,對(duì)Action屏蔽任何Web容器的相關(guān)信息,使得每個(gè)層次更加清晰。

            View層的顯示類(lèi)型非常多,有最常見(jiàn)的JSP、當(dāng)下非常流行的Freemarker/Velocity模板、Redirect到一個(gè)新的地址、文本流、圖片流、甚至是JSON對(duì)象等等。所以Result層的獨(dú)立存在,就能夠?qū)@些顯示類(lèi)型進(jìn)行區(qū)分,并封裝合理的跳轉(zhuǎn)邏輯。

            以JSP轉(zhuǎn)向?yàn)槔?/span>Struts2自帶的ServletDispatcherResult中就存在著核心的JSP跳轉(zhuǎn)邏輯:

          常用的Result 

            接下來(lái),大致介紹一下Struts2內(nèi)部已經(jīng)實(shí)現(xiàn)的Result,并看看他們是如何工作的。

            dispatcher

          Xml代碼

          <result-type name="dispatcher" class="org.apache.struts2.dispatcher.ServletDispatcherResult" default="true"/>

            dispatcher主要用于返回JSPHTML等以頁(yè)面為基礎(chǔ)View視圖,這個(gè)也是Struts2默認(rèn)的Result類(lèi)型。在使用dispatcher時(shí),唯一需要指定的,是JSP或者HTML頁(yè)面的位置,這個(gè)位置將被用于定位返回的頁(yè)面:

          Xml代碼

          <result name="success">/index.jsp</result>

            而Struts2本身也沒(méi)有對(duì)dispatcher做出什么特殊的處理,只是簡(jiǎn)單的使用Servlet API進(jìn)行forward

            freemarker / velocity

          Xml代碼
          <result-type name="freemarker" class="org.apache.struts2.views.freemarker.FreemarkerResult"/> 
          <result-type name="velocity" class="org.apache.struts2.dispatcher.VelocityResult"/> 

            隨著模板技術(shù)的越來(lái)越流行,使用Freemarker或者Velocity模板進(jìn)行View層展示的開(kāi)發(fā)者越來(lái)越多。Struts2同樣為模板作為Result做出了支持。由于模板的顯示需要模板(Template)與數(shù)據(jù)(Model)的緊密配合,所以在Struts2中,這兩個(gè)Result的主要工作是為模板準(zhǔn)備數(shù)據(jù)。

            以Freemarker為例,我們來(lái)看看它是如何為模板準(zhǔn)備數(shù)據(jù)的:

          Java代碼
          public void doExecute(String location, ActionInvocation invocation) throws IOException, TemplateException {  
              this.location = location;  
              this.invocation = invocation;  
              this.configuration = getConfiguration();  
              this.wrapper = getObjectWrapper();  
           
              //
          獲取模板的位置  
              if (!location.startsWith("/")) {  
                  ActionContext ctx = invocation.getInvocationContext();  
                  HttpServletRequest req = (HttpServletRequest) ctx.get(ServletActionContext.HTTP_REQUEST);  
                  String base = ResourceUtil.getResourceBase(req);  
                  location = base + "/" + location;  
              }  
           
              //
          得到模板  
              Template template = configuration.getTemplate(location, deduceLocale());  
              //
          為模板準(zhǔn)備數(shù)據(jù)  
              TemplateModel model = createModel();  
           
              //
          根據(jù)模板和數(shù)據(jù)進(jìn)行輸出  
              // Give subclasses a chance to hook into preprocessing  
              if (preTemplateProcess(template, model)) {  
                  try {  
                      // Process the template  
                      template.process(model, getWriter());  
                  } finally {  
                      // Give subclasses a chance to hook into postprocessing  
                      postTemplateProcess(template, model);  
                  }  
              }  

          public void doExecute(String location, ActionInvocation invocation) throws IOException, TemplateException {
              this.location = location;
              this.invocation = invocation;
              this.configuration = getConfiguration();
              this.wrapper = getObjectWrapper();

              // 獲取模板的位置
              if (!location.startsWith("/")) {
                  ActionContext ctx = invocation.getInvocationContext();
                  HttpServletRequest req = (HttpServletRequest) ctx.get(ServletActionContext.HTTP_REQUEST);
                  String base = ResourceUtil.getResourceBase(req);
                  location = base + "/" + location;
              }

              // 得到模板
              Template template = configuration.getTemplate(location, deduceLocale());
              //
          為模板準(zhǔn)備數(shù)據(jù)
              TemplateModel model = createModel();

              // 根據(jù)模板和數(shù)據(jù)進(jìn)行輸出
              // Give subclasses a chance to hook into preprocessing
              if (preTemplateProcess(template, model)) {
                  try {
                      // Process the template
                      template.process(model, getWriter());
                  } finally {
                      // Give subclasses a chance to hook into postprocessing
                      postTemplateProcess(template, model);
                  }
              }
          }

            從源碼中,我們可以看到,createModel()方法真正為模板準(zhǔn)備需要顯示的數(shù)據(jù)。而之前,我們已經(jīng)看到過(guò)這個(gè)方法的源碼,這個(gè)方法所準(zhǔn)備的數(shù)據(jù)不僅包含ValueStack中的數(shù)據(jù),還包含了被封裝過(guò)的HttpServletRequestHttpSession等對(duì)象的數(shù)據(jù)。從而使得模板能夠以它特定的語(yǔ)法輸出這些數(shù)據(jù)。 [SPAN]

            VelocityResult也是類(lèi)似,有興趣的讀者可以順著思路繼續(xù)深究源碼。

            redirect

          Xml代碼
          <result-type name="chain" class="com.opensymphony.xwork2.ActionChainResult"/> 
          <result-type name="redirect" class="org.apache.struts2.dispatcher.ServletRedirectResult"/> 
          <result-type name="redirectAction" class="org.apache.struts2.dispatcher.ServletActionRedirectResult"/> 

            如果你在Action執(zhí)行完畢后,希望執(zhí)行另一個(gè)Action,有2種方式可供選擇。一種是forward,另外一種是redirect。有關(guān)forwardredirect的區(qū)別,這里我就不再展開(kāi),這應(yīng)該屬于Java程序員的基本知識(shí)。在Struts2中,分別對(duì)應(yīng)這兩種方式的Result,就是chainredirect

            先來(lái)談?wù)?/span>redirect,既然是重定向,那么源地址與目標(biāo)地址之間是2個(gè)不同的HttpServletRequest。所以目標(biāo)地址將無(wú)法通過(guò)ValueStackStruts2的特性來(lái)獲取源Action中的數(shù)據(jù)。如果你需要對(duì)目標(biāo)地址傳遞參數(shù),那么需要在目標(biāo)地址url或者配置文件中指出:

          Xml代碼
          <!--  
             The redirect-action url generated will be :  
             /genReport/generateReport.jsp?reportType=pie&width=100&height=100 
             --> 
             <action name="gatherReportInfo" class="..."> 
                <result name="showReportResult" type="redirect"> 
                   <param name="location">generateReport.jsp</param> 
                   <param name="namespace">/genReport</param> 
                   <param name="reportType">pie</param> 
                   <param name="width">${width}</param> 
                   <param name="height">${height}</param> 
                </result> 
             </action> 

            同時(shí),RedirectResult支持在配置文件中,讀取并解析源ActionValueStack的值,并成為參數(shù)傳遞到Redirect的地址中。上面給出的例子中,widthheight就是ValueStack中的值。

            chain

          Xml代碼
          <result-type name="chain" class="com.opensymphony.xwork2.ActionChainResult"/> 

            再來(lái)談?wù)?/span>chain,之前提到,chain其實(shí)只是在一個(gè)action執(zhí)行完畢之后,forward到另外一個(gè)action,所以他們之間是共享HttpServletRequest的。在使用chain作為Result時(shí),往往會(huì)配合使用ChainingInterceptor。有關(guān)ChainingInterceptorStruts2Reference說(shuō)明了其作用:

            Struts2 Reference 寫(xiě)道:If you need to copy the properties from your previous Actions in the chain to the current action, you should apply the ChainingInterceptor. The Interceptor will copy the original parameters from the request, and the ValueStack is passed in to the target Action. The source Action is remembered by the ValueStack, allowing the target Action to access the properties of the preceding Action(s) using the ValueStack, and also makes these properties available to the final result of the chain, such as the JSP or Velocity page.

            也就是說(shuō),ChainingInterceptor的作用是在Action直接傳遞數(shù)據(jù)。事實(shí)上,源ActionValueStack的數(shù)據(jù)會(huì)被做一次Copy,這樣,2個(gè)Action中的數(shù)據(jù)都在ValueStack中,使得對(duì)于前臺(tái)來(lái)說(shuō),通過(guò)ValueStack來(lái)取數(shù)據(jù),是透明而共享的。chain這個(gè)Result有一些常用的使用情景,這點(diǎn)在Struts2Reference中也有說(shuō)明:

            Struts2 Reference 寫(xiě)道:One common use of Action chaining is to provide lookup lists (like for a dropdown list of states). Since these Actions get put on the ValueStack, their properties will be available in the view. This functionality can also be done using the ActionTag to execute an Action from the display page.

            比如說(shuō),一張頁(yè)面中,你可能有許多數(shù)據(jù)要顯示,而某些數(shù)據(jù)的獲取方式可能被很多不同的頁(yè)面共享(典型來(lái)說(shuō),推薦文章這個(gè)小欄目的數(shù)據(jù)獲取,可能會(huì)被很多頁(yè)面所共享)。這種情況下,可以把這部分邏輯抽取到一個(gè)獨(dú)立Action中,并使用chain,將這個(gè)Action與主Action串聯(lián)起來(lái)。這樣,最后到達(dá)頁(yè)面的時(shí)候,頁(yè)面始終可以得到每個(gè)Action中的數(shù)據(jù)。

            不過(guò)chain這種Result,是在使用時(shí)需要慎重考慮的一種Result

            Struts2 Reference 寫(xiě)道:As a rule, Action Chaining is not recommended. First explore other options, such as the Redirect After Post technique.

            而Struts2也做出了理由上的說(shuō)明:

            Struts2 Reference 寫(xiě)道:Experience shows that chaining should be used with care. If chaining is overused, an application can turn into "spaghetti code". Actions should be treated as a Transaction Script, rather than as methods in a Business Facade. Be sure to ask yourself why you need to chain from one Action to another. Is a navigational issue, or could the logic in Action2 be pushed back to a support class or business facade so that Action1 can call it too?

          Ideally, Action classes should be as short as possible. All the core logic should be pushed back to a support class or a business facade, so that Actions only call methods. Actions are best used as adapters, rather than as a class where coding logic is defined.

            從實(shí)戰(zhàn)上將,使用chain作為Result也的確存在著上面所說(shuō)的許多問(wèn)題,我個(gè)人也是非常不推崇濫用這種Result。尤其是,對(duì)于使用SpringHibernate的朋友來(lái)說(shuō),如果你開(kāi)啟OpenSessionInView模式,那么Hibernatesession是跟隨HttpServletRequest的,所以session在整個(gè)action鏈中共享。這會(huì)為我們的編程帶來(lái)極大的麻煩。因?yàn)槲覀冎?/span>Hibernatesession會(huì)保留一份一級(jí)緩存,在action鏈中,共享一級(jí)緩存無(wú)疑會(huì)為你的調(diào)試工作帶來(lái)很大的不方便。

            所以,謹(jǐn)慎使用chain作為你的Result,應(yīng)該成為一條最佳實(shí)踐。

            stream

          Xml代碼
          <result-type name="stream" class="org.apache.struts2.dispatcher.StreamResult"/> 

            StreamResult等價(jià)于在Servlet中直接輸出Stream流。這種Result被經(jīng)常使用于輸出圖片、文檔等二進(jìn)制流到客戶(hù)端。通過(guò)使用StreamResult,我們只需要在Action中準(zhǔn)備好需要輸出的InputStream即可。

          Xml代碼
          <result name="success" type="stream"> 
            <param name="contentType">image/jpeg</param> 
            <param name="inputName">imageStream</param> 
            <param name="contentDisposition">filename="document.pdf"</param> 
            <param name="bufferSize">1024</param> 
          </result> 

            同時(shí),StreamResult支持許多參數(shù),對(duì)輸出的Stream流進(jìn)行參數(shù)控制。具體每個(gè)參數(shù)的作用,可以參考:http://struts.apache.org/2.0.14/docs/stream-result.html

            其他

            Struts2的高度可擴(kuò)展性保證了許多自定義的Result可以通過(guò)插件的形式發(fā)布出來(lái)。比較著名的有JSONResultJFreeChartResult等等。有興趣的讀者可以在Struts2的官方網(wǎng)站上找到它們,并選擇合適的加入到你的項(xiàng)目中去。

            關(guān)于Result配置簡(jiǎn)化的思考 

            Struts2Result,解決了如何從Control層轉(zhuǎn)向View的問(wèn)題。不過(guò)看了上面介紹的這些由框架本身實(shí)現(xiàn)的Result,我們可以發(fā)現(xiàn)Result所涉及到的,基本上還停留在為Control層到View層搭建橋梁。

            傳統(tǒng)的,我們需要通過(guò)配置文件,來(lái)指定Action執(zhí)行完畢之后,到底執(zhí)行什么樣的Result。不過(guò)在這樣一個(gè)到處呼吁簡(jiǎn)化配置的年代,存在著許多方式,可以省略配置:

            1. 使用Annotation

            Struts2的一些插件提供了@Result@ResultsAnnotation,可以通過(guò)Annotation來(lái)省略XML配置。具體請(qǐng)參考相關(guān)的文檔。

            2. Codebehind插件

            Struts2自帶了一個(gè)Codebehind插件(Struts2.1以后被合并到了其他的插件中)。Codebehind的基本思想是通過(guò)CoC的方式,使用命名約定來(lái)確定JSP等資源文件的位置。它通過(guò)實(shí)現(xiàn)了XWorkUnknownHandler接口,來(lái)實(shí)現(xiàn)當(dāng)Struts2框架無(wú)法找到相應(yīng)的Result時(shí),如何進(jìn)行處理的邏輯。具體文檔可以參考:http://struts.apache.org/2.0.14/docs/codebehind-plugin.html

            大家可以在上面這兩種方式中任意選擇,國(guó)內(nèi)著名的開(kāi)源倡導(dǎo)者Springside也是采用了上述2種方法。在多數(shù)情況下,使用Codebehind,針對(duì)其他的一些Result使用Annotation進(jìn)行配置,這樣可以在一定程度上簡(jiǎn)化配置。

            不過(guò)我本人對(duì)使用Annotation簡(jiǎn)化配置的評(píng)價(jià)不高。因?yàn)閷?shí)際上使用Annotation,只是將原本就非常簡(jiǎn)單的配置,從xml文件中移動(dòng)到java代碼中而已。就代碼量而言,本身并沒(méi)有減少。

            在這里,我也在經(jīng)常在思考,如何進(jìn)行配置簡(jiǎn)化,可以不寫(xiě)Annotation,完全使用CoC的方式來(lái)指定ResultCodebehindCoC方面已經(jīng)做出了榜樣,只是Codebehind無(wú)法判別Result的類(lèi)型,所以它只能支持dispatcher / freemarker / velocity這三種Result。所以Result的類(lèi)型的判別,成為了阻礙簡(jiǎn)化其配置CoC化的攔路虎。

            前一段時(shí)間,曾經(jīng)熱播一部電視劇《暗算》,其中的《看風(fēng)》篇中數(shù)學(xué)家黃依依的一段話給了我靈感:

            黃依依寫(xiě)道:開(kāi)啟密鎖鑰匙的復(fù)雜化,是現(xiàn)代密碼發(fā)展的趨勢(shì)。但這種復(fù)雜化卻受到無(wú)線通訊本身的限制,尤其是距離遠(yuǎn)、布點(diǎn)多的呈放射性的無(wú)線通訊,一般的密鑰總是要藏在報(bào)文中。

            密鑰既然可以藏在報(bào)文中,那么Result的類(lèi)型當(dāng)然也能夠藏在ResultCode中。

          Java代碼
          return "success"; 

            這樣一個(gè)簡(jiǎn)單的success作為ResultCode,是無(wú)法識(shí)別成復(fù)雜的Result類(lèi)型的,我們需要設(shè)計(jì)一套更加有效的ResultCode,同時(shí),Struts2能夠識(shí)別這些ResultCode,并得到相應(yīng)的Result類(lèi)型和Result實(shí)例。這樣,我們就可以借用Codebehind的實(shí)現(xiàn)方式,實(shí)現(xiàn)XWorkUnknownHandler接口,從而達(dá)到我們的目的。例如,我們規(guī)定ResultCode的解析規(guī)則:

            success —— 使用codebehind的規(guī)則進(jìn)行JSPFreemarker模板的尋址

            r:/user/list  —— 返回一個(gè)redirectResult,地址為/user/list

            c:/user/list —— 返回一個(gè)chainResult,地址為/user/list

            j:user —— 返回一個(gè)JSONResultJSONResultRoot對(duì)象為user

            s:inputStream-text/html —— 返回一個(gè)StreamResult,使用inputStream,并將contentType設(shè)置成text/html

            以此類(lèi)推,大家可以定義自己喜歡的ResultCode的格式,從而簡(jiǎn)化配置。有了這樣的規(guī)則,也就有了后來(lái)的實(shí)現(xiàn)。具體解析這些ResultCode,并為他們構(gòu)建Result實(shí)例的源碼,大家可以參考我的一個(gè)插件項(xiàng)目LightURL

          主站蜘蛛池模板: 巴楚县| 河西区| 香河县| 上犹县| 临沭县| 大洼县| 东兴市| 威宁| 通化县| 东乡县| 兴国县| 道孚县| 芮城县| 栾川县| 扶余县| 宁明县| 太原市| 九台市| 长春市| 堆龙德庆县| 通化县| 水富县| 万州区| 洛阳市| 苏州市| 津南区| 益阳市| 瓦房店市| 宁强县| 南部县| 玉龙| 崇仁县| 黄龙县| 荥阳市| 大邑县| 灵寿县| 象州县| 永州市| 岳池县| 渝中区| 泸西县|