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. javaSE第二十六天

    第二十六天    414 1:网络编程(理解)    414 (1)网络编程:用Java语言实现计算机间数据的信息传递和资源共享    414 (2)网络编程模型    414 (3)网络编程的三要素 ...

  2. activiti搭建(五)BPMN介绍

    转载请注明源地址:http://www.cnblogs.com/lighten/p/5931207.html 对于BPMN我也不是十分清楚,目前也只是因为对于Modeler中不熟悉的组件查询,来对这部 ...

  3. 【转】src与href属性的区别

    ref:http://www.jb51.net/web/77258.html src和href之间存在区别,能混淆使用.src用于替换当前元素,href用于在当前文档和引用资源之间确立联系. src是 ...

  4. 收缩SQL数据库日志

    各位同学,相信大家在使用SQL数据库时,常常会遇到日志文件比数据库文件还在大的情况.以下有一简单的办法,可以快速的删除日志档.使用其大小变为540K. 供各位参考. DUMP TRANSACTION ...

  5. js中forEach无法跳出循环?

    1. forEach() forEach() 方法从头至尾遍历数组,为每个元素调用指定的函数.如上所述,传递的函数作为forEach()的第一个参数.然后forEach()使用三个参数调用该 函数:数 ...

  6. hbase与Hadoop

    Hbase是一种低延迟的在线系统,Hadoop是优化吞吐量的离线系统.这种互补关系造就了一种强大的.灵活的数据平台,可以用来搭建水平扩展的数据应用.

  7. 单个input框上传多个文件操作

    HTML页面: <div class="form-group thumb"> <label class="control-label col-xs-12 ...

  8. try 返回前执行fianlly

    try catch  finally 语句中 如果try中有返回语句,如果在fianlly代码块中有对这个值修改的话,并不影响其放回值 public class Test { public stati ...

  9. linux内核SPI总线驱动分析(二)(转)

    简而言之,SPI驱动的编写分为: 1.spi_device就构建并注册  在板文件中添加spi_board_info,并在板文件的init函数中调用spi_register_board_info(s3 ...

  10. SynchronizationContext一篇

    SynchronizationContext context; 最近写代码用到了这个,特别记录一下. 作用如下: // 摘要: // 提供在各种同步模型中传播同步上下文的基本功能. public cl ...