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; } ... 略 ... }