首先我们看看struts2中插件struts2-json-plugin2.2.1的使用:
1、xml配置:当然package还是得直接或间接继承自json-default
<action name="list" class="com.sample.s2web.action.user.UserAction" method="list" >
<result name="success" type="json">
<param name="ignoreHierarchy">true</param>
<param name="excludeNullProperties">true</param>//排除空值属性
<param name="excludeProperties">message</param>//排除属性
<param name="includeProperties"></param>//包含属性,默认包含所有属性,如果此处填了属性那么就只会包含该
<param name="root">pageModule</param>
<result>
</action>
2、UserAction.java中的list方法:
public String list() {
try {
pageModule = new PageModule<User>();
pageModule.setList(null);
pageModule.setTotalCount(30);
pageModule.setPage(1);
pageModule.setMessage("Result");
} catch (Exception e) {
e.printStackTrace();
}
return SUCCESS;
}
我们来分析下配置和相应的结果:
1、按照上面的配置,这里对root对象pageModule会忽略父类对象:
<param name="ignoreHierarchy">true</param>//default : true
这里主要是内省(Introspector),见JSONWriter中的方法:bean
info = ((object == this.root) && this.ignoreHierarchy) ? Introspector.getBeanInfo(clazz, clazz
.getSuperclass()) : Introspector.getBeanInfo(clazz);
当然这里只会忽略root的父类,而该root的成员变量如果也有继承那就不会被忽略
考虑如下类图:
我们将Student作为root对象,看看采用上面配置生产的JSON:
Student s = new Student();
s.setBirthday(new Date());
s.setUsername("robin");
s.setAddress("chengdu");
s.setSalary(12345);
s.setTitle("SEE");
SCourse c = new SCourse();
c.setName("JAVA");
c.setDesc("xxxx");
c.setPrice(123);
c.setPublish("Null");
s.setCourse(c);
return s;
JSONUtil.serialize(format(), null, null, true, false, false)
//{"address":"chengdu","course":{"desc":"xxxx","name":"JAVA","price":123,"publish":"Null"},"courses":null,"salary":12345,"title":"SEE"}
由此可见,在生产的JSON串中只忽略了root对象Student的父类,而没有忽略成员变量course的父类。这也是导致某些JSON转换中循环引用的问题。我们来看看JSONWriter.java中的源码:
//通过内省来对bean属性进行序列化
private void bean(Object object) throws JSONException {
this.add("{");
BeanInfo info;
try {
Class clazz = object.getClass();
//关键在这里,是否需要忽略父类,这里有两个条件:当前bean是否是根对象和ignoreHierarchy的设置。在下次递归调用此方法时object就不再是root
info = ((object == this.root) && this.ignoreHierarchy) ? Introspector.getBeanInfo(clazz, clazz
.getSuperclass()) : Introspector.getBeanInfo(clazz);
PropertyDescriptor[] props = info.getPropertyDescriptors();
boolean hasData = false;
for (int i = 0; i < props.length; ++i) {
PropertyDescriptor prop = props[i];
String name = prop.getName();
Method accessor = prop.getReadMethod();
Method baseAccessor = null;
if (clazz.getName().indexOf("$$EnhancerByCGLIB$$") > -1) {
try {
baseAccessor = Class.forName(
clazz.getName().substring(0, clazz.getName().indexOf("$$"))).getMethod(
accessor.getName(), accessor.getParameterTypes());
} catch (Exception ex) {
LOG.debug(ex.getMessage(), ex);
}
} else if (clazz.getName().indexOf("$$_javassist") > -1) {
try {
baseAccessor = Class.forName(
clazz.getName().substring(0, clazz.getName().indexOf("_$$")))
.getMethod(accessor.getName(), accessor.getParameterTypes());
} catch (Exception ex) {
LOG.debug(ex.getMessage(), ex);
}
} else
baseAccessor = accessor;
if (baseAccessor != null) {
JSON json = baseAccessor.getAnnotation(JSON.class);
if (json != null) {
if (!json.serialize())
continue;
else if (json.name().length() > 0)
name = json.name();
}
// ignore "class" and others
if (this.shouldExcludeProperty(clazz, prop)) {
continue;
}
String expr = null;
if (this.buildExpr) {
expr = this.expandExpr(name);
if (this.shouldExcludeProperty(expr)) {
continue;
}
expr = this.setExprStack(expr);
}
Object value = accessor.invoke(object, new Object[0]);
boolean propertyPrinted = this.add(name, value, accessor, hasData);
hasData = hasData || propertyPrinted;
if (this.buildExpr) {
this.setExprStack(expr);
}
}
}
// special-case handling for an Enumeration - include the name() as
// a property */
if (object instanceof Enum) {
Object value = ((Enum) object).name();
this.add("_name", value, object.getClass().getMethod("name"), hasData);
}
} catch (Exception e) {
throw new JSONException(e);
}
this.add("}");
}
在工作中也曾遇到一个问题,就是作为当然root对象的一个成员变量的父类,在一个public getXXX(可***的方法)会导致NPE,而一直抛出异常java.lang.reflect.InvocationTargetException导致困扰不短时间,就是没搞清楚这个时候的父类不会被忽略。还有一个问题就是同事在做用户权限时,基于RBAC的用户权限,在设计时子类和父类存在多对多的映射,导致在JSON转换时抛出异常,很明显这就是循环引用的问题。
2、接下来的excludeNullProperties指明了在root中(这里pageModule)如果有空值将被忽略,同时接下来的两个属性采用正则匹配排除root中的属性不进行json转化,而includeProperties则指明了包含的属性,默认会全部(当然如果excludeNullProperties=true对于为null或空值的属性不会进行JSON转化)。
如上面的一个配置,对于Action中的方法,那么最终的JSON为:
{"page":1,"totalCount":30}
3、上面是自己在使用时常用的几个参数,在JSONInterceptor类中还包含其他几个,这里介绍一二:
wrapWithComments:将JSON添加注释:/* {..json..} */单并不推荐使用,在使用时需要eval(),可能会有XSS攻击
prefix: 生成前缀,如果设置为true,会在json加上前缀"{}&& "这有助于防止被劫持.
enableGZIP:压缩输出
前面说了JSON插件的基本配置,在JSONInterceptor中对root或json进行转换的是JSONUtil,主要是调用了
Object obj = JSONUtil.deserialize(request.getReader());
String json = JSONUtil.serialize(result, excludeProperties, includeProperties,
ignoreHierarchy, excludeNullProperties);
在JSONUtil中提供了JSON来对需要***的root进行设置:
| 注释名 | 说明 | 默认值 | ||
| name | 配置JSON中name | "" | ||
| serialize | 可序列化serialize | true | ||
| deserialize | 不进行*** | true | ||
| format | Date格式化 | yyyy-MM-dd'T'HH:mm:ss |
对上面的进行设置:
@JSON(format="yyyy-MM-dd HH:mm:ss")
public Date getCreateDay() {
return createDay;
}
[JSON]{"createDay":"2012-07-14 21:54:27","message":"Result","page":1,"totalCount":30}