DefaultSavedRequest匿名子类Redis序列化失败排查求助
Redis序列化失败:DefaultSavedRequest匿名子类意外携带request字段问题
问题场景
排查Redis会话序列化问题时遇到以下情况:
- 创建
org.springframework.security.web.savedrequest.DefaultSavedRequest的匿名子类实例后,对象中多出一个类型为org.springframework.security.web.servletapi.HttpServlet3RequestFactory$Servlet3SecurityContextHolderAwareRequestWrapper的request字段(该字段是构造方法传入的第一个请求对象),但DefaultSavedRequest本身并没有定义这个字段。 - 这个隐藏字段导致整个会话对象存储到Redis时序列化失败,抛出报错。
- 环境信息:spring-security-web-5.6.5、OpenJDK 17.0.5
- 匿名子类代码:
final DefaultSavedRequest savedRequest = new DefaultSavedRequest(request, portResolver) { private final String referer = request.getHeader(REFERER); private final String contextPath = request.getContextPath(); @Override public String getRedirectUrl() { // 自定义重定向URL逻辑 } };
原因分析
这是Java匿名内部类的固有特性:匿名内部类会隐式持有外部作用域中被它直接引用的对象。这里匿名类的referer和contextPath字段直接用外部的request对象初始化,导致匿名类内部自动生成了一个隐藏的request引用字段。而Servlet3SecurityContextHolderAwareRequestWrapper包含大量不可序列化的组件(比如线程上下文、容器内部对象),最终导致整个对象无法被Redis序列化。
解决方案
方案1:提前提取字段,避免持有request引用
先从request中提取所需的字段值,再将这些值传入匿名类,避免直接引用request对象:
// 提前提取需要的字段值,脱离request对象 String referer = request.getHeader(REFERER); String contextPath = request.getContextPath(); final DefaultSavedRequest savedRequest = new DefaultSavedRequest(request, portResolver) { private final String referer = referer; private final String contextPath = contextPath; @Override public String getRedirectUrl() { // 自定义重定向URL逻辑 } };
这样匿名类持有的是字符串副本,不会生成隐藏的request字段。
方案2:改用独立子类实现自定义逻辑
创建独立的子类继承DefaultSavedRequest,在构造方法中传入提前提取好的字段,彻底隔离对request的引用:
public class CustomSavedRequest extends DefaultSavedRequest { private final String referer; private final String contextPath; public CustomSavedRequest(HttpServletRequest request, PortResolver portResolver, String referer, String contextPath) { super(request, portResolver); this.referer = referer; this.contextPath = contextPath; } @Override public String getRedirectUrl() { // 自定义重定向URL逻辑 } }
使用示例:
String referer = request.getHeader(REFERER); String contextPath = request.getContextPath(); final DefaultSavedRequest savedRequest = new CustomSavedRequest(request, portResolver, referer, contextPath);
方案3:尝试让request对象可序列化(不推荐)
Servlet3SecurityContextHolderAwareRequestWrapper本身未实现Serializable接口,且内部依赖大量容器级不可序列化对象,强行修改使其序列化可能引发更多兼容性问题,因此不建议采用此方案。
内容的提问来源于stack exchange,提问作者endurance2296
相关产品推荐
相关产品推荐

