Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1468534
  • 博文数量: 842
  • 博客积分: 12411
  • 博客等级: 上将
  • 技术积分: 5772
  • 用 户 组: 普通用户
  • 注册时间: 2011-06-14 14:43
文章分类

全部博文(842)

文章存档

2013年(157)

2012年(685)

分类:

2012-07-06 10:11:01

原文地址:Struts2 —— Result机制 作者:soranokiseki

Struts2将Result列为一个独立的层次,可以说是整个Struts2的Action层架构设计中的另外一个精华所在。Result之所以成为一个层次,其实是为了解决MVC框架中,如何从Control层转向View层这样一个问题而存在的。所以,接下来我们详细讨论一下Result的方方面面。

Result的职责

Result作为一个独立的层次存在,必然有其存在的价值,它也必须完成它所在的层次的职责。Result是为了解决如何从Control层转向View层这样一个问题而存在的,那么Result最大的职责,就是架起Action到View的桥梁。具体来说,我把这些职责大概分成以下几个方面:

封装跳转逻辑

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

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

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

Java代码 复制代码
  1. HttpServletRequest request = ServletActionContext.getRequest();   
  2. RequestDispatcher dispatcher = request.getRequestDispatcher(finalLocation);   
  3.   
  4. ....   
  5.   
  6. dispatcher.forward(request, response);  
  1. "font-size: small;">"color: #ff00ff;">HttpServletRequest request = ServletActionContext.getRequest();  
  2. RequestDispatcher dispatcher = request.getRequestDispatcher(finalLocation);  
  3.   
  4. ....  
  5.   
  6. dispatcher.forward(request, response);  



再以Redirect重定向为例,在Struts2自带的ServletRedirectResult中,也同样存在着重定向的核心代码:

Java代码 复制代码
  1. HttpServletResponse response = (HttpServletResponse) ctx.get(ServletActionContext.HTTP_RESPONSE);   
  2.   
  3. ....   
  4.   
  5. response.sendRedirect(finalLocation);  
  1. "font-size: small;">"color: #ff00ff;">HttpServletResponse response = (HttpServletResponse) ctx.get(ServletActionContext.HTTP_RESPONSE);  
  2.   
  3. ....  
  4.   
  5. response.sendRedirect(finalLocation);  



由此可见,绝大多数的Result,都封装了与Web容器相关的跳转逻辑,由于这些逻辑往往需要和Servlet对象打交道,所以,遵循Struts2的基本原则,将它作为一个独立的层次,从而将Action从Web容器中解放出来。

准备显示数据

之 前提到,View层的展现方式很多,除了传统的JSP以外,还有类似Freemarker/Velocity这样的模板。根据模板显示的基本原理,需要将 预先定义好的模板(Template)和需要展示的数据(Model)组织起来,交给模板引擎,才能够正确显示。而这部分工作,就由Result层来完 成。

以Struts2自带的FreemarkerResult为例,在Result中,就存在着为模板准备数据的逻辑代码:

Java代码 复制代码
  1. protected TemplateModel createModel() throws TemplateModelException {   
  2.     ServletContext servletContext = ServletActionContext.getServletContext();   
  3.     HttpServletRequest request = ServletActionContext.getRequest();   
  4.     HttpServletResponse response = ServletActionContext.getResponse();   
  5.     ValueStack stack = ServletActionContext.getContext().getValueStack();   
  6.   
  7.     Object action = null;   
  8.     if(invocation!= null ) action = invocation.getAction(); //Added for NullPointException   
  9.     return freemarkerManager.buildTemplateModel(stack, action, servletContext, request, response, wrapper);   
  10. }  
  1. "font-size: small;">"color: #ff00ff;">protected TemplateModel createModel() throws TemplateModelException {  
  2.     ServletContext servletContext = ServletActionContext.getServletContext();  
  3.     HttpServletRequest request = ServletActionContext.getRequest();  
  4.     HttpServletResponse response = ServletActionContext.getResponse();  
  5.     ValueStack stack = ServletActionContext.getContext().getValueStack();  
  6.   
  7.     Object action = null;  
  8.     if(invocation!= null ) action = invocation.getAction(); //Added for NullPointException  
  9.     return freemarkerManager.buildTemplateModel(stack, action, servletContext, request, response, wrapper);  
  10. }  



