JSON有一个非常经典的问题:JSONException: There is a cycle in the hierarchy!俗称死循环.解决这个问题至少有三种以上的办法,总之一句话就是过滤.今天尝试着从
反射的角度来阐述和解决这个问题.
一.“反射重组(姑且这么叫吧)”
废话不多说,直接上代码.以下代码,预设有两个实体类,Company及Product,它们为一对多双向关联映射。类Product中有属性company与之关联类Company.现在,需要以
列表形式展示Product,后台以JSON格式传递数据。
1 class 2 { 3 @RequestMapping 4 @ResponseBody 5 public void getproduct(HttpServletRequest request,HttpServletResponse response,Product product) throws Exception{ 6 PageBean<Product, Product> pageBean = this.getPageBean(request); 7 pageBean.setSearchCondObj(product); 8 PageBean<Product, Product> bean = this.productService.getProduct(pageBean); 9 Map<String, Object> map=new HashMap<String, Object>(); 10 JsonConfig config = new JsonConfig(); 11 12 //屏蔽掉相关联的实体属性,以及不需要在列表中展示的属性 13 14 config.setExcludes(new String[] {"description","companyperson","comment","productitems","companycontact"}); 15 // 把列表显示需要的实体属性传过去 16 17 config.registerJsonValueProcessor(Company.class, //调用registerJsonValueProcessor构造方法,初始化参数 18 new ObjectJsonValueProcessor(new String[]{"name","id"},Company.class)); 19 20 Map<String, Object> jsonMap = new HashMap<String, Object>(); 21 jsonMap.put("total", bean.getSize()); 22 jsonMap.put("rows", bean.getSource()); 23 JSONObject result = JSONObject.fromObject(jsonMap, config); 24 this.outPrint(response, request, result.toString()); 25 } 26 }
为了避免陷入"net.sf.json.JSONException: There is a cycle in the hierarchy!",以下这段代码是核心代码,它的核心是反射重组.代码出处为网络,非本人原创.
我添加了注释,以便理解查看.
1 package com.project.pojo; 2 3 import java.beans.PropertyDescriptor; 4 import java.lang.reflect.Method; 5 6 import net.sf.json.JSONObject; 7 import net.sf.json.JsonConfig; 8 import net.sf.json.processors.JsonValueProcessor; 9 10 /** 11 * 解决JSONObject.fromObject抛出"There is a cycle in the hierarchy"异常导致死循环的解决办法 12 * 以及实体属性无法传递的问题 13 * 此段代码为网络资料,非原创 14 */ 15 public class ObjectJsonValueProcessor implements JsonValueProcessor { 16 17 /** 18 * 需要留下的字段数组 19 */ 20 private String[] properties; 21 22 /** 23 * 需要做处理的复杂属性类型 24 */ 25 private Class<?> clazz; 26 27 /** 28 * 构造方法,参数必须 29 * @param properties 30 * @param clazz 31 */ 32 public ObjectJsonValueProcessor(String[] properties,Class<?> clazz){ 33 this.properties = properties; 34 this.clazz =clazz; 35 } 36 37 @Override 38 public Object processArrayValue(Object value, JsonConfig arg1) { 39 PropertyDescriptor pd = null; 40 Method method = null; 41 StringBuffer json = new StringBuffer("{"); 42 try{ 43 for(int i=0;i<properties.length;i++){ 44 pd = new PropertyDescriptor(properties[i], clazz); 45 method = pd.getReadMethod(); 46 String v = String.valueOf(method.invoke(value)); 47 json.append("'"+properties[i]+"':'"+v+"'"); 48 json.append(i != properties.length-1?",":""); 49 } 50 json.append("}"); 51 }catch (Exception e) { 52 e.printStackTrace(); 53 } 54 return JSONObject.fromObject(json.toString()); 55 return null; 56 } 57 58 @Override 59 public Object processObjectValue(String key, Object value, JsonConfig jsonConfig) { 60 //key为实体关联字段,即外键 61 PropertyDescriptor pd = null; 62 Method method = null; 63 StringBuffer json = new StringBuffer("{"); 64 try{ 65 for(int i=0;i<properties.length;i++){ 66 pd = new PropertyDescriptor(properties[i], clazz); 67 //反射:通过类PropertyDescriptor可以得到properties数组即相关联的实体中需要传递的属性,它的名称,类型以及getter,setter方法 68 method = pd.getReadMethod(); //得到属性的读取方法,即getter() 69 if (value != null){ 70 71 String v = String.valueOf(method.invoke(value));//执行getter(),当然也可以看出这里value 72 //即是一个实体类的字节码,在这里是Company.class,然后在组装JSON格式的字符串 73 74 json.append("'" + properties[i] + "':'" + v + "'"); 75 json.append(i != properties.length - 1 ? "," : ""); 76 77 } 78 } 79 80 json.append("}"); 81 System.out.println("json = "+json.toString()); 82 }catch (Exception e) { 83 e.printStackTrace(); 84 } 85 return JSONObject.fromObject(json.toString()); 86 } 87 }
为了更好的验证以及看清processObjectValue(),贴一段测试代码及结果:
1 class 2 { 3 public static void main(String[] args) 4 { 5 @Override 6 public Object processObjectValue(String key, Object value, JsonConfig jsonConfig) { 7 System.out.println("key :"+key); 8 PropertyDescriptor pd = null; 9 Method method = null; 10 StringBuffer json = new StringBuffer("{"); 11 try{ 12 for(int i=0;i<properties.length;i++){ 13 pd = new PropertyDescriptor(properties[i], clazz); 14 System.out.println("pd :"+pd); 15 16 method = pd.getReadMethod(); 17 18 if (value != null){ 19 System.out.println("value :"+value); 20 String v = String.valueOf(method.invoke(value)); 21 System.out.println("v :"+v); 22 json.append("'" + properties[i] + "':'" + v + "'"); 23 json.append(i != properties.length - 1 ? "," : ""); 24 } 25 26 } 27 28 json.append("}"); 29 System.out.println("json = "+json.toString()); 30 }catch (Exception e) { 31 e.printStackTrace(); 32 } 33 return JSONObject.fromObject(json.toString()); 34 } 35 } 36 }
测试结果:
在执行
config.registerJsonValueProcessor(Company.class,new ObjectJsonValueProcessor(new String[]{"name","id"},Company.class));
这段代码的时候,仅仅只是对ObjectJsonValueProcessor作了初始化,真正执行类ObjectJsonValueProcessor中processObjectValue(),还是在这里:
JSONObject result = JSONObject.fromObject(jsonMap, config);
通过观察JSONObject源码可以看到,在fromObject(jsonMap, config)中调用了fromDynaBean(DynaBean bean, JsonConfig jsonConfig),从名字中可以看出,
参数为一个动态JAVABEAN,即为在processObjectValue()中,通过反射得到关联实体的属性,然后动态组装。
1 public static JSONObject fromObject(Object object, JsonConfig jsonConfig) 2 { 3 if ((object == null) || (JSONUtils.isNull(object))) 4 return new JSONObject(true); 5 if ((object instanceof Enum)) 6 throw new JSONException("'object' is an Enum. Use JSONArray instead"); 7 if (((object instanceof Annotation)) || ((object != null) && (object.getClass().isAnnotation()))) 8 { 9 throw new JSONException("'object' is an Annotation."); 10 }if ((object instanceof JSONObject)) 11 return _fromJSONObject((JSONObject)object, jsonConfig); 12 if ((object instanceof DynaBean)) 13 return _fromDynaBean((DynaBean)object, jsonConfig);//执行这里 14 if ((object instanceof JSONTokener)) 15 return _fromJSONTokener((JSONTokener)object, jsonConfig); 16 if ((object instanceof JSONString)) 17 return _fromJSONString((JSONString)object, jsonConfig); 18 if ((object instanceof Map)) 19 return _fromMap((Map)object, jsonConfig); 20 if ((object instanceof String)) 21 return _fromString((String)object, jsonConfig); 22 if ((JSONUtils.isNumber(object)) || (JSONUtils.isBoolean(object)) || (JSONUtils.isString(object))) 23 { 24 return new JSONObject(); 25 }if (JSONUtils.isArray(object)) { 26 throw new JSONException("'object' is an array. Use JSONArray instead"); 27 } 28 return _fromBean(object, jsonConfig); 29 }
然后再执行processObjectValue(),得到关联实体需要的属性组成的JSON对象,然后再调用 setValue(jsonObject, key, value, value.getClass(), jsonConfig, bypass)
组装完整的JSON对象
1 if (!exclusions.contains(key)) 2 { 3 Object value = entry.getValue(); 4 if ((jsonPropertyFilter == null) || (!jsonPropertyFilter.apply(map, key, value))) 5 { 6 if (value != null) { 7 JsonValueProcessor jsonValueProcessor = jsonConfig.findJsonValueProcessor(value.getClass(), key); 8 9 if (jsonValueProcessor != null) { 10 value = jsonValueProcessor.processObjectValue(key, value, jsonConfig);//执行processObjectValue(),得到关联实体需要的属性组成的JSON对象 11 bypass = true; 12 if (!JsonVerifier.isValidJsonValue(value)) { 13 throw new JSONException("Value is not a valid JSON value. " + value); 14 } 15 } 16 setValue(jsonObject, key, value, value.getClass(), jsonConfig, bypass);//组装完整的JSON对象 17 }
二.其它方法
通过JsonValueProcessor解决JSON死循环的问题,到此基本描述清楚了.思虑再在一,我觉得还有必要罗嗦几句,即讲一讲其它三种解决JSON死循环的方法,不然怎能看出用
JsonValueProcessor解决的好处呢?
1:过滤屏蔽,如:
1 1 JsonConfig config = new JsonConfig(); 2 2 config.setExcludes(new String[] {"company"});
如此可以过滤掉不需要的属性以及关联实体属性,这样当然不会报错,但是如果我需要展示"company"的属性呢?这样显然无法满足需求。
2:使用JSON属性过滤器PropertyFilter()
1 config.setJsonPropertyFilter(new PropertyFilter() { 2 3 @Override 4 /** 5 * argo:当前进行操作的实体,如:product 6 * arg1:实体属性 7 * arg2:实体属性的类型 8 */ 9 public boolean apply(Object arg0, String arg1, Object arg2) { 10 if(arg1.equals("company")){ 11 return true;//表示过滤掉此属性 12 } 13 return false;//表示正常操作 14 } 15 });
此方法实际上跟方法1所能达到的效果一样,但是更为复杂。当然可以在[if(arg1.equals("company"))]这里通过反射得到setter方法,重新设置属性值,但是反射不能改
变属性的类型和方法参数类型,所以还是不能避免死循环。
3:使用JsonValueProcessor()
1 config.setCycleDetectionStrategy(CycleDetectionStrategy.LENIENT); //首先避免掉死循环 2 config.setExcludes(new String[]{"handler","hibernateLazyInitializer"}); //设置延迟加载 3 config.registerJsonValueProcessor(Date.class,new JsonValueProcessor() { 4 public Object processObjectValue(String arg0, Object arg1, JsonConfig arg2) { 5 SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 6 Date d=(Date) arg1; 7 return sdf.format(d); 8 } 9 public Object processArrayValue(Object arg0, JsonConfig arg1) { 10 return null; 11 } 12 });
这段代码完全能够实现既避免死循环又得到"company"的全部属性。功能上没有问题,但是效率上有大问题。尽管我在实体和属性层面上都设置了延迟加载,但是product还
是通过company把所有的区域信息加载出来,所形成的JSON字符串足足有3.5MB,严重影响了效率。
三.小结
其它的方法应该还有,我甚至见过有人新建一个JAVABean或新建一个内部类,在通过循环赋值构建一个没有关联实体的单独的JAVABean,来作为构建JSON对象的实体类。既
然有这么多的方法可能解决问题,就应该寻求一个高效的解决方法,我认为使用这种姑且叫做“反射重组”的方法是比较高效的,它可以代码重用,可以有效得到需要的内容。欢
迎讨论,轻拍。