背景
JSON: Infinite recursion (StackOverflowError); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Infinite recursion
过后开发的时候报了这个错。
看报错就晓得是因为,json 的相互依赖。
比方 A 中有一个属性是 B , 而 B 中也有一个属性是 A。这将造成了 json 序列化的时候,报相互依赖的异样。
而正好,以后的代码就是这么干的。
@Entity
public class DivisionalWorksType {@ApiModelProperty("评分我的项目模版")
@OneToMany(mappedBy = "divisionalWorksType")
@JsonView(ScoringItemTemplateJsonView.class)
List<ScoringItemTemplate> scoringItemTemplates = new ArrayList<>();}
@Entity
public class ScoringItemTemplate {
@ManyToOne
@ApiModelProperty("所属散布工程模版类型")
@JsonView(DivisionalWorksTypeJsonView.class)
private DivisionalWorksType divisionalWorksType;
}
序列化
可能还有的人不太分明序列化。
什么是序列化与反序列化?
Java 序列化 是指把 Java 对象转换为字节序列的过程,而 Java 反序列化是指把字节序列复原为 Java 对象的过程。
为什么要实现对象的序列化和反序列化?
- 咱们创立的 Java 对象被存储在 Java 堆中,当程序运行完结后,这些对象会被 JVM 回收。但在事实的利用中,可能会要求在程序运行完结之后还能读取这些对象,并在当前检索数据,这时就须要用到序列化。
- 当 Java 对象通过网络进行传输的时候。因为数据只可能以二进制的模式在网络中进行传输,因而当把对象通过网络发送进来之前须要先序列化成二进制数据,在接收端读到二进制数据之后反序列化成 Java 对象。
spring boot 中如何实现的序列化?
答案是:通过 Serializable
接口
能够发现,该接口是空的。
实际上,Serializable
这个标记接口是给 java 虚拟机参考的,java 虚拟机看到这个接口之后,会为该类主动生成一个序列化版本号。
有人感到纳闷:理论在开发中,有时候咱们的类并没有实现 Serializable 接口。然而为什么依然能通过网络传输?
答案兴许是:类里的属性的根本类型,都根本实现了 Serializable 接口
能够看到,很多的 java 类型都实现了 Serializable 接口。这时候,就能够很快地将该对象里的属性序列化。
同时 Spring 框架帮咱们做了另一些一些事件:Spring 并不是间接把 Object 进行网络传输,而是先把 Object通过序列化 转换成 json 格局的字符串,而后再进行传输的。
这里的序列化也能够示意为:序列化就是将对象转换成 Json 格局的字符串,反序列化就是逆过程,将 Json 串转换成对象。
Spring 框架如何将数据转换成 Json?
通常,咱们应用的时候,只须要在 Controller 类中做如下定义:
@RestController
@RequestMapping("User")
public class UserController {
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public User save(@RequestBody User user) {return this.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 怎么解决,这里就不开展阐明了
解决问题
回到背景问题。
过后去谷歌之后有两个比拟不便的办法:
1. 应用 @JsonManagedReference
和@JsonBackReference
注解。
将 @JsonManagedReference
正文增加到父级模型,即父级的 getter 办法
@JsonManagedReference
public List<DivisionalWorksType> getDivisionalWorksTypes() {return divisionalWorksTypes;}
再将@JsonBackReference
增加到子级模型
@JsonBackReference
public DivisionalWorksTemplate getDivisionalWorksTemplate() {return divisionalWorksTemplate;}
2. 应用 @JsonIgnore
注解
@JsonIgnore
public List<DivisionalWorksType> getDivisionalWorksTypes() {return divisionalWorksTypes;}
原理:
@JsonManagedReference
:治理援用的一方,能够了解为具备援用的一方,被这个注解的属性序列化时会失常获取@JsonBackReference
:反向援用,能够了解为此属性为反向援用,被这个注解的属性序列化时会疏忽
一个用于父级角色,另一个用于子级角色:
用了这两个注解后,作用即:一方不被序列化
再来看 @JsonIgnore
@JsonIgnore 并不是为解决有限递归问题而设计的,它只是疏忽了正文属性不被序列化或反序列化。然而,如果字段之间存在双向链接,则因为 @JsonIgnore 会疏忽带正文的属性,因而能够防止有限递归。
后话
过后用了 @JsonManagedReference 和 @JsonBackReference 这对注解后,便没有再报错了。
然而之后删除这两个注解后,也并不会报错。
或者是因为起初加上了 @JsonView 注解,管制输入输出后的 json。
entity:
@ApiModelProperty("散布工程类型")
@OneToMany(mappedBy = "divisionalWorksTemplate")
@JsonView(DivisionalWorksTypesJsonView.class)
List<DivisionalWorksType> divisionalWorksTypes = new ArrayList<>();
controller:
@GetMapping("page")
@JsonView(PageJsonView.class)
public Page<DivisionalWorksTemplate> page(@RequestParam(required = false) String name,
@SortDefault.SortDefaults(@SortDefault(sort = "id", direction = Sort.Direction.DESC)) Pageable pageable
) {return this.divisionalWorksTemplateService.page(name, pageable);
}
private class PageJsonView implements DivisionalWorksTemplate.DivisionalWorksTypesJsonView, DivisionalWorksType.ScoringItemTemplateJsonView {}
配置 @JsonView
想管制 json 格局输入的时候,若在实体中,一个个地加上 @JsonView 注解太麻烦了;
咱们就能够配置 @JsonView
@Configuration
public class WebConfig implements WebMvcConfigurer {
/**
* 配置 JsonView
*/
@Override
public void configureMessageConverters(final List<HttpMessageConverter<?>> converters) {final ObjectMapper mapper = Jackson2ObjectMapperBuilder.json().defaultViewInclusion(true).build();
converters.add(new MappingJackson2HttpMessageConverter(mapper));
}
这里设置了defaultViewInclusion(true)
,看 javadoc 说这是一个双向开关,开启将输入没有 JsonView 注解的属性,false 敞开将输入有 JsonView 注解的属性。
所以咱们就能够反转 @JsonView 注解的应用了。
参考文章: https://blog.csdn.net/qq_4261…