首先我们看看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}