有兴趣的读者可以顺着思路去看源码,看看这些Result到底是如何获取各种对象的值的。

控制输出行为

有 的时候,针对同一种类型的View展示,我们可能会有不同的输出行为。具体来说,可能有时候,我们需要对输出流指定特定的BufferSize、 Encoding等等。Result层,作为一个独立的层次,可以提供极大的扩展性,从而保证我们能够定义自己期望的输出类型。

以Struts2自带的HttpHeaderResult为例:

Java代码 复制代码
  1. public void execute(ActionInvocation invocation) throws Exception {   
  2.     HttpServletResponse response = ServletActionContext.getResponse();   
  3.   
  4.     if (status != -1) {   
  5.         response.setStatus(status);   
  6.     }   
  7.   
  8.     if (headers != null) {   
  9.         ValueStack stack = ActionContext.getContext().getValueStack();   
  10.   
  11.         for (Iterator iterator = headers.entrySet().iterator();   
  12.              iterator.hasNext();) {   
  13.             Map.Entry entry = (Map.Entry) iterator.next();   
  14.             String value = (String) entry.getValue();   
  15.             String finalValue = parse ? TextParseUtil.translateVariables(value, stack) : value;   
  16.             response.addHeader((String) entry.getKey(), finalValue);   
  17.         }   
  18.     }   
  19. }  
  1. "font-size: small;">"color: #ff00ff;">public void execute(ActionInvocation invocation) throws Exception {  
  2.     HttpServletResponse response = ServletActionContext.getResponse();  
  3.   
  4.     if (status != -1) {  
  5.         response.setStatus(status);  
  6.     }  
  7.   
  8.     if (headers != null) {  
  9.         ValueStack stack = ActionContext.getContext().getValueStack();  
  10.   
  11.         for (Iterator iterator = headers.entrySet().iterator();  
  12.              iterator.hasNext();) {  
  13.             Map.Entry entry = (Map.Entry) iterator.next();  
  14.             String value = (String) entry.getValue();  
  15.             String finalValue = parse ? TextParseUtil.translateVariables(value, stack) : value;  
  16.             response.addHeader((String) entry.getKey(), finalValue);  
  17.         }  
  18.     }  
  19. }  



我们可以在这里添加我们自定义的内容到HttpHeader中去,从而控制Http的输出。

Result的定义

让我们来看看Result的接口定义:

Java代码 复制代码
  1. public interface Result extends Serializable {   
  2.   
  3.     /**  
  4.      * Represents a generic interface for all action execution results, whether that be displaying a webpage, generating  
  5.      * an email, sending a JMS message, etc.  
  6.      */  
  7.     public void execute(ActionInvocation invocation) throws Exception;   
  8. }  
  1. "font-size: small;">"color: #ff00ff;">public interface Result extends Serializable {  
  2.   
  3.     /** 
  4.      * Represents a generic interface for all action execution results, whether that be displaying a webpage, generating 
  5.      * an email, sending a JMS message, etc. 
  6.      */  
  7.     public void execute(ActionInvocation invocation) throws Exception;  
  8. }  



这个接口定义非常简单,通过传入ActionInvocation,执行一段逻辑。我们来看看Struts2针对这个接口实现的一个抽象类,它规定了许多默认实现:

