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

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

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

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

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

 class
{
@RequestMapping
@ResponseBody
public void getproduct(HttpServletRequest request,HttpServletResponse response,Product product) throws Exception{
PageBean<Product, Product> pageBean = this.getPageBean(request);
pageBean.setSearchCondObj(product);
PageBean<Product, Product> bean = this.productService.getProduct(pageBean);
Map<String, Object> map=new HashMap<String, Object>();
JsonConfig config = new JsonConfig(); //屏蔽掉相关联的实体属性,以及不需要在列表中展示的属性 config.setExcludes(new String[] {"description","companyperson","comment","productitems","companycontact"});
// 把列表显示需要的实体属性传过去 config.registerJsonValueProcessor(Company.class, //调用registerJsonValueProcessor构造方法,初始化参数
new ObjectJsonValueProcessor(new String[]{"name","id"},Company.class)); Map<String, Object> jsonMap = new HashMap<String, Object>();
jsonMap.put("total", bean.getSize());
jsonMap.put("rows", bean.getSource());
JSONObject result = JSONObject.fromObject(jsonMap, config);
this.outPrint(response, request, result.toString());
}
}

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

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

 package com.project.pojo;

 import java.beans.PropertyDescriptor;
import java.lang.reflect.Method; import net.sf.json.JSONObject;
import net.sf.json.JsonConfig;
import net.sf.json.processors.JsonValueProcessor; /**
* 解决JSONObject.fromObject抛出"There is a cycle in the hierarchy"异常导致死循环的解决办法
* 以及实体属性无法传递的问题
* 此段代码为网络资料,非原创
*/
public class ObjectJsonValueProcessor implements JsonValueProcessor { /**
* 需要留下的字段数组
*/
private String[] properties; /**
* 需要做处理的复杂属性类型
*/
private Class<?> clazz; /**
* 构造方法,参数必须
* @param properties
* @param clazz
*/
public ObjectJsonValueProcessor(String[] properties,Class<?> clazz){
this.properties = properties;
this.clazz =clazz;
} @Override
public Object processArrayValue(Object value, JsonConfig arg1) {
PropertyDescriptor pd = null;
Method method = null;
StringBuffer json = new StringBuffer("{");
try{
for(int i=0;i<properties.length;i++){
pd = new PropertyDescriptor(properties[i], clazz);
method = pd.getReadMethod();
String v = String.valueOf(method.invoke(value));
json.append("'"+properties[i]+"':'"+v+"'");
json.append(i != properties.length-1?",":"");
}
json.append("}");
}catch (Exception e) {
e.printStackTrace();
}
return JSONObject.fromObject(json.toString());
return null;
} @Override
public Object processObjectValue(String key, Object value, JsonConfig jsonConfig) {
//key为实体关联字段,即外键
PropertyDescriptor pd = null;
Method method = null;
StringBuffer json = new StringBuffer("{");
try{
for(int i=0;i<properties.length;i++){
pd = new PropertyDescriptor(properties[i], clazz);
//反射:通过类PropertyDescriptor可以得到properties数组即相关联的实体中需要传递的属性,它的名称,类型以及getter,setter方法
method = pd.getReadMethod(); //得到属性的读取方法,即getter()
if (value != null){ String v = String.valueOf(method.invoke(value));//执行getter(),当然也可以看出这里value
//即是一个实体类的字节码,在这里是Company.class,然后在组装JSON格式的字符串 json.append("'" + properties[i] + "':'" + v + "'");
json.append(i != properties.length - 1 ? "," : ""); }
} json.append("}");
System.out.println("json = "+json.toString());
}catch (Exception e) {
e.printStackTrace();
}
return JSONObject.fromObject(json.toString());
}
}

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

 class
{
public static void main(String[] args)
{
@Override
public Object processObjectValue(String key, Object value, JsonConfig jsonConfig) {
System.out.println("key :"+key);
PropertyDescriptor pd = null;
Method method = null;
StringBuffer json = new StringBuffer("{");
try{
for(int i=0;i<properties.length;i++){
pd = new PropertyDescriptor(properties[i], clazz);
System.out.println("pd :"+pd); method = pd.getReadMethod(); if (value != null){
System.out.println("value :"+value);
String v = String.valueOf(method.invoke(value));
System.out.println("v :"+v);
json.append("'" + properties[i] + "':'" + v + "'");
json.append(i != properties.length - 1 ? "," : "");
} } json.append("}");
System.out.println("json = "+json.toString());
}catch (Exception e) {
e.printStackTrace();
}
return JSONObject.fromObject(json.toString());
}
}
}

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

 public static JSONObject fromObject(Object object, JsonConfig jsonConfig)
{
if ((object == null) || (JSONUtils.isNull(object)))
return new JSONObject(true);
if ((object instanceof Enum))
throw new JSONException("'object' is an Enum. Use JSONArray instead");
if (((object instanceof Annotation)) || ((object != null) && (object.getClass().isAnnotation())))
{
throw new JSONException("'object' is an Annotation.");
}if ((object instanceof JSONObject))
return _fromJSONObject((JSONObject)object, jsonConfig);
if ((object instanceof DynaBean))
return _fromDynaBean((DynaBean)object, jsonConfig);//执行这里
if ((object instanceof JSONTokener))
return _fromJSONTokener((JSONTokener)object, jsonConfig);
if ((object instanceof JSONString))
return _fromJSONString((JSONString)object, jsonConfig);
if ((object instanceof Map))
return _fromMap((Map)object, jsonConfig);
if ((object instanceof String))
return _fromString((String)object, jsonConfig);
if ((JSONUtils.isNumber(object)) || (JSONUtils.isBoolean(object)) || (JSONUtils.isString(object)))
{
return new JSONObject();
}if (JSONUtils.isArray(object)) {
throw new JSONException("'object' is an array. Use JSONArray instead");
}
return _fromBean(object, jsonConfig);
}

