从反射看JSON死循环

 

JSON有一个非常经典的问题:JSONException: There is a cycle in the hierarchy!俗称死循环.解决这个问题至少有三种以上的办法,总之一句话就是过滤.今天尝试着从

反射的角度来阐述和解决这个问题.

 

一.“反射重组(姑且这么叫吧)”

 

废话不多说,直接上代码.以下代码,预设有两个实体类,Company及Product,它们为一对多双向关联映射。类Product中有属性company与之关联类Company.现在,需要以

列表形式展示Product,后台以JSON格式传递数据。

从反射看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 }
View Code

 

为了避免陷入"net.sf.json.JSONException: There is a cycle in the hierarchy!",以下这段代码是核心代码,它的核心是反射重组.代码出处为网络,非本人原创.

我添加了注释,以便理解查看.

从反射看JSON死循环
 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 }
从反射看JSON死循环

 

为了更好的验证以及看清processObjectValue(),贴一段测试代码及结果:

从反射看JSON死循环
 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 }
processObjectValue测试代码

 测试结果:

从反射看JSON死循环

 

在执行

1
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()中,通过反射得到关联实体的属性,然后动态组装。

从反射看JSON死循环
 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   }
JSONObject.fromObject(jsonMap, config)

 然后再执行processObjectValue(),得到关联实体需要的属性组成的JSON对象,然后再调用 setValue(jsonObject, key, value, value.getClass(), jsonConfig, bypass)

 组装完整的JSON对象

从反射看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             }
fromDynaBean(DynaBean bean, JsonConfig jsonConfig)

 

二.其它方法 

通过JsonValueProcessor解决JSON死循环的问题,到此基本描述清楚了.思虑再在一,我觉得还有必要罗嗦几句,即讲一讲其它三种解决JSON死循环的方法,不然怎能看出用

JsonValueProcessor解决的好处呢?

 1:过滤屏蔽,如: 

1  1 JsonConfig config = new JsonConfig(); 
2  2 config.setExcludes(new String[] {"company"});

 如此可以过滤掉不需要的属性以及关联实体属性,这样当然不会报错,但是如果我需要展示"company"的属性呢?这样显然无法满足需求。

 

2:使用JSON属性过滤器PropertyFilter()

从反射看JSON死循环
 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         });
从反射看JSON死循环

此方法实际上跟方法1所能达到的效果一样,但是更为复杂。当然可以在[if(arg1.equals("company"))]这里通过反射得到setter方法,重新设置属性值,但是反射不能改变属性的类型和方法参数类型,

所以还是不能避免死循环。

 

3:使用JsonValueProcessor()

从反射看JSON死循环
 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         }); 
从反射看JSON死循环

 

这段代码完全能够实现既避免死循环又得到"company"的全部属性。功能上没有问题,但是效率上有大问题。尽管我在实体和属性层面上都设置了延迟加载,但是product还

是通过company把所有的区域信息加载出来,所形成的JSON字符串足足有3.5MB,严重影响了效率。

 

三.小结

其它的方法应该还有,我甚至见过有人新建一个JAVABean或新建一个内部类,在通过循环赋值构建一个没有关联实体的单独的JAVABean,来作为构建JSON对象的实体类。既

然有这么多的方法可能解决问题,就应该寻求一个高效的解决方法,我认为使用这种姑且叫做“反射重组”的方法是比较高效的,它可以代码重用,可以有效得到需要的内容。欢

迎讨论,轻拍。

  

从反射看JSON死循环,布布扣,bubuko.com

从反射看JSON死循环

上一篇:Mybatis SQL映射文件详解


下一篇:不修改代码优化 ASP.NET 网站性能的一些方法