Java代码 复制代码
  1. public abstract class StrutsResultSupport implements Result, StrutsStatics {   
  2.   
  3.     private static final Log _log = LogFactory.getLog(StrutsResultSupport.class);   
  4.   
  5.     /** The default parameter */  
  6.     public static final String DEFAULT_PARAM = "location";   
  7.   
  8.     private boolean parse;   
  9.     private boolean encode;   
  10.     private String location;   
  11.     private String lastFinalLocation;   
  12.   
  13.     public StrutsResultSupport() {   
  14.         this(nulltruefalse);   
  15.     }   
  16.   
  17.     public StrutsResultSupport(String location) {   
  18.         this(location, truefalse);   
  19.     }   
  20.   
  21.     public StrutsResultSupport(String location, boolean parse, boolean encode) {   
  22.         this.location = location;   
  23.         this.parse = parse;   
  24.         this.encode = encode;   
  25.     }   
  26.   
  27.     // setter method 省略   
  28.     
  29.     /**  
  30.      * Implementation of the execute method from the Result interface. This will call  
  31.      * the abstract method {@link #doExecute(String, ActionInvocation)} after optionally evaluating the  
  32.      * location as an OGNL evaluation.  
  33.      *  
  34.      * @param invocation the execution state of the action.  
  35.      * @throws Exception if an error occurs while executing the result.  
  36.      */  
  37.     public void execute(ActionInvocation invocation) throws Exception {   
  38.         lastFinalLocation = conditionalParse(location, invocation);   
  39.         doExecute(lastFinalLocation, invocation);   
  40.     }   
  41.   
  42.     /**  
  43.      * Parses the parameter for OGNL expressions against the valuestack  
  44.      *  
  45.      * @param param The parameter value  
  46.      * @param invocation The action invocation instance  
  47.      * @return The resulting string  
  48.      */  
  49.     protected String conditionalParse(String param, ActionInvocation invocation) {   
  50.         if (parse && param != null && invocation != null) {   
  51.             return TextParseUtil.translateVariables(param, invocation.getStack(),   
  52.                     new TextParseUtil.ParsedValueEvaluator() {   
  53.                         public Object evaluate(Object parsedValue) {   
  54.                             if (encode) {   
  55.                                 if (parsedValue != null) {   
  56.                                     try {   
  57.                                         // use UTF-8 as this is the recommended encoding by W3C to   
  58.                                         // avoid incompatibilities.   
  59.                                         return URLEncoder.encode(parsedValue.toString(), "UTF-8");   
  60.                                     }   
  61.                                     catch(UnsupportedEncodingException e) {   
  62.                                         _log.warn("error while trying to encode ["+parsedValue+"]", e);   
  63.                                     }   
  64.                                 }   
  65.                             }   
  66.                             return parsedValue;   
  67.                         }   
  68.             });   
  69.         } else {   
  70.             return param;   
  71.         }   
  72.     }   
  73.   
  74.     /**  
  75.      * Executes the result given a final location (jsp page, action, etc) and the action invocation  
  76.      * (the state in which the action was executed). Subclasses must implement this class to handle  
  77.      * custom logic for result handling.  
  78.      *  
  79.      * @param finalLocation the location (jsp page, action, etc) to go to.  
  80.      * @param invocation    the execution state of the action.  
  81.      * @throws Exception if an error occurs while executing the result.  
  82.      */  
  83.     protected abstract void doExecute(String finalLocation, ActionInvocation invocation) throws Exception;   
  84. }  
  1. "font-size: small;">"color: #ff00ff;">public abstract class StrutsResultSupport implements Result, StrutsStatics {  
  2.   
  3.     private static final Log _log = LogFactory.getLog(StrutsResultSupport.class);  
  4.   
  5.     /** The default parameter */  
  6.     public static final String DEFAULT_PARAM = "location";  
  7.   
  8.     private boolean parse;  
  9.     private boolean encode;  
  10.     private String location;  
  11.     private String lastFinalLocation;  
  12.   
  13.     public StrutsResultSupport() {  
  14.         this(nulltruefalse);  
  15.     }  
  16.   
  17.     public StrutsResultSupport(String location) {  
  18.         this(location, truefalse);  
  19.     }  
  20.   
  21.     public StrutsResultSupport(String location, boolean parse, boolean encode) {  
  22.         this.location = location;  
  23.         this.parse = parse;  
  24.         this.encode = encode;  
  25.     }  
  26.   
  27.     // setter method 省略  
  28.    
  29.     /** 
  30.      * Implementation of the execute method from the Result interface. This will call 
  31.      * the abstract method {@link #doExecute(String, ActionInvocation)} after optionally evaluating the 
  32.      * location as an OGNL evaluation. 
  33.      * 
  34.      * @param invocation the execution state of the action. 
  35.      * @throws Exception if an error occurs while executing the result. 
  36.      */  
  37.     public void execute(ActionInvocation invocation) throws Exception {  
  38.         lastFinalLocation = conditionalParse(location, invocation);  
  39.         doExecute(lastFinalLocation, invocation);  
  40.     }  
  41.   
  42.     /** 
  43.      * Parses the parameter for OGNL expressions against the valuestack 
  44.      * 
  45.      * @param param The parameter value 
  46.      * @param invocation The action invocation instance 
  47.      * @return The resulting string 
  48.      */  
  49.     protected String conditionalParse(String param, ActionInvocation invocation) {  
  50.         if (parse && param != null && invocation != null) {  
  51.             return TextParseUtil.translateVariables(param, invocation.getStack(),  
  52.                     new TextParseUtil.ParsedValueEvaluator() {  
  53.                         public Object evaluate(Object parsedValue) {  
  54.                             if (encode) {  
  55.                                 if (parsedValue != null) {  
  56.                                     try {  
  57.                                         // use UTF-8 as this is the recommended encoding by W3C to  
  58.                                         // avoid incompatibilities.  
  59.                                         return URLEncoder.encode(parsedValue.toString(), "UTF-8");  
  60.                                     }  
  61.                                     catch(UnsupportedEncodingException e) {  
  62.                                         _log.warn("error while trying to encode ["+parsedValue+"]", e);  
  63.                                     }  
  64.                                 }  
  65.                             }  
  66.                             return parsedValue;  
  67.                         }  
  68.             });  
  69.         } else {  
  70.             return param;  
  71.         }  
  72.     }  
  73.   
  74.     /** 
  75.      * Executes the result given a final location (jsp page, action, etc) and the action invocation 
  76.      * (the state in which the action was executed). Subclasses must implement this class to handle 
  77.      * custom logic for result handling. 
  78.      * 
  79.      * @param finalLocation the location (jsp page, action, etc) to go to. 
  80.      * @param invocation    the execution state of the action. 
  81.      * @throws Exception if an error occurs while executing the result. 
  82.      */  
  83.     protected abstract void doExecute(String finalLocation, ActionInvocation invocation) throws Exception;  
  84. }  



