Spring ResponseEntity返回含HttpSession的Token时触发StackOverflow错误
我在Spring MVC的Rest控制器方法里,尝试把包含HttpSession对象的自定义Token类通过ResponseEntity返回给客户端。但只要Token类里有HttpSession属性,就会因为Jackson数据绑定的无限递归问题触发StackOverflow错误;如果去掉这个属性就完全正常。
我的Token类代码:
public class Token implements Serializable { private static final long serialVersionUID = 1L; private HttpSession session; private String value; public Token () {} public Token (HttpSession session, String value) { this.session = session; this.value = value; } public String getValue() { return value; } public void setValue(String value) { this.value = value; } public HttpSession getSession() { return session; } public void setSession(HttpSession session) { this.session = session; } }
控制器方法代码:
@RequestMapping(value = "/getData", method = RequestMethod.GET) public ResponseEntity<Token> getData(HttpServletRequest request) { HttpSession session = request.getSession(); Token t = new Token(session, "value"); return new ResponseEntity<Token>(t, HttpStatus.OK); }
堆栈跟踪的核心信息:
Root cause of ServletException. org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: Infinite recursion (StackOverflowError); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Infinite recursion (StackOverflowError) (through reference chain: weblogic.servlet.internal.WebAppServletContext["securityContext"]->weblogic.servlet.internal.ServletSecurityContextImpl["servletContext"]............... com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:155) at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:727) at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:719) at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:155) at ........-
请问出现该问题的原因是什么?
这个问题的核心在于Jackson在序列化HttpSession对象时触发了无限递归引用,具体可以拆解为这几点:
HttpSession内部的引用闭环
从堆栈里的weblogic.servlet.internal相关类能看出来,你使用的WebLogic容器实现的HttpSession,内部持有对ServletContext的引用,而ServletContext又反过来引用了SecurityContext,SecurityContext再次指向ServletContext——形成了一个无法打断的引用循环。当Jackson尝试递归序列化这些对象的所有属性时,会一直在这个闭环里循环,直到栈内存耗尽,抛出StackOverflowError。Jackson默认序列化规则的局限
Jackson默认会通过对象的getter方法序列化所有可访问属性,Token类里的getSession()方法会让Jackson尝试完整序列化HttpSession实例。但HttpSession是Web容器管理的核心服务器端对象,它本身就不是为序列化成JSON设计的,内部包含大量容器上下文的关联对象,引用关系极其复杂,很容易出现循环引用。不合理的序列化对象选择
另外,虽然你的Token类实现了Serializable,但HttpSession本身就是不能被安全序列化到客户端的对象——它是服务器端维护的会话标识,包含服务器上下文、会话状态等敏感且无法在客户端复用的内容,强行把它返回给客户端本身就是不符合设计逻辑的。
简单来说,你试图把一个服务器端的核心会话对象直接返回给客户端,Jackson在序列化它的时候陷入了无限循环,最终导致栈溢出。
内容的提问来源于stack exchange,提问作者Kingamere

