- 浏览: 717189 次
- 性别:
- 来自: 上海
最新评论
-
A741841403:
附件怎么看不到了呢?
介绍一个PDF的生成方案 -
chunguang_yao:
写得非常好
《Struts2技术内幕》 新书部分篇章连载(一)—— 如何学习开源框架 -
su_chunlong:
rrredriver 写道极赞的文章,感谢楼主!有一个问题想请 ...
SpringMVC深度探险(三) —— DispatcherServlet与初始化主线 -
qq_32671287:
...
SpringMVC深度探险(一) —— SpringMVC前传 -
shenghuorulan:
看的好爽,可不可以这么笼统的概括一个框架的学习过程1,框架要解 ...
SpringMVC深度探险(二) —— SpringMVC概览
专栏地址:http://www.iteye.com/wiki/struts2/1462-result-in-struts2
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跳转逻辑:
再以Redirect重定向为例,在Struts2自带的ServletRedirectResult中,也同样存在着重定向的核心代码:
由此可见,绝大多数的Result,都封装了与Web容器相关的跳转逻辑,由于这些逻辑往往需要和Servlet对象打交道,所以,遵循Struts2的基本原则,将它作为一个独立的层次,从而将Action从Web容器中解放出来。
准备显示数据
之前提到,View层的展现方式很多,除了传统的JSP以外,还有类似Freemarker/Velocity这样的模板。根据模板显示的基本原理,需要将预先定义好的模板(Template)和需要展示的数据(Model)组织起来,交给模板引擎,才能够正确显示。而这部分工作,就由Result层来完成。
以Struts2自带的FreemarkerResult为例,在Result中,就存在着为模板准备数据的逻辑代码:
有兴趣的读者可以顺着思路去看源码,看看这些Result到底是如何获取各种对象的值的。
控制输出行为
有的时候,针对同一种类型的View展示,我们可能会有不同的输出行为。具体来说,可能有时候,我们需要对输出流指定特定的BufferSize、Encoding等等。Result层,作为一个独立的层次,可以提供极大的扩展性,从而保证我们能够定义自己期望的输出类型。
以Struts2自带的HttpHeaderResult为例:
我们可以在这里添加我们自定义的内容到HttpHeader中去,从而控制Http的输出。
Result的定义
让我们来看看Result的接口定义:
这个接口定义非常简单,通过传入ActionInvocation,执行一段逻辑。我们来看看Struts2针对这个接口实现的一个抽象类,它规定了许多默认实现:
很显然,这个默认实现是为那些类似JSP,Freemarker或者Redirect这样的页面跳转的Result而准备的一个基类,它规定了Result将要跳转到的具体页面的位置、是否需要解析参数,等等。
如果我们试图编写自定义的Result,我们可以实现Result接口,并在struts.xml中进行声明:
常用的Result
接下来,大致介绍一下Struts2内部已经实现的Result,并看看他们是如何工作的。
dispatcher
dispatcher主要用于返回JSP,HTML等以页面为基础View视图,这个也是Struts2默认的Result类型。
在使用dispatcher时,唯一需要指定的,是JSP或者HTML页面的位置,这个位置将被用于定位返回的页面:
而Struts2本身也没有对dispatcher做出什么特殊的处理,只是简单的使用Servlet API进行forward。
freemarker / velocity
随着模板技术的越来越流行,使用Freemarker或者Velocity模板进行View层展示的开发者越来越多。Struts2同样为模板作为Result做出了支持。由于模板的显示需要模板(Template)与数据(Model)的紧密配合,所以在Struts2中,这两个Result的主要工作是为模板准备数据。
以Freemarker为例,我们来看看它是如何为模板准备数据的:
从源码中,我们可以看到,createModel()方法真正为模板准备需要显示的数据。而之前,我们已经看到过这个方法的源码,这个方法所准备的数据不仅包含ValueStack中的数据,还包含了被封装过的HttpServletRequest,HttpSession等对象的数据。从而使得模板能够以它特定的语法输出这些数据。
Velocity的Result也是类似,有兴趣的读者可以顺着思路继续深究源码。
redirect
如果你在Action执行完毕后,希望执行另一个Action,有2种方式可供选择。一种是forward,另外一种是redirect。有关forward和redirect的区别,这里我就不再展开,这应该属于Java程序员的基本知识。在Struts2中,分别对应这两种方式的Result,就是chain和redirect。
先来谈谈redirect,既然是重定向,那么源地址与目标地址之间是2个不同的HttpServletRequest。所以目标地址将无法通过ValueStack等Struts2的特性来获取源Action中的数据。如果你需要对目标地址传递参数,那么需要在目标地址url或者配置文件中指出:
同时,Redirect的Result支持在配置文件中,读取并解析源Action中ValueStack的值,并成为参数传递到Redirect的地址中。上面给出的例子中,width和height就是ValueStack中的值。
chain
再来谈谈chain,之前提到,chain其实只是在一个action执行完毕之后,forward到另外一个action,所以他们之间是共享HttpServletRequest的。在使用chain作为Result时,往往会配合使用ChainingInterceptor。有关ChainingInterceptor,Struts2的Reference说明了其作用:
也就是说,ChainingInterceptor的作用是在Action直接传递数据。事实上,源Action中ValueStack的数据会被做一次Copy,这样,2个Action中的数据都在ValueStack中,使得对于前台来说,通过ValueStack来取数据,是透明而共享的。
chain这个Result有一些常用的使用情景,这点在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也做出了理由上的说明:
从实战上将,使用chain作为Result也的确存在着上面所说的许多问题,我个人也是非常不推崇滥用这种Result。尤其是,对于使用Spring和Hibernate的朋友来说,如果你开启OpenSessionInView模式,那么Hibernate的session是跟随HttpServletRequest的,所以session在整个action链中共享。这会为我们的编程带来极大的麻烦。因为我们知道Hibernate的session会保留一份一级缓存,在action链中,共享一级缓存无疑会为你的调试工作带来很大的不方便。
所以,谨慎使用chain作为你的Result,应该成为一条最佳实践。
stream
StreamResult等价于在Servlet中直接输出Stream流。这种Result被经常使用于输出图片、文档等二进制流到客户端。通过使用StreamResult,我们只需要在Action中准备好需要输出的InputStream即可。
同时,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中。
这样一个简单的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.iteye.com/topic/242838
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跳转逻辑:
HttpServletRequest request = ServletActionContext.getRequest(); RequestDispatcher dispatcher = request.getRequestDispatcher(finalLocation); .... dispatcher.forward(request, response);
再以Redirect重定向为例,在Struts2自带的ServletRedirectResult中,也同样存在着重定向的核心代码:
HttpServletResponse response = (HttpServletResponse) ctx.get(ServletActionContext.HTTP_RESPONSE); .... response.sendRedirect(finalLocation);
由此可见,绝大多数的Result,都封装了与Web容器相关的跳转逻辑,由于这些逻辑往往需要和Servlet对象打交道,所以,遵循Struts2的基本原则,将它作为一个独立的层次,从而将Action从Web容器中解放出来。
准备显示数据
之前提到,View层的展现方式很多,除了传统的JSP以外,还有类似Freemarker/Velocity这样的模板。根据模板显示的基本原理,需要将预先定义好的模板(Template)和需要展示的数据(Model)组织起来,交给模板引擎,才能够正确显示。而这部分工作,就由Result层来完成。
以Struts2自带的FreemarkerResult为例,在Result中,就存在着为模板准备数据的逻辑代码:
protected TemplateModel createModel() throws TemplateModelException { ServletContext servletContext = ServletActionContext.getServletContext(); HttpServletRequest request = ServletActionContext.getRequest(); HttpServletResponse response = ServletActionContext.getResponse(); ValueStack stack = ServletActionContext.getContext().getValueStack(); Object action = null; if(invocation!= null ) action = invocation.getAction(); //Added for NullPointException return freemarkerManager.buildTemplateModel(stack, action, servletContext, request, response, wrapper); }
有兴趣的读者可以顺着思路去看源码,看看这些Result到底是如何获取各种对象的值的。
控制输出行为
有的时候,针对同一种类型的View展示,我们可能会有不同的输出行为。具体来说,可能有时候,我们需要对输出流指定特定的BufferSize、Encoding等等。Result层,作为一个独立的层次,可以提供极大的扩展性,从而保证我们能够定义自己期望的输出类型。
以Struts2自带的HttpHeaderResult为例:
public void execute(ActionInvocation invocation) throws Exception { HttpServletResponse response = ServletActionContext.getResponse(); if (status != -1) { response.setStatus(status); } if (headers != null) { ValueStack stack = ActionContext.getContext().getValueStack(); for (Iterator iterator = headers.entrySet().iterator(); iterator.hasNext();) { Map.Entry entry = (Map.Entry) iterator.next(); String value = (String) entry.getValue(); String finalValue = parse ? TextParseUtil.translateVariables(value, stack) : value; response.addHeader((String) entry.getKey(), finalValue); } } }
我们可以在这里添加我们自定义的内容到HttpHeader中去,从而控制Http的输出。
Result的定义
让我们来看看Result的接口定义:
public interface Result extends Serializable { /** * Represents a generic interface for all action execution results, whether that be displaying a webpage, generating * an email, sending a JMS message, etc. */ public void execute(ActionInvocation invocation) throws Exception; }
这个接口定义非常简单,通过传入ActionInvocation,执行一段逻辑。我们来看看Struts2针对这个接口实现的一个抽象类,它规定了许多默认实现:
public abstract class StrutsResultSupport implements Result, StrutsStatics { private static final Log _log = LogFactory.getLog(StrutsResultSupport.class); /** The default parameter */ public static final String DEFAULT_PARAM = "location"; private boolean parse; private boolean encode; private String location; private String lastFinalLocation; public StrutsResultSupport() { this(null, true, false); } public StrutsResultSupport(String location) { this(location, true, false); } public StrutsResultSupport(String location, boolean parse, boolean encode) { this.location = location; this.parse = parse; this.encode = encode; } // setter method 省略 /** * Implementation of the <tt>execute</tt> method from the <tt>Result</tt> interface. This will call * the abstract method {@link #doExecute(String, ActionInvocation)} after optionally evaluating the * location as an OGNL evaluation. * * @param invocation the execution state of the action. * @throws Exception if an error occurs while executing the result. */ public void execute(ActionInvocation invocation) throws Exception { lastFinalLocation = conditionalParse(location, invocation); doExecute(lastFinalLocation, invocation); } /** * Parses the parameter for OGNL expressions against the valuestack * * @param param The parameter value * @param invocation The action invocation instance * @return The resulting string */ protected String conditionalParse(String param, ActionInvocation invocation) { if (parse && param != null && invocation != null) { return TextParseUtil.translateVariables(param, invocation.getStack(), new TextParseUtil.ParsedValueEvaluator() { public Object evaluate(Object parsedValue) { if (encode) { if (parsedValue != null) { try { // use UTF-8 as this is the recommended encoding by W3C to // avoid incompatibilities. return URLEncoder.encode(parsedValue.toString(), "UTF-8"); } catch(UnsupportedEncodingException e) { _log.warn("error while trying to encode ["+parsedValue+"]", e); } } } return parsedValue; } }); } else { return param; } } /** * Executes the result given a final location (jsp page, action, etc) and the action invocation * (the state in which the action was executed). Subclasses must implement this class to handle * custom logic for result handling. * * @param finalLocation the location (jsp page, action, etc) to go to. * @param invocation the execution state of the action. * @throws Exception if an error occurs while executing the result. */ protected abstract void doExecute(String finalLocation, ActionInvocation invocation) throws Exception; }
很显然,这个默认实现是为那些类似JSP,Freemarker或者Redirect这样的页面跳转的Result而准备的一个基类,它规定了Result将要跳转到的具体页面的位置、是否需要解析参数,等等。
如果我们试图编写自定义的Result,我们可以实现Result接口,并在struts.xml中进行声明:
public class CustomerResult implements Result { public void execute(ActionInvocation invocation) throws Exception { // write your code here } }
<result-type name="customerResult" class="com.javaeye.struts2.CustomerResult"/>
常用的Result
接下来,大致介绍一下Struts2内部已经实现的Result,并看看他们是如何工作的。
dispatcher
<result-type name="dispatcher" class="org.apache.struts2.dispatcher.ServletDispatcherResult" default="true"/>
dispatcher主要用于返回JSP,HTML等以页面为基础View视图,这个也是Struts2默认的Result类型。
在使用dispatcher时,唯一需要指定的,是JSP或者HTML页面的位置,这个位置将被用于定位返回的页面:
<result name="success">/index.jsp</result>
而Struts2本身也没有对dispatcher做出什么特殊的处理,只是简单的使用Servlet API进行forward。
freemarker / velocity
<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为例,我们来看看它是如何为模板准备数据的:
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等对象的数据。从而使得模板能够以它特定的语法输出这些数据。
Velocity的Result也是类似,有兴趣的读者可以顺着思路继续深究源码。
redirect
<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或者配置文件中指出:
<!-- 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
<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.
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
<result-type name="stream" class="org.apache.struts2.dispatcher.StreamResult"/>
StreamResult等价于在Servlet中直接输出Stream流。这种Result被经常使用于输出图片、文档等二进制流到客户端。通过使用StreamResult,我们只需要在Action中准备好需要输出的InputStream即可。
<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中。
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.iteye.com/topic/242838
评论
3 楼
shenzhw
2012-03-07
不曾记起,又何谈忘记!
2 楼
fxzjdtk
2009-02-05
楼主说说freemarker和jsp的区别吧?
我只会jsp,不知道freemarker有什么好处,为什么这么流行,详细说说吧。
我只会jsp,不知道freemarker有什么好处,为什么这么流行,详细说说吧。
1 楼
langyu
2009-02-05
支持楼主,写的很多
读完明白很多
读完明白很多
发表评论
-
《Struts2技术内幕》 新书部分篇章连载(十)—— XWork概览
2012-01-29 14:43 7422第7章 别具匠心 —— XWo ... -
《Struts2技术内幕》 新书部分篇章连载(九)—— 强大的OGNL
2012-01-29 13:17 5113第6章 灵丹妙药 —— OGN ... -
《Struts2技术内幕》 新书部分篇章连载(八)—— XWork容器概览
2012-01-29 11:56 4719第5章 生命之源 —— XWork中的容器 对象的生命周期管 ... -
Struts2的一些不尽人意的地方,兼答hantsy
2012-01-06 10:21 4779hantsy 写道 在 Webwork 合并到Apache S ... -
《Struts2技术内幕》 新书部分篇章连载(七)—— ThreadLocal模式
2012-01-05 14:39 14740第4章 源头活水 —— Str ... -
《Struts2技术内幕》 新书部分篇章连载(六)—— 框架的本质
2012-01-05 14:02 4857第2章 固本清源 —— Web ... -
《Struts2技术内幕》自评 —— 尚未完成的话题
2011-12-30 11:11 4211此文接我另外一篇博客:新书上市:《Struts2技术内幕》 ... -
新书上市:《Struts2技术内幕》
2011-12-26 14:28 10564我的新书《Struts2技术内 ... -
《Struts2技术内幕》 新书部分篇章连载(四)—— 核心分发器
2011-10-27 20:15 75489.2核心分发器 —— Dispa ... -
《Struts2技术内幕》 新书部分篇章连载(五)—— 请求响应哲学
2011-10-27 20:01 14060第7章 别具匠心 —— XWork设计原理 众所周知,现代电 ... -
《Struts2技术内幕》 新书部分篇章连载(三)—— 多视角透析Struts2
2011-10-27 19:09 88403.3 多视角透析Struts2 Str ... -
《Struts2技术内幕》 新书部分篇章连载(一)—— 如何学习开源框架
2011-10-27 18:40 161982.6 如何学习开源框架 ... -
《Struts2技术内幕》 新书部分篇章连载(二)—— 面向对象浅谈
2011-10-26 19:46 9731第2章 固本清源 —— Web ... -
《Struts2技术内幕》 新书样章和导读
2011-10-27 20:40 5072由于本书尚未出版,我 ... -
忘记李刚,一步一步跟我学Struts2 —— 标签库,永恒的争论话题
2009-02-08 22:52 7665专栏地址:http://www.iteye ... -
忘记李刚,一步一步跟我学Struts2 —— 拦截器详解
2009-02-01 12:49 10857专栏地址:http://www.iteye.com/wiki/ ... -
忘记李刚,一步一步跟我学Struts2 —— MVC框架的困惑
2009-01-21 11:43 10222专栏地址:http://www.iteye.com/wiki/ ... -
忘记李刚,一步一步跟我学Struts2 —— Struts2配置详解
2009-01-19 10:06 6356专栏地址:http://www.iteye.com/wiki/ ... -
忘记李刚,一步一步跟我学Struts2 —— Struts2中的Action
2009-01-15 15:02 7112专栏地址:http://www.iteye.com/wiki/ ... -
忘记李刚,一步一步跟我学Struts2 —— Struts2中的参数传递
2009-01-07 17:21 8557专栏地址:http://www.iteye.com/wiki/ ...
相关推荐
《Struts2权威指南》是由著名Java技术专家李刚编著的一本经典教材,这本书深入浅出地讲解了Struts2的核心概念、架构和使用方法。书中不仅涵盖了Struts2的基础知识,还详细讨论了如何将Struts2与其他流行技术如Spring...
Struts 2是一个强大的MVC(模型-视图-控制器)框架,它提供了灵活的请求处理和强大的拦截器机制,使得开发者能够轻松管理用户请求并实现复杂的业务逻辑。Struts 2与Spring的整合可以更好地处理依赖注入,提供更好的...
《Struts2权威指南》是李刚先生撰写的一本深入解析Struts2框架的专业书籍,其源代码的提供对于读者理解和实践Struts2框架具有极大的帮助。 源代码通常包含了书中讲解的各种示例和实战项目,使得学习者能够通过实际...
总之,Struts2是Java Web开发中的重要工具,李刚老师的《Struts2权威指南》为开发者提供了全面的学习资源,无论你是初学者还是经验丰富的开发者,都能从中受益匪浅,提升自己的开发技能。通过阅读本书,你将能够深入...
李刚的Struts 2.1权威指南:基于WebWork核心的MVC开发,写的很详细。一共有九个压缩文件。
李刚编著的《Struts2权威指南》是一本深入剖析该框架的书籍,它以丰富的实例和详尽的参数解析,帮助开发者从基础到高级全面掌握Struts2的使用。 1. **Struts2框架概述** - Struts2是Apache软件基金会下的一个开源...
4. **插件机制**:Struts2支持丰富的插件,如Freemarker和Velocity模板引擎、i18n国际化支持等。源码中可能会包含这些插件的使用示例,有助于了解插件如何与Struts2框架集成。 5. **OGNL(Object-Graph Navigation ...
1. **Struts2核心组件**:Struts2的核心库包含了处理请求、响应、拦截器、结果类型、插件机制等关键功能。开发者需要这个核心包来构建基于Action的控制器,实现MVC设计模式。 2. **依赖的库**:Struts2的正常工作...
总之,Struts2教程旨在帮助开发者更高效地构建Java EE应用,通过MVC模式实现业务逻辑、视图和控制的解耦,提高代码的可维护性和可复用性。随着对Struts2的深入学习,你将能够熟练运用这个框架来创建复杂的企业级Web...
《Struts2权威指南》是李刚所著的一本详细解析Struts2框架的书籍,其完整版涵盖了Struts2的核心概念、配置、实践应用以及与其他流行技术如Spring、JSF、Hibernate的整合。 首先,让我们深入理解Struts2的基础知识。...
《Struts2权威指南》由李刚编著,全面深入地介绍了Struts2框架的各个方面,是学习和掌握Struts2不可或缺的参考资料。 在Struts2框架中,主要涉及以下核心知识点: 1. **基础概念**:Struts2是Struts1的升级版,...
3. **插件架构**:Struts2拥有丰富的插件系统,如Tiles插件用于页面布局,Freemarker或Velocity插件用于视图渲染,这些插件极大地扩展了Struts2的功能。 4. **动作(Action)**:在Struts2中,Action类是业务逻辑的...
学习JEEE的经典教材,由李刚老师编著,这是最新的第三版,第3版保持了第2版内容全面、深入的特点,主要完成全部知识的升级。该书在2011年2月由电子工业出版社出版 由于上传文件大小限制本书被压缩为四个部分,此附件...
- **第6章Struts2的输入校验**:介绍如何利用Struts2内置的验证机制确保用户输入的有效性和安全性。 - **第7章上传和下载文件**:讨论文件上传和下载功能的实现方法,包括如何处理大文件、分片上传等。 - **第8章...
李刚老师编著的,Struts2权威指南完整版(上).pdf。可以帮助初学者入门,帮助入门者提高。
《Struts2权威指南》是李刚编著的一本深入讲解Struts2框架的书籍,包含了全面的理论知识和实践案例。 在提供的源代码中,我们可以看到以下几个文件和目录: 1. **目录.txt**:这本书的完整章节目录,它将帮助读者...
Struts 2是Java Web开发中的一个MVC框架,它提供了强大的控制层解决方案,能够简化视图与业务逻辑的交互。Struts 2的核心是Action和Result,Action负责处理用户请求,而Result负责展示结果。此外,Struts 2还支持...
Struts 2作为MVC(模型-视图-控制器)框架,主要处理用户请求和转发响应,它通过Action和Interceptor实现业务逻辑与展示逻辑的解耦,提供了丰富的插件和定制机制,增强了应用的可扩展性和灵活性。 Spring框架则是一...