很显然,这个默认实现是为那些类似JSP,Freemarker或者Redirect这样的页面跳转的Result而准备的一个基类,它规定了Result将要跳转到的具体页面的位置、是否需要解析参数,等等。

如果我们试图编写自定义的Result,我们可以实现Result接口,并在struts.xml中进行声明:

Java代码 复制代码
  1. public class CustomerResult implements Result {   
  2.   
  3.     public void execute(ActionInvocation invocation) throws Exception {   
  4.     // write your code here   
  5. }   
  6. }  
  1. "font-size: small;">"color: #ff00ff;">public class CustomerResult implements Result {  
  2.   
  3.     public void execute(ActionInvocation invocation) throws Exception {  
  4.     // write your code here  
  5. }  
  6. }  



Xml代码 复制代码
  1. <result-type name="customerResult" class="com.javaeye.struts2.CustomerResult"/>  
  1. <span style="font-size: small;"><span style="color: #ff00ff;"><result-type name="customerResult" class="com.javaeye.struts2.CustomerResult"/>  
  2. span>span>  



常用的Result

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

dispatcher

Xml代码 复制代码
  1. <result-type name="dispatcher" class="org.apache.struts2.dispatcher.ServletDispatcherResult" default="true"/>  
  1. <span style="font-size: small;"><span style="color: #ff00ff;"><result-type name="dispatcher" class="org.apache.struts2.dispatcher.ServletDispatcherResult" default="true"/>  
  2. span>span>  



dispatcher主要用于返回JSP,HTML等以页面为基础View视图,这个也是Struts2默认的Result类型。

在使用dispatcher时,唯一需要指定的,是JSP或者HTML页面的位置,这个位置将被用于定位返回的页面:

Xml代码 复制代码
  1. <result name="success">/index.jspresult>  
  1. <span style="font-size: small;"><span style="color: #ff00ff;"><result name="success">/index.jspresult>span>span>  



而Struts2本身也没有对dispatcher做出什么特殊的处理,只是简单的使用Servlet API进行forward。

freemarker / velocity

Xml代码 复制代码
  1. <result-type name="freemarker" class="org.apache.struts2.views.freemarker.FreemarkerResult"/>  
  2. <result-type name="velocity" class="org.apache.struts2.dispatcher.VelocityResult"/>  
  1. <span style="font-size: small;"><span style="color: #ff00ff;"><result-type name="freemarker" class="org.apache.struts2.views.freemarker.FreemarkerResult"/>  
  2. <result-type name="velocity" class="org.apache.struts2.dispatcher.VelocityResult"/>  
  3. span>span>  



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

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

