Spring会话在pageContext.include之后清除会话中的属性

时间:2015-04-07 13:34:26

标签: spring-session

我想我在spring-session遇到了一个错误,但我想问一下这是不是一个错误。在我忘记之前

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

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

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

谢谢。

1 个答案:

答案 0 :(得分:1)

问题

问题是当使用MapSessionRepository时,SessionRepositoryFilter会自动将HttpSession同步到Spring会话,后者会覆盖API的显式使用。具体而言,发生了以下情况:

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

    解决方案

    我认为有两种方法可以解决这个问题。

    使用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。当值放在地图中时,它是单个放置(我们无法将其分解为多个操作)。