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第二十三天

    第二十三天    338 1.进程和线程的概述    338 2.多线程(理解)    339 (1)多线程:一个应用程序有多条执行路径    339 (2)Java程序的运行原理及JVM的启动是多线 ...

  2. 使用select io复用实现超时设置

    在linux的socket编程中,经常会遇到超时设置的问题,例如请求方如果在Ks内不发送数据则服务器要断开连接停止服务.这里我使用select的io复用实现超时5s设置,具体代码片段如下: fd_se ...

  3. DevExpress 使用 XtraTabbedMdiManager 控件以 Tab样式加载 Mdi窗体并合并 RibbonControl 解决方案

    最近刚接触到 DevExpress 13.1 这个皮肤组件, 觉得相当好用 于是开始准备搭建 个小应用的主体框架. 找了好久的就是没找到对应的文章来讲解这一块.. 翻了他们主网站上人家问的,以及API ...

  4. Android EditText 不弹出输入法

    当第一次进入一个activity的时候  一般是第一个edittext是默认选中的,但是该死的软键盘也一起弹出来了 那是相当的不美观哈!(#‵′)凸.为此, 本大人就去寻找在刚进入这个activity ...

  5. hadoop2—namenode—HA原理详解

    在hadoop1中NameNode存在一个单点故障问题,也就是说如果NameNode所在的机器发生故障,那么整个集群就将不可用(hadoop1中有个SecorndaryNameNode,但是它并不是N ...

  6. 10.python中的序列

    本来说完字符串.数字.布尔值之后,应该要继续讲元祖.列表之类的.但是元祖和列表都属于序列,所以有必要先讲讲python的序列是什么. 首先,序列是是Python中最基本的数据结构.序列中的每个元素都分 ...

  7. Android-简单的sdcard文件浏览

    功能:能够浏览手机里面的文件夹和文件,代码灰常简单 先看布局 <LinearLayout xmlns:android="http://schemas.android.com/apk/r ...

  8. hdu 1053 Entropy

    题目连接 http://acm.hdu.edu.cn/showproblem.php?pid=1053 Entropy Description An entropy encoder is a data ...

  9. Object C学习初步

    最近乘着项目不太紧张的时候,赶紧给自己冲了一下电.其实我自己最熟悉的平台应该是.net,所以当初上手windows phone的话是很快,我记得当初是一边跟着项目进展,一边自己开始学习前台的XAML语 ...

  10. iOS中MVC设计模式

    在组织大型项目的代码文件时,我们常用MVC的思想.MVC的概念讲起来非常简单,就和对象(object)一样.但是理解和应用起来却非常困难.今天我们就简单总结一下MVC设计理念. MVC(Model V ...