【問題】如題所示,在咱們使用hibernate框架而又須要將對象轉化爲json的時候,若是配置了雙向的關聯關係,就會出現這個死循環問題java
異常信息:apache
【緣由】爲何會這樣呢?緣由在於你要轉化的對象裏配置了對另一個對象的關聯,而那個對象裏又配置了對你這個對象的關聯。好比個人兩個類叫作Shop(商店)和Staff(員工),一個商店能夠有多個員工,因此我給這兩個對象配置了雙向的一對多和多對一的關聯關係。這時候問題就出現了,JSON lib在把shop對象轉化爲json字符串的時候,發現shop裏有個Set<Staff>,它就會去級聯的把Set<Staff>轉化爲json字符串,在它遍歷Set的時候,發現Staff裏又有一個Shop對象,這時候它又會去嘗試把shop轉化爲json字符串,而後就發現shop裏又有Set<Staff>,如此周而復始,就造成了死循環。json
1 Method public java.lang.String org.apache.commons.lang.exception.NestableRuntimeException.getMessage(int) threw an exception when invoked on net.sf.json.JSONException: There is a cycle in the hierarchy! 2 The problematic instruction: 3 ---------- 4 ==> ${msgs[0][0]} [on line 76, column 25 in org/apache/struts2/dispatcher/error.ftl] 5 ---------- 6 7 Java backtrace for programmers: 8 ---------- 9 freemarker.template.TemplateModelException: Method public java.lang.String org.apache.commons.lang.exception.NestableRuntimeException.getMessage(int) threw an exception when invoked on net.sf.json.JSONException: There is a cycle in the hierarchy! 10 at freemarker.ext.beans.SimpleMethodModel.exec(SimpleMethodModel.java:130) 11 at freemarker.ext.beans.SimpleMethodModel.get(SimpleMethodModel.java:138) 12 at freemarker.core.DynamicKeyName.dealWithNumericalKey(DynamicKeyName.java:111) 13 at freemarker.core.DynamicKeyName._getAsTemplateModel(DynamicKeyName.java:90) 14 at freemarker.core.Expression.getAsTemplateModel(Expression.java:89) 15 at freemarker.core.Expression.getStringValue(Expression.java:93) 16 at freemarker.core.DollarVariable.accept(DollarVariable.java:76) 17 at freemarker.core.Environment.visit(Environment.java:209) 18 at freemarker.core.MixedContent.accept(MixedContent.java:92) 19 at freemarker.core.Environment.visit(Environment.java:209) 20 at freemarker.core.IfBlock.accept(IfBlock.java:82) 21 at freemarker.core.Environment.visit(Environment.java:209) 22 at freemarker.core.IfBlock.accept(IfBlock.java:82) 23 at freemarker.core.Environment.visit(Environment.java:209) 24 at freemarker.core.MixedContent.accept(MixedContent.java:92) 25 at freemarker.core.Environment.visit(Environment.java:209) 26 at freemarker.core.Environment.process(Environment.java:189) 27 at freemarker.template.Template.process(Template.java:237) 28 at org.apache.struts2.dispatcher.Dispatcher.sendError(Dispatcher.java:748) 29 at org.apache.struts2.dispatcher.Dispatcher.serviceAction(Dispatcher.java:505) 30 at org.apache.struts2.dispatcher.ng.ExecuteOperations.executeAction(ExecuteOperations.java:77) 31 at org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter.doFilter(StrutsPrepareAndExecuteFilter.java:91) 32 at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235) 33 at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) 34 at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:233) 35 at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191) 36 at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127) 37 at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:103) 38 at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109) 39 at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:293) 40 at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:861) 41 at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:606) 42 at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:489) 43 at java.lang.Thread.run(Thread.java:619) 44 Caused by: java.lang.NullPointerException 45 at freemarker.ext.beans.SimpleMemberModel.unwrapArguments(SimpleMemberModel.java:85) 46 at freemarker.ext.beans.SimpleMethodModel.exec(SimpleMethodModel.java:106) 47 ... 33 more
【解決】問題清楚了,如何解決呢?我百度了一下,也找到了數十條的資料,但大都只是都說明了要用jsonConfig.setJsonPropertyFilter(new PropertyFilter(){}),而對於其中的參數和if語句該如何寫,並無一個說的很明白的。爲此我查看了json-lib的源碼,並進行了嘗試,總結以下:數組
1,思路是在JSONObject把Shop對象轉化爲json字符串的時候,在中間加一道過濾,若是當前要轉化的屬性是Set而且屬性名是staffs,那麼就進行過濾tomcat
2,代碼以下:app
Map<String, Object> map=new HashMap<String, Object>(); map.put("shops", list); map.put("total", total); JsonConfig jsonConfig = new JsonConfig(); jsonConfig.setJsonPropertyFilter(new PropertyFilter() { public boolean apply(Object obj, String name, Object value) { if(value instanceof Set<?>&&name.equals("staffs")){ return true; }else{ return false; } } }); return JSONObject.fromObject(map,jsonConfig);
如上,PropertyFilter是json-lib提供的進行屬性過濾的一個接口,具體的實現是由apply方法作的,那麼咱們就須要重寫apply方法。框架
此方法有三個參數,第一個是Object類型的,是你要轉化的對象的類型(Shop);測試
第二個參數是String類型,是你要過濾的屬性的名稱;spa
第三個參數是Object類型的,是你要過濾的屬性的值(值多是String或其它類型的,因此用Object)。 .net
返回值是boolean類型的,返回true,會進行過濾;返回false,不會進行過濾。
if語句的寫法就要根據實際的須要了,好比說我這裏要解決死循環,就要實現把Shop裏的Set<Staff> staffs屬性過濾掉,那個人if語句就應該如上面那樣寫。總而言之就是JSON-lib在轉化的時候,會對每一個屬性都調用這個apply方法,這樣咱們就要根據實際的業務須要,若是當前屬性符合你的if條件,那你就要返回true,進行過濾。是使用||仍是&&也要根據實際而定。
這樣配置後,再測試,就發現獲取Shop的時候死循環問題已經再也不出現了。
3,同理,Staff端也應該進行相似的配置
Map<String,Object> map=new HashMap<String, Object>(); map.put("staffs", list); map.put("total", total); JsonConfig jsonConfig = new JsonConfig(); jsonConfig.setExcludes(new String[]{"handler","hibernateLazyInitializer"}); jsonConfig.setJsonPropertyFilter(new PropertyFilter() { public boolean apply(Object obj, String name, Object value) { if(name.equals("shop")){ return true; }else{ return false; } } }); return JSONObject.fromObject(map,jsonConfig);
通過測試,也是沒有問題的。
這裏你們也能夠看見jsonConfig.setExcludes(new String[]{"handler","hibernateLazyInitializer"});,這一行是爲了防止hibernate延遲加載形成的異常而設置的。
4,到這裏大功告成了嗎?不,我在測試的時候發現了一個更嚴重的問題,若是按照上面作這樣配置,那我獲取shop的時候,生成的json字符串裏staffs的Set不見了;獲取Staff的時候,它的屬性shop在json字符串裏也不見了!稍加分析就能夠知道這是上面配置形成的。按上面的配置,Shop裏的Set<Staff>被過濾掉了,「過濾掉」的含義不是不級聯的轉化Staff裏的Shop了,而是直接連Set<Staff>都不轉化了。這可壞了,我配置雙向關聯關係就是爲了關聯顯示,你把個人屬性過濾掉了,那我還配置雙向關聯幹嗎?我還這麼大費周章的來解決死循環幹嗎?
那麼這個問題該如何解決呢?其實仔細一想,也不難,你們看我把Shop的配置改爲下面這樣
Map<String, Object> map=new HashMap<String, Object>(); map.put("shops", list); map.put("total", total); JsonConfig jsonConfig = new JsonConfig(); jsonConfig.setJsonPropertyFilter(new PropertyFilter() { public boolean apply(Object obj, String name, Object value) { if(value instanceof Shop&&name.equals("shop")){ return true; }else{ return false; } } }); return JSONObject.fromObject(map,jsonConfig);
這樣就能夠獲取到了,爲何呢?由於這樣配置的話,在將Shop裏的Set<Staff> staffs轉化的時候,咱們不過濾;而在將staffs裏的每一個Staff裏的shop轉化的時候,咱們進行過濾,這樣就既解決了死循環問題,又避免了Shop裏的staffs被過濾掉的問題。
同理,Staff要這樣配置
Map<String,Object> map=new HashMap<String, Object>(); map.put("staffs", list); map.put("total", total); JsonConfig jsonConfig = new JsonConfig(); jsonConfig.setExcludes(new String[]{"handler","hibernateLazyInitializer"}); jsonConfig.setJsonPropertyFilter(new PropertyFilter() { public boolean apply(Object obj, String name, Object value) { if(name.equals("staffs")){ return true; }else{ return false; } } }); return JSONObject.fromObject(map,jsonConfig);
但是我測試的時候卻發現獲得的字符串裏只有total,staffs沒有了?你們可能已經明白了,不只Shop裏的staffs被過濾掉了,map裏的staffs也被過濾掉了。解決也很簡單,把map.put("staffs",list);改爲map.put("list",list);就好了,就是換個名字。
【擴展】到這裏,應該能解決你們的問題了。另外還有一種方法也要提一下,
1 JsonConfig jsonConfig = new JsonConfig(); 2 jsonConfig.setIgnoreDefaultExcludes(false); //設置默認忽略 3 jsonConfig.setCycleDetectionStrategy(CycleDetectionStrategy.LENIENT);//設置循環策略爲忽略 解決json最頭疼的問題 死循環 4 jsonConfig.setExcludes(new String[] {"staffs"});//此處是亮點,只要將所需忽略字段加到數組中便可
這種配置也是比較好理解的,但也要注意屬性過濾問題,Shop過濾shop,Staff過濾staffs。
本文轉載自http://blog.csdn.net/superick/article/details/19421145