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);
- "font-size: small;">"color: #ff00ff;">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);
- "font-size: small;">"color: #ff00ff;">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();
- return freemarkerManager.buildTemplateModel(stack, action, servletContext, request, response, wrapper);
- }
- "font-size: small;">"color: #ff00ff;">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();
- 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);
- }
- }
- }
- "font-size: small;">"color: #ff00ff;">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 {
-
-
-
-
-
- public void execute(ActionInvocation invocation) throws Exception;
- }
- "font-size: small;">"color: #ff00ff;">public interface Result extends Serializable {
-
-
-
-
-
- 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);
-
-
- 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;
- }
-
-
-
-
-
-
-
-
-
-
-
- public void execute(ActionInvocation invocation) throws Exception {
- lastFinalLocation = conditionalParse(location, invocation);
- doExecute(lastFinalLocation, invocation);
- }
-
-
-
-
-
-
-
-
- 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 {
-
-
- return URLEncoder.encode(parsedValue.toString(), "UTF-8");
- }
- catch(UnsupportedEncodingException e) {
- _log.warn("error while trying to encode ["+parsedValue+"]", e);
- }
- }
- }
- return parsedValue;
- }
- });
- } else {
- return param;
- }
- }
-
-
-
-
-
-
-
-
-
-
- protected abstract void doExecute(String finalLocation, ActionInvocation invocation) throws Exception;
- }
- "font-size: small;">"color: #ff00ff;">public abstract class StrutsResultSupport implements Result, StrutsStatics {
-
- private static final Log _log = LogFactory.getLog(StrutsResultSupport.class);
-
-
- 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;
- }
-
-
-
-
-
-
-
-
-
-
-
- public void execute(ActionInvocation invocation) throws Exception {
- lastFinalLocation = conditionalParse(location, invocation);
- doExecute(lastFinalLocation, invocation);
- }
-
-
-
-
-
-
-
-
- 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 {
-
-
- return URLEncoder.encode(parsedValue.toString(), "UTF-8");
- }
- catch(UnsupportedEncodingException e) {
- _log.warn("error while trying to encode ["+parsedValue+"]", e);
- }
- }
- }
- return parsedValue;
- }
- });
- } else {
- return param;
- }
- }
-
-
-
-
-
-
-
-
-
-
- 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 {
-
- }
- }
- "font-size: small;">"color: #ff00ff;">public class CustomerResult implements Result {
-
- public void execute(ActionInvocation invocation) throws Exception {
-
- }
- }
- <result-type name="customerResult" class="com.javaeye.struts2.CustomerResult"/>
- <span style="font-size: small;"><span style="color: #ff00ff;"><result-type name="customerResult" class="com.javaeye.struts2.CustomerResult"/>
- span>span>
常用的Result
接下来,大致介绍一下Struts2内部已经实现的Result,并看看他们是如何工作的。
dispatcher
- <result-type name="dispatcher" class="org.apache.struts2.dispatcher.ServletDispatcherResult" default="true"/>
- <span style="font-size: small;"><span style="color: #ff00ff;"><result-type name="dispatcher" class="org.apache.struts2.dispatcher.ServletDispatcherResult" default="true"/>
- span>span>
dispatcher主要用于返回JSP,HTML等以页面为基础View视图,这个也是Struts2默认的Result类型。
在使用dispatcher时,唯一需要指定的,是JSP或者HTML页面的位置,这个位置将被用于定位返回的页面:
- <result name="success">/index.jspresult>
- <span style="font-size: small;"><span style="color: #ff00ff;"><result name="success">/index.jspresult>span>span>
而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"/>
- <span style="font-size: small;"><span style="color: #ff00ff;"><result-type name="freemarker" class="org.apache.struts2.views.freemarker.FreemarkerResult"/>
- <result-type name="velocity" class="org.apache.struts2.dispatcher.VelocityResult"/>
- span>span>
随
着模板技术的越来越流行,使用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();
-
-
-
- if (preTemplateProcess(template, model)) {
- try {
-
- template.process(model, getWriter());
- } finally {
-
- postTemplateProcess(template, model);
- }
- }
- }
- "font-size: small;">"color: #ff00ff;">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();
-
-
-
- if (preTemplateProcess(template, model)) {
- try {
-
- template.process(model, getWriter());
- } finally {
-
- 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"/>
- <span style="font-size: small;"><span style="color: #ff00ff;"><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"/>span>span>
如
果你在Action执行完毕后,希望执行另一个Action,有2种方式可供选择。一种是forward,另外一种是redirect。有关
forward和redirect的区别,这里我就不再展开,这应该属于Java程序员的基本知识。在Struts2中,分别对应这两种方式的
Result,就是chain和redirect。
先来谈谈redirect,既然是重定向,那么源地址与目标地址之间是2个不同的
HttpServletRequest。所以目标地址将无法通过ValueStack等Struts2的特性来获取源Action中的数据。如果你需要对
目标地址传递参数,那么需要在目标地址url或者配置文件中指出:
-
阅读(1337) | 评论(0) | 转发(0) |