Struts2将Result列为一个独立的层次,可以说是整个Struts2的Action层架构设计中的另外一个精华所在。Result之所以成为一个层次,其实是为了解决MVC框架中,如何从Control层转向View层这样一个问题而存在的。所以,接下来我们详细讨论一下Result的方方面面。
以JSP转向为例,在Struts2自带的ServletDispatcherResult中就存在着核心的JSP跳转逻辑:
<p><textarea cols="50" rows="15" name="code" class="classname">HttpServletRequest request = ServletActionContext.getRequest();
RequestDispatcher dispatcher = request.getRequestDispatcher(finalLocation);
...
dispatcher.forward(request, response);
</p>
再以Redirect重定向为例,在Struts2自带的ServletRedirectResult中,也同样存在着重定向的核心代码:
<p><textarea cols="50" rows="15" name="code" class="classname">
<script src="/js/SyntaxHighlighter/jquery.highlighter.js?v=20091222" type="text/javascript"></script>
<script src="/js/SyntaxHighlighter/highlighter.js?v=20091222" type="text/javascript"></script>
HttpServletResponse response = (HttpServletResponse) ctx.get(ServletActionContext.HTTP_RESPONSE);
...
response.sendRedirect(finalLocation);
</p>
View层的展现方式很多,除了传统的JSP以外,还有类似Freemarker/Velocity这样的模板。根据模板显示的基本原理,需要将预先定义好的模板(Template)和需要展示的数据(Model)组织起来,交给模板引擎,才能够正确显示。而这部分工作,就由Result层来完成。
<p>
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);
}
</p>
有的时候,针对同一种类型的View展示,我们可能会有不同的输出行为。具体来说,可能有时候,我们需要对输出流指定特定的BufferSize、Encoding等等。Result层,作为一个独立的层次,可以提供极大的扩展性,从而保证我们能够定义自己期望的输出类型。
以Struts2自带的HttpHeaderResult为例:
<p>
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);
}
}
}
</p>
看看Struts2针对这个接口实现的一个抽象类,它规定了许多默认实现:
<p>
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;
}
</p>