一步一步跟小弟我学Struts2 —— Result机制,让视图更丰富
一步一步跟我学Struts2 —— Result机制,让视图更丰富专栏地址:http://www.iteye.com/wiki/struts2/1462-res
一步一步跟我学Struts2 —— Result机制,让视图更丰富
专栏地址: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跳转逻辑:
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" src="/img/2012/07/01/04064415572.gif"><result-type name="dispatcher" 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" src="/img/2012/07/01/04064415572.gif">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" src="/img/2012/07/01/04064415572.gif"><!-- The redirect-action url generated will be : /genReport/generateReport.jsp?reportType=pie&width=100&height=100 --> <action name="gatherReportInfo" 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" src="/img/2012/07/01/04064415572.gif"><result-type name="stream" src="/img/2012/07/01/04064415572.gif"><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>??</o