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. AnyCAD C++ SDK与OpenCASCADE互操作

    AnyCAD SDK有.Net和C++两个版本,使用C++版本的AnyPlatformOcc模块可以实现与OpenCASCADE互操作. C++版本(VS2010 32bit)下载 在AOBridge ...

  2. C#处理Excel

    C#处理Excel C#处理Excel 前言 OleDb 具体操作 NPOI 具体操作 Excel C# NPOI OleDb 前言 最近需要对Excel进行加密解密操作,本身是一个简单的事情,通过 ...

  3. 大数据实践:ODI 和 Twitter (一)

    本文利用twitter做为数据源,介绍使用Oracle大数据平台及Oralce Data Integrator工具,完成从twitter抽取数据,在hadoop平台上处理数据,并最终加载到oracle ...

  4. jQuery在HTML文档加载完毕后自动执行某个事件;

    原来onchange=“fucntionname(parms)”: <select name="country" id="selCountries_{$sn}&qu ...

  5. Android消息推送完美方案[转]

    转自 Android消息推送完美方案 推送功能在手机应用开发中越来越重要,已经成为手机开发的必须.在Android应用开发中,由于众所周知的原因,Android消息推送我们不得不大费周折.本文就是用来 ...

  6. 索尼MT27i Android2.3.7 线刷Android4.04

    Author:KillerLegend From:http://www.cnblogs.com/killerlegend/p/3733150.html Date:2014.5.16 工具:Window ...

  7. Python学习教程(learning Python)--3.1 Python的if分支语句

    本节研究一下if分支语句. if分支语句是Python下逻辑条件控制语句,用于条件执行某些语句的控制操作,当if后的条件conditon满足时,if其下的语句块被执行,但当if的控制条件condito ...

  8. Hashset,Iterator

    HashSet类主要是设计用来做高性能集运算的,例如对两个集合求交集.并集.差集等.集合中包含一组不重复出现且无特性顺序的元素. (一)HashSet的一些特性如下: 1.HashSet中的值不能重复 ...

  9. 网络开发库从libuv说到epoll

    引言 这篇博文可能有点水,主要将自己libuv的学习过程和理解. 简单谈方法. 有点杂. 那我们开始吧. 首先介绍 githup . 这个工具特别好用. 代码托管. 如果不FQ可能有点卡. 但是应该试 ...

  10. Environment 类

    提供有关当前环境和平台的信息以及操作它们的方法. 此类不能被继承. using System; using System.Collections; using System.Collections.G ...