成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

淺析 SpringMVC 中返回對象的循環引用問題

開發 架構
@RestController、@ResponseBody 等注解是我們在寫 Web 應用時打交道最多的注解了,我們經常有這樣的需求:返回一個對象給前端,SpringMVC 幫助我們序列化成 JSON 對象。

[[411400]]

本文轉載自微信公眾號「Kirito的技術分享」,作者kiritomoe 。轉載本文請聯系Kirito的技術分享公眾號。

問題發現

今天這個話題還是比較輕松的,可能很多朋友也都遇到過這個問題。

@RestController、@ResponseBody 等注解是我們在寫 Web 應用時打交道最多的注解了,我們經常有這樣的需求:返回一個對象給前端,SpringMVC 幫助我們序列化成 JSON 對象。而今天我要分享的話題也不是什么高深的內容,那就是返回對象中存在循環引用時問題的探討。

該問題非常簡單容易復現,直接上代碼。

準備兩個存在循環引用的對象:

  1. @Data 
  2. public class Person { 
  3.     private String name
  4.     private IdCard idCard; 
  5.  
  6. @Data 
  7. public class IdCard { 
  8.     private String id; 
  9.     private Person person; 

在 SpringMVC 的 controller 中直接返回存在循環引用的對象:

  1. @RestController 
  2. public class HelloController { 
  3.  
  4.     @RequestMapping("/hello"
  5.     public Person hello() { 
  6.         Person person = new Person(); 
  7.         person.setName("kirito"); 
  8.  
  9.         IdCard idCard = new IdCard(); 
  10.         idCard.setId("xxx19950102xxx"); 
  11.  
  12.         person.setIdCard(idCard); 
  13.         idCard.setPerson(person); 
  14.  
  15.         return person; 
  16.     } 

執行 curl localhost:8080/hello 發現,直接報了一個 StackOverFlowError:

StackOverFlow

問題剖析

不難理解這中間發生了什么,從堆棧和常識中都應當了解到一個事實,SpringMVC 默認使用了 jackson 作為 HttpMessageConverter,這樣當我們返回對象時,會經過 jackson 的 serializer 序列化成 json 串,而另一個事實便是 jackson 是無法解析 java 中的循環引用的,套娃式的解析,最終導致了 StackOverFlowError。

有人會說,為什么你會有循環引用呢?天知道業務場景有多奇葩,既然 Java 沒有限制循環引用的存在,那就肯定會有某一合理的場景存在該可能性,如果你在線上的一個接口一直平穩運行著,知道有一天,碰到了一個包含循環引用的對象,你看著打印出來的 StackOverFlowError 的堆棧,開始懷疑人生,是哪個小(大)可(S)愛(B)干的這種事!

我們先假設循環引用存在的合理性,如何解決該問題呢?最簡單的解法:單向維護關聯,參考 Hibernate 中的 OneToMany 關聯中單向映射的思想,這需要干掉 IdCard 中的 Person 成員變量。或者,借助于 jackson 提供的注解,指定忽略循環引用的字段,例如這樣:

  1. @Data 
  2. public class IdCard { 
  3.     private String id; 
  4.     @JsonIgnore 
  5.     private Person person; 

當然,我也翻閱了一些資料,嘗試尋求 jackson 更優雅的解決方式,例如這兩個注解:

  1. @JsonManagedReference 
  2. @JsonBackReference 

但在我看來,似乎他們并沒有什么大用場。

當然,你如果不嫌棄經常出安全漏洞的 fastjson,也可以選擇使用 FastJsonHttpMessageConverter 替換掉 jackson 的默認實現,像下面這樣:

  1. @Bean 
  2. public HttpMessageConverters fastJsonHttpMessageConverters() { 
  3.     //1、定義一個convert轉換消息的對象 
  4.     FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter(); 
  5.  
  6.     //2、添加fastjson的配置信息 
  7.     FastJsonConfig fastJsonConfig = new FastJsonConfig(); 
  8.  
  9.     SerializerFeature[] serializerFeatures = new SerializerFeature[]{ 
  10.         //    輸出key是包含雙引號 
  11.         //                SerializerFeature.QuoteFieldNames, 
  12.         //    是否輸出為null的字段,若為null 則顯示該字段 
  13.         //                SerializerFeature.WriteMapNullValue, 
  14.         //    數值字段如果為null,則輸出為0 
  15.         SerializerFeature.WriteNullNumberAsZero, 
  16.         //     List字段如果為null,輸出為[],而非null 
  17.         SerializerFeature.WriteNullListAsEmpty, 
  18.         //    字符類型字段如果為null,輸出為"",而非null 
  19.         SerializerFeature.WriteNullStringAsEmpty, 
  20.         //    Boolean字段如果為null,輸出為false,而非null 
  21.         SerializerFeature.WriteNullBooleanAsFalse, 
  22.         //    Date的日期轉換器 
  23.         SerializerFeature.WriteDateUseDateFormat, 
  24.         //    循環引用 
  25.         //SerializerFeature.DisableCircularReferenceDetect, 
  26.     }; 
  27.  
  28.     fastJsonConfig.setSerializerFeatures(serializerFeatures); 
  29.     fastJsonConfig.setCharset(Charset.forName("UTF-8")); 
  30.  
  31.     //3、在convert中添加配置信息 
  32.     fastConverter.setFastJsonConfig(fastJsonConfig); 
  33.  
  34.     //4、將convert添加到converters中 
  35.     HttpMessageConverter<?> converter = fastConverter; 
  36.  
  37.     return new HttpMessageConverters(converter); 

你可以自定義一些 json 轉換時的 feature,當然我今天主要關注 SerializerFeature.DisableCircularReferenceDetect 這一屬性,只要不顯示開啟該特性,fastjson 默認就能處理循環引用的問題。

如上配置后,讓我們看看效果:

  1. {"idCard":{"id":"xxx19950102xxx","person":{"$ref":".."}},"name":"kirito"

已經正常返回了,fastjson 使用了"$ref":".." 這樣的標識,解決了循環引用的問題,如果繼續使用 fastjson 反序列化,依舊可以解析成同一對象,其實我在之前的文章中已經介紹過這一特性了《gson 替換 fastjson 引發的線上問題分析》。

使用 FastJsonHttpMessageConverter 可以徹底規避掉循環引用的問題,這對于返回類型不固定的場景十分有幫助,而 @JsonIgnore 只能作用于那些固定結構的循環引用對象上。

問題思考

值得一提的是,為什么一般標準的 JSON 類庫并沒有如此關注循環引用的問題呢?fastjson 看起來反而是個特例,我覺得主要還是 JSON 這種序列化的格式就是為了通用而存在的,$ref 這樣的契約信息,并沒有被 JSON 的規范去定義,fastjson 可以確保 $ref 在序列化、反序列化時能夠正常解析,但如果是跨框架、跨系統、跨語言等場景,這一切都是個未知數了。說到底,這還是 Java 語言的循環引用和 JSON 通用規范不包含這一概念之間的 gap(可能 JSON 規范描述了這一特性,但我沒有找到,如有問題,煩請指正)。

我到底應該選擇 @JsonIgnore 還是使用 FastJsonHttpMessageConverter 呢?經歷了上面的思考,我覺得各位看官應該能夠根據自己的場景選擇合適的方案了。

 

總結下,如果選擇 FastJsonHttpMessageConverter ,改動較大,如果有較多的存量接口,建議做好回歸,以確認解決循環引用問題的同時,別引入了其他不兼容的改動。并且,需要基于你的使用場景評估方案,如果出現了循環引用,fastjson 會使用 $ref 來記錄引用信息,請確認你的前端或者接口方能夠識別該信息,因為這可能并不是標準的 JSON 規范。你也可以選擇 @JsonIgnore 來實現最小改動,但也同時需要注意,如果根據序列化的結果再次反序列化,引用信息可不會自動恢復。

 

責任編輯:武曉燕 來源: Kirito的技術分享
相關推薦

2010-01-25 14:18:46

C++對象模型

2009-12-29 10:24:51

Linux內核循環鏈表

2009-07-31 18:39:31

C#中foreach引

2009-07-01 09:17:36

對象比較Java

2011-07-04 09:39:31

項目管理

2009-06-04 09:47:48

MySQL隱藏控件TMPDIR

2021-07-27 22:56:00

JavaScript編程開發

2023-12-25 09:30:41

Java垃圾回收

2009-12-23 15:08:38

Fedora gcc編

2009-11-03 09:21:26

Visual Stud

2021-03-06 14:22:39

池化對象類庫

2009-08-06 10:14:15

C#引用類型數組

2021-10-18 15:50:49

Android強引用軟引用

2011-06-20 15:13:08

Qt 對象模型

2010-01-20 18:24:51

C++CLI

2025-01-02 09:48:52

JVMCARD_元素

2023-03-09 12:30:55

2009-07-10 18:02:05

MyEclipseMySQL

2020-12-17 07:39:30

HashMap死循環數據

2010-08-27 09:26:32

DHCP server
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 牛牛热在线视频 | 一区二区三区在线观看免费视频 | 日韩国产精品一区二区三区 | 中文字幕 国产精品 | 中文字幕在线网 | 欧美精品一区久久 | 91精品国产综合久久福利软件 | 日韩精品久久一区 | 天天综合网7799精品 | 日韩成人国产 | 欧美黄视频 | 日韩精品不卡 | 国产夜恋视频在线观看 | 日日操av | 国产午夜精品久久久久免费视高清 | 久久三级影院 | 欧美天堂| 亚洲精品在线观 | 999久久久 | 91婷婷韩国欧美一区二区 | 亚洲精品短视频 | 日本又色又爽又黄的大片 | 国产综合久久 | 欧美αv | 欧区一欧区二欧区三免费 | 日日爱夜夜操 | 欧美一区二区在线观看 | 国产资源在线播放 | 中文字幕欧美一区 | 国产福利视频导航 | 日韩在线观看中文字幕 | 毛片一级黄色 | 久久亚洲欧美日韩精品专区 | 隔壁老王国产在线精品 | 亚洲成人在线免费 | 我想看国产一级毛片 | 91精品久久久 | 日韩字幕一区 | av日韩在线播放 | 91免费高清视频 | 欧美区日韩区 |