问题
问题是,当使用 MapSessionRepository 时,SessionRepositoryFilter 会自动将 HttpSession 同步到 Spring Session,这会覆盖 API 的显式使用。具体情况如下:
- SessionRepositoryFilter 正在获取当前的 Spring Session。它将它缓存在 HttpServletRequest 中,以确保每次调用 HttpServletRequest.getSession() 都不会进行数据库调用。这个缓存版本的 Spring Session 没有与之关联的属性。
- HomepageController 获取自己的 Spring Session 副本,对其进行修改,然后保存。
- JSP 刷新提交 HttpServletResponse 的响应。这意味着我们必须在设置刷新之前写出会话 cookie。我们还需要确保会话在这一点上是持久的,因为之后客户端可能会立即访问会话 ID 并能够发出另一个请求。这意味着 #1 中的 Spring Session 被保存,没有覆盖 #2 中保存的会话的属性。
- 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(我们不能将它分解为多个操作)。