我存在,你深深的循環里--從反射看JSON死循環


 

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 }
View Code

 

為了避免陷入"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 }
processObjectValue測試代碼

 測試結果:

 

在執行

 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   }
JSONObject.fromObject(jsonMap, config)

 然后再執行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             }
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()

 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對象的實體類。既

然有這么多的方法可能解決問題,就應該尋求一個高效的解決方法,我認為使用這種姑且叫做“反射重組”的方法是比較高效的,它可以代碼重用,可以有效得到需要的內容。歡

迎討論,輕拍。

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM