2

背景

image.png

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对象的过程。

为什么要实现对象的序列化和反序列化?

  1. 我们创建的Java对象被存储在Java堆中,当程序运行结束后,这些对象会被JVM回收。但在现实的应用中,可能会要求在程序运行结束之后还能读取这些对象,并在以后检索数据,这时就需要用到序列化。
  2. 当Java对象通过网络进行传输的时候。因为数据只能够以二进制的形式在网络中进行传输,因此当把对象通过网络发送出去之前需要先序列化成二进制数据,在接收端读到二进制数据之后反序列化成Java对象。

image.png

spring boot中如何实现的序列化?

答案是:通过Serializable接口

image.png

可以发现,该接口是空的。

实际上,Serializable这个标志接口是给java虚拟机参考的,java虚拟机看到这个接口之后,会为该类自动生成一个序列化版本号。

有人感到疑惑:实际在开发中,有时候我们的类并没有实现Serializable接口。但是为什么仍然能通过网络传输?


答案也许是:类里的属性的基本类型,都基本实现了Serializable接口

可以看到,很多的java类型都实现了Serializable接口。 这时候,就可以很快地将该对象里的属性序列化。
image.png

image.png



同时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

image.png

前端发来请求后,

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...


weiweiyi
1k 声望123 粉丝