我存在,你深深的循环里--从反射看JSON死循环
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死循环的更多相关文章
- handlebars,each循环里面套each循环
handlebars可以用each自动进行循环,下面介绍一下each循环里面套循环来着. html代码 !DOCTYPE html> <html> <head> < ...
- for循环里使用查询如何优化(代码库)
for循环里的查询,只是为了赋值对象中的一个字段,如果每一个都重新查一下数据库,影响效率 应该先进行查询,然后再循环里组装自己需要的业务数据 如下代码:list1 查询出对象的一部分内容,list2 ...
- foreach循环里不能remove/add元素的原理
foreach循环 foreach循环(Foreach loop)是计算机编程语言中的一种控制流程语句,通常用来循环遍历数组或集合中的元素.Java语言从JDK 1.5.0开始引入forea ...
- 不要在 foreach 循环里进行元素的 remove/add 操作。remove 元素请使用 Iterator 方式,如果并发操作,需要对 Iterator 对象加锁
不要在 foreach 循环里进行元素的 remove/add 操作.remove 元素请使用 Iterator 方式,如果并发操作,需要对 Iterator 对象加锁. 正例: Iterator&l ...
- 【vue】vue使用Element组件时v-for循环里的表单项验证方法
转载至:https://www.jb51.net/article/142750.htm标题描述看起来有些复杂,有vue,Element,又有表单验证,还有v-for循环?是不是有点乱?不过我相信开发中 ...
- qt——for循环里创建widget
在for循环里创建 widget,比如test类 不能使用 test t; 而要使用 test t = new test(): for (i=0;i<=3;i++) { QPushButton* ...
- for循环里面的break;和continue;语句
for循环里面的break;和continue;语句 break语句 哇,我已经找到我要的答案了,我不需要进行更多的循环了! 比如,寻找第一个能被5整除的数: for循环中,如果遇见了break语句, ...
- js循环里进行回调,引用循环里的变量,发现只是最后值的问题
做项目的时候,栽在一个小地方,是这样的 我有很多个坐标点,我想把这些坐标点都绑定一个事件,当点击了这个坐标点之后,发送一个ajax 请求,将坐标点的id 发出去,等待显示返回的数据 但是实际当中,无论 ...
- 关于for 循环里 线程执行顺序问题
最近在做项目时遇到了 这样的需求 要在一个for循环里执行下载的操作, 而且要等 下载完每个 再去接着走循环.上网查了一些 觉得说的不是很明确.现在把我用到的代码 贴上 希望可以帮到有此需求的开发者 ...
随机推荐
- final 的用法总结
1.修饰成员变量 修饰普通变量 表明这个变量是一个常量,不可以修改这个变量的值,一般这样的变量的变量名都要大写 修饰引用变量 表明这个引用不能够指向别的对象了,只能够指向指定的这个对象 2.修饰方法 ...
- JavaScript高级 Function类型
· Function类型 (属于引用类型) 1.JS中,有的函数均是对象,这个一个非常有特点的地方.它既然是对象,那么它的构造函数是谁呢?就是Function.(例如:function Pers ...
- Dockpanel的控件加载问题
1. 正确加载模式:panel.ControlContainer.Controls.Add(control); 如果用panel.Controls.Add(control);则可能出现模块发生位移问题 ...
- (function ( ){})( ) 与 (function ( ){}( )) 有什么区别?
js立即执行函数: (function ( ){})( ) 与 (function ( ){}( )) 有什么区别? 转自:http://www.jb51.net/article/75089.htm ...
- 合并果子 (codevs 1063) 题解
[问题描述] 在一个果园里,多多已经将所有的果子打了下来,而且按果子的不同种类分成了不同的堆.多多决定把所有的果子合成一堆. 每一次合并,多多可以把两堆果子合并到一起,消耗的体力等于两堆果子的重量之和 ...
- Python 网页投票信息抓取
最近学习python,为了巩固一下学过的知识,花了半天(主要还是因为自己正则表达式不熟)写了个小脚本来抓取一个网站上的投票信息,排名后进行输出. 抓取的网站网址是http://www.mudidi.n ...
- 远程连接数据库(通过pgAdmin)
1.编辑/var/lib/pgsql/data/pg_hba.conf,增加语句 host all all 192.168.105.225/36 trust 让数据库接受网络 192.168.105 ...
- apache 403错
<Directory />Options FollowSymLinksAllowOverride NoneOrder deny,allowAllow from all</Direct ...
- hdu 1305 Immediate Decodability
原题链接:http://acm.hdu.edu.cn/showproblem.php?pid=1305 字典树裸题,如下: #include<algorithm> #include< ...
- JavaScript高级程序设计之表单基础
A FORM <form id='form' action='http://a-response-url' method="post"> <!--maxlengt ...