了解 SessionManager
类继承图

- 在IDEA中选择某个类,按 Shift+Ctrl+Alt+U生成类继承图;选择某个类,按 F4进入类
- 按Alt+7查看类的结构
- 按Alt+F7查看方法在哪里被调用了
使用上面三个快捷键快捷查看框架源码
!!!记住上面的结构
SessionManager

AbstractSessionManager
在源码注释中已经说明该类过时了,为了全局过期时间这个属性,而单独创建一个类是不必要的。

AbstractNativeSessionManager

NativeSessionManger

AbstractValidatingSessionManager

注意:成员变量
SessionValidationScheduler sessionValidationScheduler会话验证调度器,定时调用validateSessions()验证会话是否过期。
DefaultSessionManager
(省略私有变量的getter和setter)

SessionFactory sessionFactory用来生成 Session对象SessionDAO sessionDAO用来实现session的持久化CacheManager cacheManager缓存管理器
DefaultWebSessionManager
(省略私有变量的getter和setter)

Cookie sessionIdCookie,携带名为JSESSIONID的cookieboolean sessionIdCookieEnable是否使用 sessionIdCookie 的方式boolean sessionIdUrlRewritingEnable是否在URL中添加 JESSIONID 参数
小结
在web应用中,shiro默认使用 DefaultWebSessionManager,它有4个重要对象
- SessionFactory
- SessionDAO
- CacheManager
- SessionValidationScheduler
前三者在 DefaultSessionManager 中定义, SessionValidationScheduler 在 AbstractValidatingSessionManager 中被定义。所以有关 SessionFactory、SessionFactory、CacheManager 的相关操作都会在 DefaultSessionManager 中调用它们的相应方法
SessionFactory 只是简单的 createSession() ,具体详情参考 Shiro源码解析之SessionManager01-Session 中。CacheManager 在多个组件都有用到,会单独解析
理解 SessionManager
SessionManager接口对外只有两个方法 start() 和 getSession()。我们一步步的来寻找它们的最终实现
start(SessionContext): Session
1. AbstractNativeSessionManager | start()
1 | // line 98 |
2 | public Session start(SessionContext context) { |
3 | Session session = createSession(context); |
4 | // 设置session的过期时间 |
5 | applyGlobalSessionTimeout(session); |
6 | onStart(session, context); |
7 | notifyStart(session); |
8 | //Don't expose the EIS-tier Session object to the client-tier: |
9 | return createExposedSession(session, context); |
10 | } |
11 | ...... |
12 | // line 161 |
13 | protected Session createExposedSession(Session session, SessionContext context) { |
14 | return new DelegatingSession(this, new DefaultSessionKey(session.getId())); |
15 | } |
16 | |
17 | protected Session createExposedSession(Session session, SessionKey key) { |
18 | return new DelegatingSession(this, new DefaultSessionKey(session.getId())); |
19 | } |
20 | ...... |
21 | // line 184 |
22 | /** |
23 | * Notifies any interested {@link SessionListener}s that a Session has started. This method is invoked |
24 | * <em>after</em> the {@link #onStart onStart} method is called. |
25 | * |
26 | * @param session the session that has just started that will be delivered to any |
27 | * {@link #setSessionListeners(java.util.Collection) registered} session listeners. |
28 | * @see SessionListener#onStart(org.apache.shiro.session.Session) |
29 | */ |
30 | protected void notifyStart(Session session) { |
31 | for (SessionListener listener : this.listeners) { |
32 | listener.onStart(session); |
33 | } |
34 | } |
2. AbstractValidatingSessionManager | createSession()
1 | // line 82 |
2 | private void enableSessionValidationIfNecessary() { |
3 | SessionValidationScheduler scheduler = getSessionValidationScheduler(); |
4 | if (isSessionValidationSchedulerEnabled() && (scheduler == null || !scheduler.isEnabled())) { |
5 | enableSessionValidation(); |
6 | } |
7 | } |
8 | // line 133 |
9 | protected Session createSession(SessionContext context) throws AuthorizationException { |
10 | enableSessionValidationIfNecessary(); |
11 | return doCreateSession(context); |
12 | } |
13 | |
14 | protected abstract Session doCreateSession(SessionContext initData) throws AuthorizationException; |
3. DefaultSessionManager | doCreateSession()
1 | // line 153 |
2 | protected Session doCreateSession(SessionContext context) { |
3 | // 调用 sessionFactory 创建 session 对象 |
4 | Session s = newSessionInstance(context); |
5 | if (log.isTraceEnabled()) { |
6 | log.trace("Creating session for host {}", s.getHost()); |
7 | } |
8 | // 调用 sessionDAO 存储 session 对象,并生成 sessionId |
9 | create(s); |
10 | return s; |
11 | } |
12 | |
13 | protected Session newSessionInstance(SessionContext context) { |
14 | return getSessionFactory().createSession(context); |
15 | } |
16 | |
17 | /** |
18 | * Persists the given session instance to an underlying EIS (Enterprise Information System). This implementation |
19 | * delegates and calls |
20 | * <code>this.{@link SessionDAO sessionDAO}.{@link SessionDAO#create(org.apache.shiro.session.Session) create}(session);<code> |
21 | * |
22 | * @param session the Session instance to persist to the underlying EIS. |
23 | */ |
24 | protected void create(Session session) { |
25 | if (log.isDebugEnabled()) { |
26 | log.debug("Creating new EIS record for new session instance [" + session + "]"); |
27 | } |
28 | sessionDAO.create(session); |
29 | } |
4. DefaultWebSessionManager | onStart()
1 | // line 91 |
2 | private void storeSessionId(Serializable currentId, HttpServletRequest request, HttpServletResponse response) { |
3 | if (currentId == null) { |
4 | String msg = "sessionId cannot be null when persisting for subsequent requests."; |
5 | throw new IllegalArgumentException(msg); |
6 | } |
7 | Cookie template = getSessionIdCookie(); |
8 | Cookie cookie = new SimpleCookie(template); |
9 | String idString = currentId.toString(); |
10 | cookie.setValue(idString); |
11 | cookie.saveTo(request, response); |
12 | log.trace("Set session ID cookie for session with id {}", idString); |
13 | } |
14 | ...... |
15 | // line 238 |
16 | /** |
17 | * Stores the Session's ID, usually as a Cookie, to associate with future requests. |
18 | * |
19 | * @param session the session that was just {@link #createSession created}. |
20 | */ |
21 | |
22 | protected void onStart(Session session, SessionContext context) { |
23 | // 父类的 onStart 方法什么也没做 |
24 | super.onStart(session, context); |
25 | |
26 | if (!WebUtils.isHttp(context)) { |
27 | log.debug("SessionContext argument is not HTTP compatible or does not have an HTTP request/response " + |
28 | "pair. No session ID cookie will be set."); |
29 | return; |
30 | |
31 | } |
32 | HttpServletRequest request = WebUtils.getHttpRequest(context); |
33 | HttpServletResponse response = WebUtils.getHttpResponse(context); |
34 | |
35 | if (isSessionIdCookieEnabled()) { |
36 | Serializable sessionId = session.getId(); |
37 | // 将sessionId设置到cookie中 |
38 | storeSessionId(sessionId, request, response); |
39 | } else { |
40 | log.debug("Session ID cookie is disabled. No cookie has been set for new session with id {}", session.getId()); |
41 | } |
42 | |
43 | request.removeAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE); |
44 | request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_IS_NEW, Boolean.TRUE); |
45 | } |
5. createExposedSession(Session, SessionKey): Session
看这个方法的名字:创建暴露的session
1 | protected Session createExposedSession(Session session, SessionKey key) { |
2 | return new DelegatingSession(this, new DefaultSessionKey(session.getId())); |
3 | } |
这个返回的 DelegatingSession 对象只保存 sessionId。它的 getAttribute() 方法如下
1 | // line 137 |
2 | /** |
3 | * @see org.apache.shiro.session.Session#getAttribute(Object key) |
4 | */ |
5 | public Object getAttribute(Object attributeKey) throws InvalidSessionException { |
6 | return sessionManager.getAttribute(this.key, attributeKey); |
7 | } |
小结
- 如果开启了定时检查session是否有效,则开启定时任务(AbstractValidatingSessionManager)
- 使用 sessionFactory 创建 session(DefaultSessionManager)
- 使用 sessionDAO 存储 session,并生成sessionId(DefaultSessionManager)
- 设置session的全局过期时间(AbstractNativeSessionManager)
- 调用 onStart,web应用将 sessionId 存储到 cookie中(DefaultWebSessionManager)
- 调用 notifyStart()(AbstractNativeSessionManager)
- 创建一个向外暴露的 session —— DegegatingSession(AbstractNativeSessionManager)
getSession(SessionKey): Session
1. AbstractNativeSessionManager | getSession()
1 | ...... |
2 | // line 139 |
3 | public Session getSession(SessionKey key) throws SessionException { |
4 | Session session = lookupSession(key); |
5 | return session != null ? createExposedSession(session, key) : null; |
6 | } |
7 | |
8 | private Session lookupSession(SessionKey key) throws SessionException { |
9 | if (key == null) { |
10 | throw new NullPointerException("SessionKey argument cannot be null."); |
11 | } |
12 | return doGetSession(key); |
13 | } |
14 | ...... |
15 | // line 160 |
16 | protected abstract Session doGetSession(SessionKey key) throws InvalidSessionException; |
先不理第5行中的
createExposedSession(session, key),我们先就着doGetSession这条线向下追。
2. AbstractValidatingSessionManager | doGetSession()
1 | ...... |
2 | // line 113 |
3 | |
4 | protected final Session doGetSession(final SessionKey key) throws InvalidSessionException { |
5 | enableSessionValidationIfNecessary(); |
6 | |
7 | log.trace("Attempting to retrieve session with key {}", key); |
8 | |
9 | Session s = retrieveSession(key); |
10 | if (s != null) { |
11 | validate(s, key); |
12 | } |
13 | return s; |
14 | } |
15 | |
16 | /** |
17 | * Looks up a session from the underlying data store based on the specified session key. |
18 | * |
19 | * @param key the session key to use to look up the target session. |
20 | * @return the session identified by {@code sessionId}. |
21 | * @throws UnknownSessionException if there is no session identified by {@code sessionId}. |
22 | */ |
23 | protected abstract Session retrieveSession(SessionKey key) throws UnknownSessionException; |
doGetSession() 又调用了 retrieveSession() 方法,而这个方法在 AbstractValidatingSessionManager 中是抽象的。先不急着去找谁实现了这个方法,看看它头上的注释。大意:通过指定的session key 在潜在的数据存储中查询session
之所以在AbstractValidatingSessionManager实现
doGetSession(),就是为了调用一下enableSessionValidationIfNecessary(),这个方法用来开启验证session的定时任务
3. DefaultSessionManager | retrieveSession()
1 | ...... |
2 | // line 215 |
3 | protected Session retrieveSession(SessionKey sessionKey) throws UnknownSessionException { |
4 | Serializable sessionId = getSessionId(sessionKey); |
5 | if (sessionId == null) { |
6 | log.debug("Unable to resolve session ID from SessionKey [{}]. Returning null to indicate a " + |
7 | "session could not be found.", sessionKey); |
8 | return null; |
9 | } |
10 | Session s = retrieveSessionFromDataSource(sessionId); |
11 | if (s == null) { |
12 | //session ID was provided, meaning one is expected to be found, but we couldn't find one: |
13 | String msg = "Could not find session with ID [" + sessionId + "]"; |
14 | throw new UnknownSessionException(msg); |
15 | } |
16 | return s; |
17 | } |
18 | ...... |
19 | // line 235 |
20 | protected Session retrieveSessionFromDataSource(Serializable sessionId) throws UnknownSessionException { |
21 | return sessionDAO.readSession(sessionId); |
22 | } |
看来我们已经发现真相了,getSession(SessionKey) 实际上是调用 sessionDAO.readSession(sessionId) 获取session的,当其不为null时,则AbstractNativeSessionManager 调用 createExposedSession() 。
因为 DefaultSessionManager 中有 SessionDAO 成员变量,所以在它内部获取 sessionDAO存储的session
4. createExposedSession(Session, SessionKey): Session
总结
- 如果开启了定时检查session是否有效,则开启定时任务(AbstractValidatingSessionManager)
- 使用 sessionDAO 根据 sessionId 获取 session (DefaultSessionManager)
- 创建一个向外暴露的 session —— DegegatingSession(AbstractNativeSessionManager)