Java代码 复制代码
  1. public void doExecute(String location, ActionInvocation invocation) throws IOException, TemplateException {   
  2.     this.location = location;   
  3.     this.invocation = invocation;   
  4.     this.configuration = getConfiguration();   
  5.     this.wrapper = getObjectWrapper();   
  6.   
  7.     // 获取模板的位置   
  8.     if (!location.startsWith("/")) {   
  9.         ActionContext ctx = invocation.getInvocationContext();   
  10.         HttpServletRequest req = (HttpServletRequest) ctx.get(ServletActionContext.HTTP_REQUEST);   
  11.         String base = ResourceUtil.getResourceBase(req);   
  12.         location = base + "/" + location;   
  13.     }   
  14.   
  15.     // 得到模板   
  16.     Template template = configuration.getTemplate(location, deduceLocale());   
  17.     // 为模板准备数据   
  18.     TemplateModel model = createModel();   
  19.   
  20.     // 根据模板和数据进行输出   
  21.     // Give subclasses a chance to hook into preprocessing   
  22.     if (preTemplateProcess(template, model)) {   
  23.         try {   
  24.             // Process the template   
  25.             template.process(model, getWriter());   
  26.         } finally {   
  27.             // Give subclasses a chance to hook into postprocessing   
  28.             postTemplateProcess(template, model);   
  29.         }   
  30.     }   
  31. }  
  1. "font-size: small;">"color: #ff00ff;">public void doExecute(String location, ActionInvocation invocation) throws IOException, TemplateException {  
  2.     this.location = location;  
  3.     this.invocation = invocation;  
  4.     this.configuration = getConfiguration();  
  5.     this.wrapper = getObjectWrapper();  
  6.   
  7.     // 获取模板的位置  
  8.     if (!location.startsWith("/")) {  
  9.         ActionContext ctx = invocation.getInvocationContext();  
  10.         HttpServletRequest req = (HttpServletRequest) ctx.get(ServletActionContext.HTTP_REQUEST);  
  11.         String base = ResourceUtil.getResourceBase(req);  
  12.         location = base + "/" + location;  
  13.     }  
  14.   
  15.     // 得到模板  
  16.     Template template = configuration.getTemplate(location, deduceLocale());  
  17.     // 为模板准备数据  
  18.     TemplateModel model = createModel();  
  19.   
  20.     // 根据模板和数据进行输出  
  21.     // Give subclasses a chance to hook into preprocessing  
  22.     if (preTemplateProcess(template, model)) {  
  23.         try {  
  24.             // Process the template  
  25.             template.process(model, getWriter());  
  26.         } finally {  
  27.             // Give subclasses a chance to hook into postprocessing  
  28.             postTemplateProcess(template, model);  
  29.         }  
  30.     }  
  31. }  



从 源码中,我们可以看到,createModel()方法真正为模板准备需要显示的数据。而之前,我们已经看到过这个方法的源码,这个方法所准备的数据不仅 包含ValueStack中的数据,还包含了被封装过的HttpServletRequest,HttpSession等对象的数据。从而使得模板能够以 它特定的语法输出这些数据。

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

redirect

Xml代码 复制代码
  1. <result-type name="chain" class="com.opensymphony.xwork2.ActionChainResult"/>  
  2. <result-type name="redirect" class="org.apache.struts2.dispatcher.ServletRedirectResult"/>  
  3. <result-type name="redirectAction" class="org.apache.struts2.dispatcher.ServletActionRedirectResult"/>  
  1. <span style="font-size: small;"><span style="color: #ff00ff;"><result-type name="chain" class="com.opensymphony.xwork2.ActionChainResult"/>  
  2. <result-type name="redirect" class="org.apache.struts2.dispatcher.ServletRedirectResult"/>  
  3. <result-type name="redirectAction" class="org.apache.struts2.dispatcher.ServletActionRedirectResult"/>span>span>  



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

先来谈谈redirect,既然是重定向,那么源地址与目标地址之间是2个不同的 HttpServletRequest。所以目标地址将无法通过ValueStack等Struts2的特性来获取源Action中的数据。如果你需要对 目标地址传递参数,那么需要在目标地址url或者配置文件中指出:

Xml代码 复制代码
  1. 阅读(1337) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~