2

我想我遇到了一个错误,spring-session但我只想在这里问一下它是否真的是一个错误。在我忘记之前

https://github.com/paranoiabla/spring-session-issue.git

这是一个重现问题的 github 存储库。基本上我有 2 个控制器和 2 个 jsps,所以流程是这样的:

  • 用户打开http://localhost:8080/,流程通过HomepageController,将 1 个属性放入 spring-session 并返回homepage.jsp渲染会话 id 和属性数 (1)
  • homepage.jsp里面有这一行: 它 ${pageContext.include("/include")} 调用IncludeController要调用的。
  • 从会话存储库中IncludeController找到会话并记录属性的数量(现在它们被记录为 0 绝对奇怪)并返回include.jsp呈现会话 ID 和会话属性的数量 (0)。两个 jsps 中的会话 id 是相同的,但是在pageContext.include调用之后属性被重置为一个空映射!!!有人可以确认这是否是一个错误。

谢谢你。

4

1 回答 1

2

问题

问题是,当使用 MapSessionRepository 时,SessionRepositoryFilter 会自动将 HttpSession 同步到 Spring Session,这会覆盖 API 的显式使用。具体情况如下:

  1. SessionRepositoryFilter 正在获取当前的 Spring Session。它将它缓存在 HttpServletRequest 中,以确保每次调用 HttpServletRequest.getSession() 都不会进行数据库调用。这个缓存版本的 Spring Session 没有与之关联的属性。
  2. HomepageController 获取自己的 Spring Session 副本,对其进行修改,然后保存。
  3. JSP 刷新提交 HttpServletResponse 的响应。这意味着我们必须在设置刷新之前写出会话 cookie。我们还需要确保会话在这一点上是持久的,因为之后客户端可能会立即访问会话 ID 并能够发出另一个请求。这意味着 #1 中的 Spring Session 被保存,没有覆盖 #2 中保存的会话的属性。
  4. IncludeController 获取从 #3 保存的 Spring Session(没有属性)

解决方案

我认为有两种选择可以解决这个问题。

使用 HttpSession API

那么我将如何解决这个问题。最简单的方法是直接停止使用 Spring Session API。无论如何,这是首选,因为如果可能的话,我们不想将自己绑定到 Spring Session API。例如,而不是使用以下内容:

@Controller
public class HomepageController {

    @Resource(name = "sessionRepository")
    private SessionRepository<ExpiringSession> sessionRepository;

    @Resource(name = "sessionStrategy")
    private HttpSessionStrategy sessionStrategy;

    @RequestMapping(value = "/", method = RequestMethod.GET)
    public String home(final Model model) {

        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();

        final String sessionIds = sessionStrategy.getRequestedSessionId(request);

        if (sessionIds != null) {
            final ExpiringSession session = sessionRepository.getSession(sessionIds);
            if (session != null) {
                session.setAttribute("attr", "value");
                sessionRepository.save(session);
                model.addAttribute("session", session);
            }
        }

        return "homepage";
    }

}

@Controller
public class IncludeController {

    private final static Logger LOG = LogManager.getLogger(IncludeController.class);

    @Resource(name = "sessionRepository")
    private SessionRepository<ExpiringSession> sessionRepository;

    @Resource(name = "sessionStrategy")
    private HttpSessionStrategy sessionStrategy;

    @RequestMapping(value = "/include", method = RequestMethod.GET)
    public String home(final Model model) {

        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();

        final String sessionIds = sessionStrategy.getRequestedSessionId(request);

        if (sessionIds != null) {
            final ExpiringSession session = sessionRepository.getSession(sessionIds);
            if (session != null) {
                LOG.error(session.getAttributeNames().size());
                model.addAttribute("session", session);
            }
        }

        return "include";
    }
}

您可以使用以下方法简化它:

@Controller
public class HomepageController {

    @RequestMapping(value = "/", method = RequestMethod.GET)
    public String home(HttpServletRequest request, Model model) {

        String sessionIds = request.getRequestedSessionId();

        if (sessionIds != null) {
            final HttpSession session = request.getSession(false);
            if (session != null) {
                session.setAttribute("attr", "value");
                model.addAttribute("session", session);
            }
        }

        return "homepage";
    }

}

@Controller
public class IncludeController {

    @RequestMapping(value = "/include", method = RequestMethod.GET)
    public String home(HttpServletRequest request, final Model model) {

        final String sessionIds = request.getRequestedSessionId();

        if (sessionIds != null) {
            final HttpSession session = request.getSession(false);
            if (session != null) {
                model.addAttribute("session", session);
            }
        }

        return "include";
    }
}

使用 RedisOperationsSessionRepository

当然,如果我们不能直接使用 HttpSession API,这可能会出现问题。要处理这个问题,您需要使用不同的 SessionRepository 实现。例如,另一个修复方法是使用 RedisOperationsSessionRepository。这是有效的,因为它足够聪明,只更新已更改的属性。

这意味着在上面的第 3 步中,Redis 实现将仅更新上次访问时间,因为没有更新其他属性。当 IncludeController 请求 Spring Session 时,它仍然会看到保存在 HomepageController 中的属性。

那么为什么 MapSessionRepository 不这样做呢?因为 MapSessionRepository 是基于一个全有或全无的 Map。当值被放置在 map 中时,它是一个 put(我们不能将它分解为多个操作)。

于 2015-04-08T15:55:44.547 回答