zoukankan      html  css  js  c++  java
  • 我存在,你深深的循环里--从反射看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对象的实体类。既

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

    迎讨论,轻拍。

      

  • 相关阅读:
    BZOJ 2752: [HAOI2012]高速公路(road)
    codevs 1979 第K个数
    洛谷 P2680 运输计划
    hdu 3501 Calculation 2
    POJ 2417 Discrete Logging
    比较数组和字典
    js事件之event.preventDefault()与event.stopPropagation()用法区别
    alert
    js基本类型的包装对象
    js取自定义data属性
  • 原文地址:https://www.cnblogs.com/eryuan/p/3627638.html
Copyright © 2011-2022 走看看