You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 13:55:20