JSONObject.fromObject(jsonMap, config)

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

组装完整的JSON对象

  if (!exclusions.contains(key))
{
Object value = entry.getValue();
if ((jsonPropertyFilter == null) || (!jsonPropertyFilter.apply(map, key, value)))
{
if (value != null) {
JsonValueProcessor jsonValueProcessor = jsonConfig.findJsonValueProcessor(value.getClass(), key); if (jsonValueProcessor != null) {
value = jsonValueProcessor.processObjectValue(key, value, jsonConfig);//执行processObjectValue(),得到关联实体需要的属性组成的JSON对象
bypass = true;
if (!JsonVerifier.isValidJsonValue(value)) {
throw new JSONException("Value is not a valid JSON value. " + value);
}
}
setValue(jsonObject, key, value, value.getClass(), jsonConfig, bypass);//组装完整的JSON对象
}

fromDynaBean(DynaBean bean, JsonConfig jsonConfig)

二.其它方法

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

JsonValueProcessor解决的好处呢?

1:过滤屏蔽,如:

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

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

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

 config.setJsonPropertyFilter(new PropertyFilter() {

             @Override
/**
* argo:当前进行操作的实体,如:product
* arg1:实体属性
* arg2:实体属性的类型
*/
public boolean apply(Object arg0, String arg1, Object arg2) {
if(arg1.equals("company")){
return true;//表示过滤掉此属性
}
return false;//表示正常操作
}
});

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

变属性的类型和方法参数类型,所以还是不能避免死循环。

3:使用JsonValueProcessor()

 config.setCycleDetectionStrategy(CycleDetectionStrategy.LENIENT); //首先避免掉死循环
config.setExcludes(new String[]{"handler","hibernateLazyInitializer"}); //设置延迟加载
config.registerJsonValueProcessor(Date.class,new JsonValueProcessor() {
public Object processObjectValue(String arg0, Object arg1, JsonConfig arg2) {
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date d=(Date) arg1;
return sdf.format(d);
}
public Object processArrayValue(Object arg0, JsonConfig arg1) {
return null;
}
});

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

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

三.小结

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

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

迎讨论,轻拍。

我存在,你深深的循环里--从反射看JSON死循环的更多相关文章

  1. handlebars,each循环里面套each循环

    handlebars可以用each自动进行循环,下面介绍一下each循环里面套循环来着. html代码 !DOCTYPE html> <html> <head> < ...

  2. for循环里使用查询如何优化(代码库)

    for循环里的查询,只是为了赋值对象中的一个字段,如果每一个都重新查一下数据库,影响效率 应该先进行查询,然后再循环里组装自己需要的业务数据 如下代码:list1 查询出对象的一部分内容,list2 ...

  3. foreach循环里不能remove/add元素的原理

    foreach循环 ​    foreach循环(Foreach loop)是计算机编程语言中的一种控制流程语句,通常用来循环遍历数组或集合中的元素.Java语言从JDK 1.5.0开始引入forea ...

  4. 不要在 foreach 循环里进行元素的 remove/add 操作。remove 元素请使用 Iterator 方式,如果并发操作,需要对 Iterator 对象加锁

    不要在 foreach 循环里进行元素的 remove/add 操作.remove 元素请使用 Iterator 方式,如果并发操作,需要对 Iterator 对象加锁. 正例: Iterator&l ...

  5. 【vue】vue使用Element组件时v-for循环里的表单项验证方法

    转载至:https://www.jb51.net/article/142750.htm标题描述看起来有些复杂,有vue,Element,又有表单验证,还有v-for循环?是不是有点乱?不过我相信开发中 ...

  6. qt——for循环里创建widget

    在for循环里创建 widget,比如test类 不能使用 test t; 而要使用 test t = new test(): for (i=0;i<=3;i++) { QPushButton* ...

  7. for循环里面的break;和continue;语句

    for循环里面的break;和continue;语句 break语句 哇,我已经找到我要的答案了,我不需要进行更多的循环了! 比如,寻找第一个能被5整除的数: for循环中,如果遇见了break语句, ...

  8. js循环里进行回调,引用循环里的变量,发现只是最后值的问题

    做项目的时候,栽在一个小地方,是这样的 我有很多个坐标点,我想把这些坐标点都绑定一个事件,当点击了这个坐标点之后,发送一个ajax 请求,将坐标点的id 发出去,等待显示返回的数据 但是实际当中,无论 ...

  9. 关于for 循环里 线程执行顺序问题

    最近在做项目时遇到了 这样的需求 要在一个for循环里执行下载的操作, 而且要等 下载完每个 再去接着走循环.上网查了一些 觉得说的不是很明确.现在把我用到的代码 贴上 希望可以帮到有此需求的开发者  ...

随机推荐

  1. final 的用法总结

    1.修饰成员变量 修饰普通变量 表明这个变量是一个常量,不可以修改这个变量的值,一般这样的变量的变量名都要大写 修饰引用变量 表明这个引用不能够指向别的对象了,只能够指向指定的这个对象 2.修饰方法 ...

  2. JavaScript高级 Function类型

    ·    Function类型 (属于引用类型) 1.JS中,有的函数均是对象,这个一个非常有特点的地方.它既然是对象,那么它的构造函数是谁呢?就是Function.(例如:function Pers ...

  3. Dockpanel的控件加载问题

    1. 正确加载模式:panel.ControlContainer.Controls.Add(control); 如果用panel.Controls.Add(control);则可能出现模块发生位移问题 ...

  4. (function ( ){})( ) 与 (function ( ){}( )) 有什么区别?

    js立即执行函数: (function ( ){})( ) 与 (function ( ){}( )) 有什么区别? 转自:http://www.jb51.net/article/75089.htm ...

  5. 合并果子 (codevs 1063) 题解

    [问题描述] 在一个果园里,多多已经将所有的果子打了下来,而且按果子的不同种类分成了不同的堆.多多决定把所有的果子合成一堆. 每一次合并,多多可以把两堆果子合并到一起,消耗的体力等于两堆果子的重量之和 ...

  6. Python 网页投票信息抓取

    最近学习python,为了巩固一下学过的知识,花了半天(主要还是因为自己正则表达式不熟)写了个小脚本来抓取一个网站上的投票信息,排名后进行输出. 抓取的网站网址是http://www.mudidi.n ...

  7. 远程连接数据库(通过pgAdmin)

    1.编辑/var/lib/pgsql/data/pg_hba.conf,增加语句  host all all 192.168.105.225/36 trust 让数据库接受网络 192.168.105 ...

  8. apache 403错

    <Directory />Options FollowSymLinksAllowOverride NoneOrder deny,allowAllow from all</Direct ...

  9. hdu 1305 Immediate Decodability

    原题链接:http://acm.hdu.edu.cn/showproblem.php?pid=1305 字典树裸题,如下: #include<algorithm> #include< ...

  10. JavaScript高级程序设计之表单基础

    A FORM <form id='form' action='http://a-response-url' method="post"> <!--maxlengt ...