使用Jersey将POJO转为JSON时出现栈溢出问题求助
这个问题我之前也碰到过,核心原因就是多对多关系带来的循环引用导致序列化时无限递归,最终触发栈溢出。XML能正常工作是因为JAXB(JAX-RS默认的XML提供者)识别了你在Group类的users字段上加的@XmlTransient注解,序列化时会跳过反向引用;但JSON提供者要么不识别JAXB注解,要么配置没到位,所以没处理好循环引用。
下面给你几个可行的解决方案:
方案1:切换到MOXy作为JSON提供者(推荐,原生支持JAXB注解)
MOXy是EclipseLink提供的JAXB扩展,原生支持JAXB注解,而且能直接处理JSON序列化。GlassFish 5自带MOXy,但默认可能没启用它作为JSON提供者,你需要手动注册:
方式1:通过JAX-RS应用类注册
创建一个继承Application的类,注册MOXyJsonProvider:
import javax.ws.rs.ApplicationPath; import javax.ws.rs.core.Application; import java.util.HashSet; import java.util.Set; import org.eclipse.persistence.jaxrs.MOXyJsonProvider; @ApplicationPath("/api") // 替换成你的API根路径 public class MyJaxrsApplication extends Application { @Override public Set<Class<?>> getClasses() { Set<Class<?>> classes = new HashSet<>(); // 注册MOXy JSON提供者 classes.add(MOXyJsonProvider.class); // 注册你的REST资源类(比如User相关的资源) classes.add(UserResource.class); return classes; } }
方式2:通过web.xml配置
如果你的项目用web.xml管理JAX-RS,可以添加以下配置:
<servlet> <servlet-name>JAX-RS Servlet</servlet-name> <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class> <init-param> <param-name>jersey.config.server.provider.classnames</param-name> <param-value>org.eclipse.persistence.jaxrs.MOXyJsonProvider</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>JAX-RS Servlet</servlet-name> <url-pattern>/api/*</url-pattern> <!-- 替换成你的API路径 --> </servlet-mapping>
注册后,MOXy会识别你已有的@XmlTransient注解,跳过循环引用的反向关联,JSON序列化就不会栈溢出了。
方案2:让默认的Jackson提供者识别JAXB注解
GlassFish 5默认的JSON提供者是Jackson,它本身不识别JAXB注解,需要添加Jackson的JAXB模块来支持:
步骤1:添加依赖
在你的父POM里添加Jackson JAXB模块的依赖:
<dependency> <groupId>com.fasterxml.jackson.module</groupId> <artifactId>jackson-module-jaxb-annotations</artifactId> <version>2.9.8</version> <!-- 版本尽量匹配GlassFish 5自带的Jackson版本 --> <scope>provided</scope> <!-- 如果GlassFish已包含该模块,用provided;否则用compile --> </dependency>
步骤2:注册JAXB模块到Jackson
创建一个ContextResolver来配置Jackson的ObjectMapper,注册JAXB注解模块:
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.module.jaxb.JaxbAnnotationModule; import javax.ws.rs.ext.ContextResolver; import javax.ws.rs.ext.Provider; @Provider public class JacksonObjectMapperProvider implements ContextResolver<ObjectMapper> { private final ObjectMapper objectMapper; public JacksonObjectMapperProvider() { objectMapper = new ObjectMapper(); // 注册JAXB注解模块,让Jackson识别@XmlTransient等注解 objectMapper.registerModule(new JaxbAnnotationModule()); // 可选:关闭循环引用检测失败的报错,避免栈溢出 objectMapper.configure(com.fasterxml.jackson.databind.SerializationFeature.FAIL_ON_CIRCULAR_REFERENCES, false); } @Override public ObjectMapper getContext(Class<?> type) { return objectMapper; } }
最后把这个Provider注册到你的JAX-RS应用类里(或者通过web.xml配置),Jackson就能识别你的@XmlTransient注解了。
方案3:直接使用Jackson的循环引用注解
如果你不想依赖JAXB注解,可以直接用Jackson的专属注解来处理循环引用:
- 在
User类的groups字段上添加@JsonManagedReference(标记正向引用) - 在
Group类的users字段上添加@JsonBackReference(标记反向引用,序列化时会被忽略)
修改后的实体类代码:
// User类 @Entity @XmlRootElement public class User extends Person { @Column(length = 60) private String email; @Column(name = "PSEUDO", length = 50) protected String pseudo; @ManyToMany(fetch = FetchType.LAZY ,targetEntity = Group.class) @OrderBy("group_name ASC") @com.fasterxml.jackson.annotation.JsonManagedReference protected List<ItGroup> groups = new LinkedList<ItGroup>(); // getters/setters } // Group类 @Entity @Table(name = "groups") public class Group implements ItGroup, Serializable { // ...其他字段 @XmlTransient @ManyToMany(fetch = FetchType.LAZY,mappedBy = "groups",targetEntity = User.class) @OrderBy("email ASC") @com.fasterxml.jackson.annotation.JsonBackReference private List<ItUser> users = new LinkedList<ItUser>(); // getters/setters }
这样Jackson序列化时会自动跳过反向引用的users字段,避免无限递归。
额外注意:懒加载问题
你的关联关系用了FetchType.LAZY,如果序列化时Hibernate的Session已经关闭,可能会触发懒加载异常。可以在JSON提供者里配置忽略未加载的懒加载对象:
- 对于MOXy:可以设置
eclipselink.json.include-unmapped为false,或者在实体类上加@XmlTransient到Hibernate的代理字段(比如hibernateLazyInitializer) - 对于Jackson:添加
@JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})到实体类上,或者配置objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false)
内容的提问来源于stack exchange,提问作者kaizokun

