Struts2 Result-type(封装Action层到View层的跳转逻辑)(1)
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" 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" + 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" 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" 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。