Struts2 Result-type(打包Action层到View层的跳转逻辑)

Struts2 Result-type(封装Action层到View层的跳转逻辑)

Struts2 Result 列为一个独立的层次,可以说是整个 Struts2 Action 层架构设计中的另外一个精华所在。 Result 之所以成为一个层次,其实

是为了解决
MVC 框架中,如何从 Control 层转向 View 层这样一个问题而存在的

 

struts2-core.jar/struts-default.xml 中,我们可以找到关于 result-type 的一些配置信息,从中可以看出 struts2 组件默认为我们提供了这
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 >



封装跳转逻辑

   Result 的首要职责,是封装 Action 层到 View 层的跳转逻辑。之前我们已经反复提到, Struts2 Action 是一个与 Web 容器无关的 POJO 。所以,在 Action 执行完毕之后,框架需要把代码的执行权重新交还给 Web 容器,并转向到相应的页面或者其他类型的 View 层。而这个跳转逻辑,就由 Result 来完成。这样,好处也是显而易见的,对 Action 屏蔽任何 Web 容器的相关信息,使得每个层次更加清晰。

   View 层的显示类型非常多,有最常见的 JSP 、当下非常流行的 Freemarker/Velocity 模板、 Redirect 到一个新的地址、文本流、图片流、甚至是 JSON 对象等等。所以 Result 层的独立存在,就能够对这些显示类型进行区分,并封装合理的跳转逻辑。

  以 JSP 转向为例,在 Struts2 自带的 ServletDispatcherResult 中就存在着核心的 JSP 跳转逻辑:

常用的 Result  

  接下来,大致介绍一下 Struts2 内部已经实现的 Result ,并看看他们是如何工作的。

   dispatcher

Xml 代码

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

   dispatcher 主要用于返回 JSP HTML 等以页面为基础 View 视图,这个也是 Struts2 默认的 Result 类型。在使用 dispatcher 时,唯一需要指定的,是 JSP 或者 HTML 页面的位置,这个位置将被用于定位返回的页面:

Xml 代码

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

  而 Struts2 本身也没有对 dispatcher 做出什么特殊的处理,只是简单的使用 Servlet API 进行 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"/> 

  随着模板技术的越来越流行,使用 Freemarker 或者 Velocity 模板进行 View 层展示的开发者越来越多。 Struts2 同样为模板作为 Result 做出了支持。由于模板的显示需要模板( Template )与数据( Model )的紧密配合,所以在 Struts2 中,这两个 Result 的主要工作是为模板准备数据。

  以 Freemarker 为例,我们来看看它是如何为模板准备数据的:

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());  
    //
为模板准备数据   
    TemplateModel model = createModel();  
 
    //
根据模板和数据进行输出   
    // 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());
    //
