学习 准备 尝试 谨慎小心

0%

Shiro源码解析之SessionManager

了解 SessionManager

类继承图

DefaultWebSessionManager继承图

  • 在IDEA中选择某个类,按 Shift+Ctrl+Alt+U生成类继承图;选择某个类,按 F4进入类
  • Alt+7查看类的结构
  • Alt+F7查看方法在哪里被调用了

使用上面三个快捷键快捷查看框架源码

!!!记住上面的结构

SessionManager

1571280469834

AbstractSessionManager

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

1571280571702

AbstractNativeSessionManager

1571281149103

NativeSessionManger

NativeSessionManager

AbstractValidatingSessionManager

1571281647315

注意:成员变量 SessionValidationScheduler sessionValidationScheduler 会话验证调度器,定时调用 validateSessions() 验证会话是否过期。

理解 SessionValidationScheduler

DefaultSessionManager

(省略私有变量的getter和setter)

1571281832489

  • SessionFactory sessionFactory 用来生成 Session对象
  • SessionDAO sessionDAO 用来实现session的持久化
  • CacheManager cacheManager 缓存管理器

DefaultWebSessionManager

(省略私有变量的getter和setter)

1571282013982

  • Cookie sessionIdCookie ,携带名为JSESSIONID的cookie
  • boolean sessionIdCookieEnable 是否使用 sessionIdCookie 的方式
  • boolean sessionIdUrlRewritingEnable 是否在URL中添加 JESSIONID 参数

小结

在web应用中,shiro默认使用 DefaultWebSessionManager,它有4个重要对象

  • SessionFactory
  • SessionDAO
  • CacheManager
  • SessionValidationScheduler

前三者在 DefaultSessionManager 中定义, SessionValidationSchedulerAbstractValidatingSessionManager 中被定义。所以有关 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
@Override
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
}

小结

  1. 如果开启了定时检查session是否有效,则开启定时任务(AbstractValidatingSessionManager)
  2. 使用 sessionFactory 创建 session(DefaultSessionManager)
  3. 使用 sessionDAO 存储 session,并生成sessionId(DefaultSessionManager)
  4. 设置session的全局过期时间(AbstractNativeSessionManager)
  5. 调用 onStart,web应用将 sessionId 存储到 cookie中(DefaultWebSessionManager)
  6. 调用 notifyStart()(AbstractNativeSessionManager)
  7. 创建一个向外暴露的 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
@Override
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

总结

  1. 如果开启了定时检查session是否有效,则开启定时任务(AbstractValidatingSessionManager)
  2. 使用 sessionDAO 根据 sessionId 获取 session (DefaultSessionManager)
  3. 创建一个向外暴露的 session —— DegegatingSession(AbstractNativeSessionManager)