Shiro 提供了强大的 Session 管理功能,基于 Shiro 实现 Session 共享很是方便,只须要定制一个咱们本身的SessionDAO,并将它绑定给 SessionManager 便可。在咱们的 SessionDAO 中,一般会将 Session 保存到 Redis,那么 Shiro 对 Session 的增删改查,都会直接操做 Redis。java
可是因为 Shiro 对 Session 的访问很是频繁,用户的一次请求,可能就会触发几十次的 Session 访问操做,在 Session 共享的场景下,若是每次都访问 Redis,势必会影响性能。redis
将 Session 对象缓存于本地内存中,可以有效减小从 Redis 中读取 Session 的次数。缓存
最简单的方案,就是将 Session 对象保存到 request 域中,那么在一次请求内,只须要从 Redis 中获取一次,以后就能够直接从当前 request 域中获取,而且当请求结束后缓存会自动销毁,不用担忧内存泄漏。session
ShiroFilter 对每一个请求都会检查 Session 是否存在,若是存在,则调用 SessionManager 的 touch() 方法,将 Session 的 lastAccessTime 属性值更新为当前时间,并调用 SessionDAO 的 update() 方法保存更新。ide
因而可知,当 Session 被建立出来以后,用户的每一个请求都会使 SessionDAO 的 update() 方法至少被调用一次。性能
那么 Session 的 lastAccessTime 属性是干吗用的呢?有必要每一个请求都去更新一下吗?线程
lastAccessTime 属性记录的是用户的上次访问时间,它主要用于验证 Session 是否超时,当用户访问系统时,若是本次访问的时间距离上次访问时间超过了 timeout 阈值,则断定 Session 超时。若是 lastAccessTime 的值不断更新,那么 Session 就有可能永不超时。所以,更新 lastAccessTime 属性值的操做能够认为是给 Session “续命”。code
既然是“续命”,不必每次都“续”(除非命真的很短)。咱们能够重写 SessionManager 的 touch() 方法,在更新过 lastAccessTime 属性的值后,先不急着保存更新,而是计算一下两次访问的时间间隔,只有当它大于某个阈值时,才去主动调用 SessionDAO 的 update() 方法来保存更新。这样也就大大下降了 Session 更新的频率。对象
@Repository public class ShiroSessionDAO extends AbstractSessionDAO { private static final String SESSION_REDIS_KEY_PREFIX = "session:"; @Autowired private RedisTemplate<String, Object> redisTemplate; @Override protected Serializable doCreate(Session session) { Serializable sessionId = generateSessionId(session); assignSessionId(session, sessionId); redisTemplate.boundValueOps(SESSION_REDIS_KEY_PREFIX + session.getId().toString()).set(session); return sessionId; } @Override public void update(Session session) throws UnknownSessionException { redisTemplate.boundValueOps(SESSION_REDIS_KEY_PREFIX + session.getId().toString()).set(session); } @Override public void delete(Session session) { redisTemplate.delete(SESSION_REDIS_KEY_PREFIX + session.getId().toString()); HttpServletRequest request = getRequest(); if (request != null) { // 必定要进行空值判断,由于SessionValidationScheduler的线程也会调用这个方法,而在那个线程中是不存在Request对象的 request.removeAttribute(session.getId().toString()); } } @Override protected Session doReadSession(Serializable sessionId) { HttpServletRequest request = getRequest(); if (request != null) { Session sessionObj = (Session) request.getAttribute(sessionId.toString()); if (sessionObj != null) { return sessionObj; } } Session session = (Session) redisTemplate.boundValueOps(SESSION_REDIS_KEY_PREFIX + sessionId).get(); if (session != null && request != null) { request.setAttribute(sessionId.toString(), session); } return session; } @Override public Collection<Session> getActiveSessions() { Set<String> keys = redisTemplate.keys(SESSION_REDIS_KEY_PREFIX + "*"); if (keys != null && !keys.isEmpty()) { List<Object> sessions = redisTemplate.opsForValue().multiGet(keys); if (sessions != null) { return sessions.stream().map(o -> (Session) o).collect(Collectors.toList()); } } return Collections.emptySet(); } private HttpServletRequest getRequest() { ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); return requestAttributes != null ? requestAttributes.getRequest() : null; } }
@Configuration public class ShiroConfig { @Bean public SessionManager sessionManager(SessionDAO sessionDAO) { DefaultWebSessionManager sessionManager = new DefaultWebSessionManager() { @Override // 重写touch()方法,下降Session更新的频率 public void touch(SessionKey key) throws InvalidSessionException { Session session = doGetSession(key); if (session != null) { long oldTime = session.getLastAccessTime().getTime(); session.touch(); // 更新访问时间 long newTime = session.getLastAccessTime().getTime(); if (newTime - oldTime > 300000) { // 若是两次访问的时间间隔大于5分钟,主动持久化Session onChange(session); } } } }; sessionManager.setSessionDAO(sessionDAO); // 绑定SessionDAO SimpleCookie sessionIdCookie = new SimpleCookie("sessionId"); sessionIdCookie.setPath("/"); sessionIdCookie.setMaxAge(8 * 60 * 60); // 单位:秒数 sessionManager.setSessionIdCookie(sessionIdCookie); // 绑定Cookie模版 sessionManager.setSessionIdUrlRewritingEnabled(false); sessionManager.setGlobalSessionTimeout(60 * 60 * 1000); sessionManager.setSessionValidationSchedulerEnabled(true); sessionManager.setSessionValidationInterval(2 * 60 * 60 * 1000); sessionManager.setDeleteInvalidSessions(true); return sessionManager; } ... 略 ... }