为模板准备数据
    TemplateModel model = createModel();

    // 根据模板和数据进行输出
    // 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() 方法真正为模板准备需要显示的数据。而之前,我们已经看到过这个方法的源码,这个方法所准备的数据不仅包含 ValueStack 中的数据,还包含了被封装过的 HttpServletRequest HttpSession 等对象的数据。从而使得模板能够以它特定的语法输出这些数据。 [SPAN]

   Velocity Result 也是类似,有兴趣的读者可以顺着思路继续深究源码。

   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 执行完毕后,希望执行另一个 Action ,有 2 种方式可供选择。一种是 forward ,另外一种是 redirect 。有关 forward redirect 的区别,这里我就不再展开,这应该属于 Java 程序员的基本知识。在 Struts2 中,分别对应这两种方式的 Result ,就是 chain redirect

  先来谈谈 redirect ,既然是重定向,那么源地址与目标地址之间是 2 个不同的 HttpServletRequest 。所以目标地址将无法通过 ValueStack Struts2 的特性来获取源 Action 中的数据。如果你需要对目标地址传递参数,那么需要在目标地址 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> 

  同时, Redirect Result 支持在配置文件中,读取并解析源 Action ValueStack 的值,并成为参数传递到 Redirect 的地址中。上面给出的例子中, width height 就是 ValueStack 中的值。

   chain

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

  再来谈谈 chain ,之前提到, chain 其实只是在一个 action 执行完毕之后, forward 到另外一个 action ,所以他们之间是共享 HttpServletRequest 的。在使用 chain 作为 Result 时,往往会配合使用 ChainingInterceptor 。有关 ChainingInterceptor Struts2 Reference 说明了其作用:

   Struts2 Reference 写道: 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.

  也就是说, ChainingInterceptor 的作用是在 Action 直接传递数据。事实上,源 Action ValueStack 的数据会被做一次 Copy ,这样, 2 Action 中的数据都在 ValueStack 中,使得对于前台来说,通过 ValueStack 来取数据,是透明而共享的。 chain 这个 Result 有一些常用的使用情景,这点在 Struts2 Reference 中也有说明:

   Struts2 Reference 写道: 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.

  比如说,一张页面中,你可能有许多数据要显示,而某些数据的获取方式可能被很多不同的页面共享(典型来说, 推荐文章 这个小栏目的数据获取,可能会被很多页面所共享)。这种情况下,可以把这部分逻辑抽取到一个独立 Action 中,并使用 chain ,将这个 Action 与主 Action 串联起来。这样,最后到达页面的时候,页面始终可以得到每个 Action 中的数据。

  不过 chain 这种 Result ,是在使用时需要慎重考虑的一种 Result

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

  而 Struts2 也做出了理由上的说明:

   Struts2 Reference 写道: 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.

  从实战上将,使用 chain 作为 Result 也的确存在着上面所说的许多问题,我个人也是非常不推崇滥用这种 Result 。尤其是,对于使用 Spring Hibernate 的朋友来说,如果你开启 OpenSessionInView 模式,那么 Hibernate session 是跟随 HttpServletRequest 的,所以 session 在整个 action 链*享。这会为我们的编程带来极大的麻烦。因为我们知道 Hibernate session 会保留一份一级缓存,在 action 链中,共享一级缓存无疑会为你的调试工作带来很大的不方便。

  所以,谨慎使用 chain 作为你的 Result ,应该成为一条最佳实践。

    stream

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

   StreamResult 等价于在 Servlet 中直接输出 Stream 流。这种 Result 被经常使用于输出图片、文档等二进制流到客户端。通过使用 StreamResult ,我们只需要在 Action 中准备好需要输出的 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> 

  同时, StreamResult 支持许多参数,对输出的 Stream 流进行参数控制。具体每个参数的作用,可以参考: http://struts.apache.org/2.0.14/docs/stream-result.html

   其他

   Struts2 的高度可扩展性保证了许多自定义的 Result 可以通过插件的形式发布出来。比较著名的有 JSONResult JFreeChartResult 等等。有兴趣的读者可以在 Struts2 的官方网站上找到它们,并选择合适的加入到你的项目中去。

   关于 Result 配置简化的思考  

   Struts2 Result ,解决了 如何从 Control 层转向 View 的问题。不过看了上面介绍的这些由框架本身实现的 Result ,我们可以发现 Result 所涉及到的,基本上还停留在为 Control 层到 View 层搭建桥梁。

  传统的,我们需要通过配置文件,来指定 Action 执行完毕之后,到底执行什么样的 Result 。不过在这样一个到处呼吁简化配置的年代,存在着许多方式,可以省略配置:

   1. 使用 Annotation

   Struts2 的一些插件提供了 @Result @Results Annotation ,可以通过 Annotation 来省略 XML 配置。具体请参考相关的文档。

   2. Codebehind 插件

   Struts2 自带了一个 Codebehind 插件( Struts2.1 以后被合并到了其他的插件中)。 Codebehind 的基本思想是通过 CoC 的方式,使用命名约定来确定 JSP 等资源文件的位置。它通过实现了 XWork UnknownHandler 接口,来实现当 Struts2 框架无法找到相应的 Result 时,如何进行处理的逻辑。具体文档可以参考: http://struts.apache.org/2.0.14/docs/codebehind-plugin.html

  大家可以在上面这两种方式中任意选择,国内著名的开源倡导者 Springside 也是采用了上述 2 种方法。在多数情况下,使用 Codebehind ,针对其他的一些 Result 使用 Annotation 进行配置,这样可以在一定程度上简化配置。

  不过我本人对使用 Annotation 简化配置的评价不高。因为实际上使用 Annotation ,只是将原本就非常简单的配置,从 xml 文件中移动到 java 代码中而已。就代码量而言,本身并没有减少。

  在这里,我也在经常在思考,如何进行配置简化,可以不写 Annotation ,完全使用 CoC 的方式来指定 Result Codebehind CoC 方面已经做出了榜样,只是 Codebehind 无法判别 Result 的类型,所以它只能支持 dispatcher / freemarker / velocity 这三种 Result 。所以 Result 的类型的判别,成为了阻碍简化其配置 CoC 化的拦路虎。

  前一段时间,曾经热播一部电视剧《暗算》,其中的《看风》篇中数学家黄依依的一段话给了我灵感:

  黄依依 写道:开启密锁钥匙的复杂化,是现代密码发展的趋势。但这种复杂化却受到无线通讯本身的限制,尤其是距离远、布点多的呈放射性的无线通讯,一般的密钥总是要藏在报文中。

  密钥既然可以藏在报文中,那么 Result 的类型当然也能够藏在 ResultCode 中。

Java 代码
return "success"; 

  这样一个简单的 success 作为 ResultCode ,是无法识别成复杂的 Result 类型的,我们需要设计一套更加有效的 ResultCode ,同时, Struts2 能够识别这些 ResultCode ,并得到相应的 Result 类型和 Result 实例。这样,我们就可以借用 Codebehind 的实现方式,实现 XWork UnknownHandler 接口,从而达到我们的目的。例如,我们规定 ResultCode 的解析规则:

   success —— 使用 codebehind 的规则进行 JSP Freemarker 模板的寻址

   r:/user/list  —— 返回一个 redirect Result ,地址为 /user/list

   c:/user/list —— 返回一个 chain Result ,地址为 /user/list

   j:user —— 返回一个 JSON Result JSONResult Root 对象为 user

   s:inputStream-text/html —— 返回一个 StreamResult ,使用 inputStream ,并将 contentType 设置成 text/html

  以此类推,大家可以定义自己喜欢的 ResultCode 的格式,从而简化配置。有了这样的规则,也就有了后来的实现。具体解析这些 ResultCode ,并为他们构建 Result 实例的源码,大家可以参考我的一个插件项目 LightURL

转载:http://www.blogjava.net/jzone/articles/322222.html