Struts2中result-type详解(三)

在struts2框架中,当action处理完之后,就应该向用户返回结果信息,该任务被分为两部分:结果类型和结果本身。

结果类型提供了返回给用户信息类型的实现细节。结果类型通常在Struts2中就已预定义好了(见下表),或者是由插件提供,开发人员也可以自定义结果类型。默认配置的结果类型是dispatcher,该结果类型使用JSP来向用户显示结果。当定义了结果类型之后,该结果类型可以在不同的action中重复使用。

?

Struts2框架提供的结果类型
已配置结果类型名  类 名  描 述
dispatcher  org.apache.struts2.dispatcher.
ServletDispatcherResult
 默认结果类型,用来呈现JSP页面
chain  com.opensymphony.xwork2.
ActionChainResult
 将action和另外一个action链接起来
freemarker  org.apache.struts2.views.freemarker.
FreemarkerResult
 呈现Freemarker模板
httpheader  org.apache.struts2.dispatcher.
HttpHeaderResult
 返回一个已配置好的HTTP头信息响应
redirect  org.apache.struts2.dispatcher.
ServletRedirectResult
 将用户重定向到一个已配置好的URL
redirectAction  org.apache.struts2.dispatcher.
ServletActionRedirectResult
 将用户重定向到一个已定义好的action
stream  org.apache.struts2.dispatcher.
StreamResult
 将原始数据作为流传递回浏览器端,
该结果类型对下载的内容和图片非常有用
velocity  org.apache.struts2.dispatcher.
VelocityResult
 呈现Velocity模板
xslt  org.apache.struts2.views.xslt.
XSLTResult
 呈现XML到客户端,
该XML可以通过XSL模板进行转换
plaintext  org.apache.struts2.dispatcher.
PlainTextResult
 返回普通文本类容
简单说明一下result的name属性和type属性:
SUCCESS:Action正确的执行完成,返回相应的视图,success是name属性的默认值。
NONE:表示Action正确的执行完成,但并不返回任何视图。
ERROR:表示Action执行失败,返回到错误处理视图。
INPUT:Action的执行,需要从前端界面获取参数,INPUT就是代表这个参数输入的界面,一般在应用中,会对这些参数进行验证,如果验证没有通过,将自动返回到该视图。
LOGIN:Action因为用户没有登陆的原因没有正确执行,将返回该登陆视图,要求用户进行登陆验证。


在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 
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。 



redirect和redirectAction chain的区别 

struts2中关于result的返回类型一般我们是转发到一个jsp页面或者是html页面等,但是struts2中的result的返回类型还有redirect,redirectAction,chain。对于这三种返回类型之间肯定是有区别的,下面我们来看看关于redirect redirectAction chain这三种struts2的返回类型之间的区别。 

当使用type=“redirectAction” 或type=“redirect”提交到一个action并且需要传递一个参数时。这里是有区别的: 
使用type=“redirectAction”时,结果就只能写Action的配置名,不能带有后缀:“.action” 


Xml代码 复制代码 收藏代码
  1. <action name="Login" class="steven.actions.LoginAction">    
  2. <result name="success" type="redirectAction">User?u_id=${loginBean.u_id}</result>    
  3. </action>  
  4. <action name="Login" class="steven.actions.LoginAction">  
  5. <result name="success" type="redirectAction">User?u_id=${loginBean.u_id}</result>  
  6. </action>  
  1. <action name="Login" class="steven.actions.LoginAction">   
  2. <result name="success" type="redirectAction">User?u_id=${loginBean.u_id}</result>   
  3. </action>  
  4. <action name="Login" class="steven.actions.LoginAction">  
  5. <result name="success" type="redirectAction">User?u_id=${loginBean.u_id}</result>  
  6. </action>  

使用type=“redirect”时,结果应是action配置名+后缀名 
Xml代码 复制代码 收藏代码
  1. <action name="Login" class="steven.actions.LoginAction">    
  2. <result name="success" type="redirect">User.action?u_id=${loginBean.u_id}</result>    
  3. </action>  
  1. <action name="Login" class="steven.actions.LoginAction">   
  2. <result name="success" type="redirect">User.action?u_id=${loginBean.u_id}</result>   
  3. </action>  


  1. redirect:action处理完后重定向到一个视图资源(如:jsp页面),请求参数全部丢失,action处理结果也全部丢失。
  2. redirect-action:action处理完后重定向到一个action,请求参数全部丢失,action处理结果也全部丢失。
  3. chain:action处理完后转发到一个action,请求参数全部丢失,action处理结果不会丢失。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值