目前的java web项目如果使用了Struts2框架,那么几乎没有不遇到使用json格式传输数据的场景,尤其是涉及到需要满足ajax异步请求需求的时候。struts2集成json很自由,可以有多种方法,这里记录一种个人认为比较简洁但也多样化的方案,参考了<<Struts 2 in Action>>一书和实际项目经验。
原理
需要自定义一种结果类型,该类型负责处理Action返回Json格式数据的场景,需要解决以下几个问题:一是能够尽量使用通用的途径从Action取到其返回的Json数据,二是正确的设置响应并传输给浏览器客户端,包括设置响应头部和响应体。
import java.io.PrintWriter;
import java.util.Map;
import org.apache.struts2.ServletActionContext;
import com.alibaba.fastjson.JSON;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.Result;
import com.opensymphony.xwork2.util.ValueStack;
public class JSONResult implements Result { //1
public static final String DEFAULT_PARAM = "xproperty"; //2
private static final long serialVersionUID = -3133273414415678697L;
private String xproperty;
public void execute(ActionInvocation invocation) throws Exception {
Map<String, Object> jsonMap = null;
Object jsonObject = null;
Object action = invocation.getAction();
if (action instanceof JSONable) {
jsonMap = ((JSONable)action).getJsonMap();
} else {
ValueStack stack = invocation.getStack();
if (xproperty == null || "".equals(xproperty.trim())) {
xproperty = "jsonObject"; //3
}
jsonObject = stack.findValue(xproperty);
}
ServletActionContext.getResponse().setContentType("text/plain"); //4
PrintWriter printWriter = ServletActionContext.getResponse().getWriter();
String jsonResultString = null;
if (jsonMap != null) {
jsonResultString = JSON.toJSONString(jsonMap); //5
} else if (jsonObject != null) {
jsonResultString = JSON.toJSONString(jsonObject);
}
printWriter.println(jsonResultString);
}
public String getXproperty() {
return xproperty;
}
public void setXproperty(String xproperty) {
this.xproperty = xproperty;
}
}
说明:
1 任何结果类型均需要实现com.opensymphony.xwork2.Result接口。
2 动态传递的action属性名称。
3 默认传递的action属性名称。
4 可以明确的指定返回的文档类型是"text/json",不过查阅MIME规范似乎没有明确的提到有"text/json"这种类型,为了保险起见这里还是设置成"text/plain"。
5 将Java数据结构转换成标准的Json格式字符串输出,这里的实现可以借助多种json解析包工具。
为了满足第一个问题,想到两种方案。第一个方案是参照STRUTS2的模型,模仿Interceptor与Action的数据访问交互原理,设置一个接口JSONable,任何需要返回Json数据的Action均实现该接口:
import java.util.Map;
public interface JSONable {
Map<String, Object> getJsonMap();
}
该接口非常简单,仅仅需要Action将返回的Json数据全部以key-value的格式存储于一个Map数据结构中,该Map作为Action与Result之间传递数据的桥梁:
if (action instanceof JSONable) {
jsonMap = ((JSONable)action).getJsonMap();
}
第二个方案不需要给Action增加约束条件,即不需要Action实现任何接口或继承任何类型,而是借助于ValueStack强大的查询能力。这个方案的亮点就在于将Action需要输出的Json格式数据对应的属性名称作为参数动态传递给自定义结果类型JSONResult,在JSONResult中以xproperty属性接受传递过来的数据属性名称,然后借助ValueStack在ActionContext中查询:
jsonObject = stack.findValue(xproperty);
为了最大限度的满足可扩展性并与struts2的配置风格保持一致,还增加了一个默认属性名称:
if (xproperty == null || "".equals(xproperty.trim())) {
xproperty = "jsonObject";
}
用法
首先需要将自定义结果类型配置于需要返回json数据的包中(这个包最好继承struts-default):
<result-types>
<result-type name="customJSON" class="com.portfolio.chapterEight.JSONResult" />
</result-types>
第一种方案的配置和Action样例:
<action name="RetrieveJsonMap" class="com.portfolio.chapterEight.JsonableAction">
<result type="customJSON"/>
</action>
<action name="RetrieveJsonMap1" class="com.portfolio.chapterEight.JsonableAction" method="getJsonInfo1">
<result type="customJSON"/>
</action>
<action name="RetrieveJsonMap2" class="com.portfolio.chapterEight.JsonableAction" method="getJsonInfo2">
<result type="customJSON"/>
</action>
import java.util.HashMap;
import java.util.Map;
import com.opensymphony.xwork2.ActionSupport;
public class JsonableAction extends ActionSupport implements JSONable {
private static final long serialVersionUID = 3838691111313679589L;
private Map<String, Object> resultMap;
@Override
public String execute() throws Exception {
resultMap = new HashMap<String, Object>();
resultMap.put("name", "warhin");
resultMap.put("sex", "male");
return SUCCESS;
}
public String getJsonInfo1() {
resultMap = new HashMap<String, Object>();
resultMap.put("name", "liyong");
resultMap.put("pwd", "111");
return SUCCESS;
}
public String getJsonInfo2() {
resultMap = new HashMap<String, Object>();
resultMap.put("name", "taibing");
resultMap.put("weight", "60kg");
return SUCCESS;
}
public Map<String, Object> getJsonMap() {
return resultMap;
}
}
第二种方案的配置和Action样例:
<action name="RetrieveJsonObject1" class="com.portfolio.chapterEight.JsonableAction2" method="getJsonInfo1">
<result type="customJSON">username</result>
</action>
<action name="RetrieveJsonObject2" class="com.portfolio.chapterEight.JsonableAction2" method="getJsonInfo2">
<result type="customJSON">userList</result>
</action>
<action name="RetrieveJsonObject3" class="com.portfolio.chapterEight.JsonableAction2" method="getJsonInfo3">
<result type="customJSON">userMap</result>
</action>
<action name="RetrieveJsonObject4" class="com.portfolio.chapterEight.JsonableAction2" method="getJsonInfo4">
<result type="customJSON"/>
</action>
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.opensymphony.xwork2.ActionSupport;
public class JsonableActoin2 extends ActionSupport {
private static final long serialVersionUID = -2004351219002739460L;
private String username;
private List<String> userList;
private Map<String, Object> userMap;
private Set<String> jsonObject;
public String getJsonInfo1() {
username = "warhin";
return SUCCESS;
}
public String getJsonInfo2() {
userList = new ArrayList<String>();
userList.add("1");
userList.add("2");
userList.add("3");
return SUCCESS;
}
public String getJsonInfo3() {
userMap = new HashMap<String, Object>();
userMap.put("1", "1");
userMap.put("2", "2");
userMap.put("3", "3");
return SUCCESS;
}
public String getJsonInfo4() {
jsonObject = new HashSet<String>();
jsonObject.add("1");
jsonObject.add("2");
jsonObject.add("3");
return SUCCESS;
}
public String getUsername() {
return username;
}
public List<String> getUserList() {
return userList;
}
public Map<String, Object> getUserMap() {
return userMap;
}
public Set<String> getJsonObject() {
return jsonObject;
}
}
第二种方案能够正常工作的前提是ValueStack能够找到Action的属性,所以Action的属性必须提供getter方法。
这两种方案是互斥的,即采用第一种方案就不可以再使用第二种方案了,jsonMap方案优于jsonObject方案。