当前位置: 首页 > 后端技术 > Java

Json序列化与无限递归

时间:2023-04-02 10:09:31 Java

背景JSON:Infiniterecursion(StackOverflowError);nestedexception是com.fasterxml.jackson.databind.JsonMappingException:Infiniterecursion开发的时候报这个错误。从报错可以看出是因为json的相互依赖。比如A中有一个属性是B,B中有一个属性是A,这样在json序列化的时候就会产生相互依赖的异常。而这正是当前代码所做的。@EntitypublicclassDivisionalWorksType{@ApiModelProperty("ScoringItemTemplate")@OneToMany(mappedBy="divisionalWorksType")@JsonView(ScoringItemTemplateJsonView.class)ListscoringItemTemplates=newArrayList<>();}@EntitypublicclassScoringItemTemplate{ManyToOne}@ApiModelProperty("所属分发项目模板类型")@JsonView(DivisionalWorksTypeJsonView.class)privateDivisionalWorksTypedivisionalWorksType;}序列化可能有些人不太了解序列化。什么是序列化和反序列化?Java序列化是指将Java对象转换为字节序列的过程,而Java反序列化是指将字节序列还原为Java对象的过程。为什么要实现对象的序列化和反序列化?我们创建的Java对象存放在Java堆中,当程序运行结束后,这些对象会被JVM回收。但是在实际应用中,可能需要在程序运行结束后读取这些对象,稍后再取回数据,这时就需要序列化。当Java对象通过网络传输时。因为数据只能以二进制形式在网络上传输,所以在通过网络发送之前需要将对象序列化为二进制数据,在接收端读取二进制数据后反序列化为Java对象。如何在springboot中实现序列化?答案是:通过Serializable接口可以发现接口是空的。其实Serializable标志接口是给java虚拟机的一个参考。java虚拟机看到这个界面后,会自动为这个类生成一个序列化的版本号。有人不解:在实际开发中,有时我们的类并没有实现Serializable接口。但是为什么它仍然可以通过网络传输呢?答案可能是:类中属性的基本类型基本上都实现了Serializable接口。可以看出很多java类型都实现了Serializable接口。这时候可以快速序列化对象中的属性。同时,Spring框架帮我们做了一些其他的事情:Spring并不是直接通过网络传输Object,而是先将Object通过序列化的方式转换成json格式的字符串,然后再进行传输。这里的序列化也可以表述为:序列化就是将一个对象转化为Json格式的字符串,反序列化则是相反的过程,将一个Json字符串转化为一个对象。Spring框架是如何将数据转成Json的?通常我们在使用的时候,只需要在Controller类中定义如下:@RestController@RequestMapping("User"){returnthis.userService.save(user);}}在Controller中使用@ResponseBody注解返回Json格式的数据。@ResponseBody的作用是将Controller方法返回的对象转换成指定的格式写入response对象的body区,通常用于返回json或xml数据,@RestController注解包含@ResponseBody注解,所以默认情况下,@RestController可以将返回的数据结构转换成Json格式。所以,我们可以使用@RestController注解。这些注解之所以能够在Json类和Java类之间进行转换,是因为HttpMessageConverter起到了作用。HttpMessageConverter前端发送请求后,1.调用HttpInputMessage从输入流中获取Json字符串。2.在HttpMessageConverter中将Json转换成接口需要的形参类型。3.在HttpMessageConverter中将Json转为Java实体类至于HttpMessageConverter如何处理,这里就不多说了,解决问题,回到后台问题。去google了一下,有两种比较方便的方法:1、使用@JsonManagedReference和@JsonBackReference注解。为父模型添加@JsonManagedReference注解,即父类的getter方法@JsonManagedReferencepublicListgetDivisionalWorksTypes(){returndivisionalWorksTypes;}为子模型添加@JsonBackReference@JsonBackReferencepublicDivisionalWorksTemplategetDivisionalWorksdivisionTemplate(){return}2.使用@JsonIgnore注解@JsonIgnorepublicListgetDivisionalWorksTypes(){returndivisionalWorksTypes;}原理:@JsonManagedReference:管理引用的一方可以理解为拥有引用的一方。通过该注解的属性序列化时,@会正常获取JsonBackReference:backreference,可以理解为该属性为反向引用,序列化该注解的属性时,一个用于父角色,一个用于父角色对于child角色:使用这两个注解后,效果就是:一侧没有序列化看看@JsonIgnore@JsonIgnore并不是为了解决无限递归问题而设计的,它只是忽略注解属性没有序列化或者反序列化。但是,如果字段之间存在双向链接,则可以避免无限递归,因为@JsonIgnore会忽略带注释的属性。当时使用了@JsonManagedReference和@JsonBackReference注解后,就没有再报错了。但是后面删除这两个注解后,就不会报错了。可能是后面加了@JsonView注解,控制输入输出后的json。entity:@ApiModelProperty("分布式工程类型")@OneToMany(mappedBy="divisionalWorksTemplate")@JsonView(DivisionalWorksTypesJsonView.class)ListdivisionalWorksTypes=newArrayList<>();controller:@GetMapping("page")@JsonView(PageJsonView.class)publicPagepage(@RequestParam(required=false)字符串名称,@SortDefault.SortDefaults(@SortDefault(sort="id",direction=Sort.Direction.DESC))Pageablepageable){returnthis.divisionalWorksTemplateService.page(name,pageable);}privateclassPageJsonViewimplementsDivisionalWorksTemplate.DivisionalWorksTypesJsonView,DivisionalWorksType.ScoringItemTemplateJsonView{}在配置@JsonView控制json格式输出的时候,如果在实体中,添加@JsonView注解一个太麻烦了;我们可以配置@JsonView@ConfigurationpublicclassWebConfigimplementsWebMvcConfigurer{/***配置JsonView*/@OverridepublicvoidconfigureMessageConverters(finalList>converters){finalObjectMappermapper=Jackson2ObjectMapperBuilder.json().defaultViewInclusion(true).build();}converters.add(新的MappingJackson2HttpMessageConverter(映射器));这里设置了defaultViewInclusion(true),看javadoc这是一个双向开关,打开会输出不带JsonView注解的属性,关闭会输出带JsonView注解的属性,这样我们就可以逆向使用@JsonView注解了。参考文章:https://blog.csdn.net/qq_4261...