微服务间调用导致的Could not write content: Infinite recursion (*Error)问题

时间:2022-04-12 05:33:55

最近在开发中遇到了一个奇葩的问题,在本地调用没有任何问题,只要是通过feign调用就出现递归调用,异常信息如下:

2017-11-01 17:23:10.302 ERROR 4172 --- [nio-6006-exec-4] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.http.converter.HttpMessageNotWritableException: Could not write content: Infinite recursion (*Error) (through reference chain: org.hibernate.collection.internal.PersistentSet[0]->com.chhliu.srd.rdcloud.user.entity.SysPermission["roleList"]->org.hibernate.collection.internal.PersistentSet[0]->com.chhliu.srd.rdcloud.user.entity.SysRole["permissionList"]->org.hibernate.collection.internal.PersistentSet[0]->com.chhliu.srd.rdcloud.user.entity.SysPermission["roleList"]->org.hibernate.collection.internal.PersistentSet[0] with root cause

java.lang.*Error: null
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(Unknown Source)
at java.security.SecureClassLoader.defineClass(Unknown Source)
at java.net.URLClassLoader.defineClass(Unknown Source)
at java.net.URLClassLoader.access$100(Unknown Source)
at java.net.URLClassLoader$1.run(Unknown Source)
at java.net.URLClassLoader$1.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:709)
at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:155)
at com.fasterxml.jackson.databind.ser.std.CollectionSerializer.serializeContents(CollectionSerializer.java:149)
at com.fasterxml.jackson.databind.ser.std.CollectionSerializer.serialize(CollectionSerializer.java:112)
at com.fasterxml.jackson.databind.ser.std.CollectionSerializer.serialize(CollectionSerializer.java:25)
at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:704)
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:690)
at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:155)

从上面的异常信息可以看出,权限表和角色表出现了递归调用,并且最终导致了*Error的错误。从异常中可以看出,是由于feign在序列化的时候,使用的是jackson,jackson在序列化的时候,导致了递归调用。

角色表如下:

@Entity
@Table(name = "t_sys_role")
@Setter
@Getter
@NoArgsConstructor
@ToString(exclude = {"permissionList", "userList"})
public class SysRole implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;

/**
* 角色编号
*/
@Id
@GeneratedValue
@Column(name = "role_id")
private Long roleId;

/**
* 角色标识 程序中判断使用,如"admin",这个是唯一的:
*/
@Column(name = "role_name")
private String roleName;

/**
* 角色描述,UI界面显示使用
*/
@Column(name = "description")
private String description;

/**
* 是否可用,如果不可用将不会添加给用户
*/
@Column(name = "available")
private Boolean available = Boolean.FALSE;

/**
* 角色 -权限关系: 多对多关系;
*/
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(name = "t_sys_role_permission", joinColumns = {
@JoinColumn(name = "roleId") }, inverseJoinColumns = {
@JoinColumn(name = "permissionId") })
private Set<SysPermission> permissionList;

/**
* 用户 - 角色关系定义;
*/
@JsonBackReference // 解决循环调用的问题
@ManyToMany
@JoinTable(name = "t_sys_user_role", joinColumns = {
@JoinColumn(name = "roleId")}, inverseJoinColumns = { @JoinColumn(name = "uid") })
private Set<Employee> userList;
}

权限表如下:

@Entity
@Table(name = "t_sys_permission")
@Setter
@Getter
@NoArgsConstructor
@ToString(exclude = "roleList")
public class SysPermission implements Serializable {
private static final long serialVersionUID = 1L;

/**
* 主键
*/
@Id
@GeneratedValue
@Column(name = "permission_id")
private long permissionId;

/**
* 权限编码名称
*/
@Column(name = "permission_code")
private String permissionCode;

/**
* 名称
*/
@Column(name = "permission_name")
private String permissionName;

/**
* 一级菜单
*/
@Column(name = "first_menu")
private String firstMenu;

/**
* 二级菜单
*/
@Column(name = "second_menu")
private String secondMenu;

/**
* 资源路径
*/
@Column(name = "url")
private String url;

/**
* 权限字符串,menu例子:role:*,button例子:role:create,role:update,role:delete,role:view
*/
@Column(name = "permission")
private String permission;

/**
* 是否可用,如果不可用将不会添加给用户
*/
@Column(name = "available")
private Boolean available = Boolean.FALSE;

/**
* 权限- 角色关系定义 一个权限对应多个角色
*/
@JsonBackReference // 解决循环调用的问题
@ManyToMany@JoinTable(name = "t_sys_role_permission", joinColumns = {@JoinColumn(name = "permissionId") }, inverseJoinColumns = { @JoinColumn(name = "roleId") })private Set<SysRole> roleList;}

解决方案如下:
比如角色和权限是ManyToMany的关系,那么就在权限对应的角色的setter上加上@JsonBackReference注解,来解决问题,例如上例中在roleList属性上加了该注解。