学习 准备 尝试 谨慎小心

0%

了解 Authenticator

Authenticator 用来进行身份认证,它的继承图以及继承者的方法如下:

ModularRealmAuthenticator

理解 Authenticator

1. AbstractAuthenticator | authencate(token)

1
// line 167
2
/**
3
 * Implementation of the {@link Authenticator} interface that functions in the following manner:
4
 * <ol>
5
 * <li>Calls template {@link #doAuthenticate doAuthenticate} method for subclass execution of the actual
6
 * authentication behavior.</li>
7
 * <li>If an {@code AuthenticationException} is thrown during {@code doAuthenticate},
8
 * {@link #notifyFailure(AuthenticationToken, AuthenticationException) notify} any registered
9
 * {@link AuthenticationListener AuthenticationListener}s of the exception and then propagate the exception
10
 * for the caller to handle.</li>
11
 * <li>If no exception is thrown (indicating a successful login),
12
 * {@link #notifySuccess(AuthenticationToken, AuthenticationInfo) notify} any registered
13
 * {@link AuthenticationListener AuthenticationListener}s of the successful attempt.</li>
14
 * <li>Return the {@code AuthenticationInfo}</li>
15
 * </ol>
16
 *
17
 * @param token the submitted token representing the subject's (user's) login principals and credentials.
18
 * @return the AuthenticationInfo referencing the authenticated user's account data.
19
 * @throws AuthenticationException if there is any problem during the authentication process - see the
20
 *                                 interface's JavaDoc for a more detailed explanation.
21
 */
22
public final AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {
23
24
    if (token == null) {
25
        throw new IllegalArgumentException("Method argument (authentication token) cannot be null.");
26
    }
27
28
    log.trace("Authentication attempt received for token [{}]", token);
29
30
    AuthenticationInfo info;
31
    try {
32
        info = doAuthenticate(token);
33
        if (info == null) {
34
            String msg = "No account information found for authentication token [" + token + "] by this " +
35
                    "Authenticator instance.  Please check that it is configured correctly.";
36
            throw new AuthenticationException(msg);
37
        }
38
    } catch (Throwable t) {
39
        AuthenticationException ae = null;
40
        if (t instanceof AuthenticationException) {
41
            ae = (AuthenticationException) t;
42
        }
43
        if (ae == null) {
44
            //Exception thrown was not an expected AuthenticationException.  Therefore it is probably a little more
45
            //severe or unexpected.  So, wrap in an AuthenticationException, log to warn, and propagate:
46
            String msg = "Authentication failed for token submission [" + token + "].  Possible unexpected " +
47
                    "error? (Typical or expected login exceptions should extend from AuthenticationException).";
48
            ae = new AuthenticationException(msg, t);
49
            if (log.isWarnEnabled())
50
                log.warn(msg, t);
51
        }
52
        try {
53
            notifyFailure(token, ae);
54
        } catch (Throwable t2) {
55
            if (log.isWarnEnabled()) {
56
                String msg = "Unable to send notification for failed authentication attempt - listener error?.  " +
57
                        "Please check your AuthenticationListener implementation(s).  Logging sending exception " +
58
                        "and propagating original AuthenticationException instead...";
59
                log.warn(msg, t2);
60
            }
61
        }
62
63
64
        throw ae;
65
    }
66
67
    log.debug("Authentication successful for token [{}].  Returned account [{}]", token, info);
68
69
    notifySuccess(token, info);
70
71
    return info;
72
}

代码很多,有用的只有 doAuthencate(token) 这句。

2. ModularRealmAuthenticator | doAuthencate(token)

1
// line 164
2
/**
3
 * Performs the authentication attempt by interacting with the single configured realm, which is significantly
4
 * simpler than performing multi-realm logic.
5
 *
6
 * @param realm the realm to consult for AuthenticationInfo.
7
 * @param token the submitted AuthenticationToken representing the subject's (user's) log-in principals and credentials.
8
 * @return the AuthenticationInfo associated with the user account corresponding to the specified {@code token}
9
 */
10
protected AuthenticationInfo doSingleRealmAuthentication(Realm realm, AuthenticationToken token) {
11
    if (!realm.supports(token)) {
12
        String msg = "Realm [" + realm + "] does not support authentication token [" +
13
                token + "].  Please ensure that the appropriate Realm implementation is " +
14
                "configured correctly or that the realm accepts AuthenticationTokens of this type.";
15
        throw new UnsupportedTokenException(msg);
16
    }
17
    // 使用 realm.getAuthenticationInfo(token) 校验并获取 authenticationInfo 
18
    AuthenticationInfo info = realm.getAuthenticationInfo(token);
19
    if (info == null) {
20
        String msg = "Realm [" + realm + "] was unable to find account data for the " +
21
                "submitted AuthenticationToken [" + token + "].";
22
        throw new UnknownAccountException(msg);
23
    }
24
    return info;
25
}
26
27
/**
28
 * Performs the multi-realm authentication attempt by calling back to a {@link AuthenticationStrategy} object
29
 * as each realm is consulted for {@code AuthenticationInfo} for the specified {@code token}.
30
 *
31
 * @param realms the multiple realms configured on this Authenticator instance.
32
 * @param token  the submitted AuthenticationToken representing the subject's (user's) log-in principals and credentials.
33
 * @return an aggregated AuthenticationInfo instance representing account data across all the successfully
34
 *         consulted realms.
35
 */
36
protected AuthenticationInfo doMultiRealmAuthentication(Collection<Realm> realms, AuthenticationToken token) {
37
38
    AuthenticationStrategy strategy = getAuthenticationStrategy();
39
40
    AuthenticationInfo aggregate = strategy.beforeAllAttempts(realms, token);
41
42
    if (log.isTraceEnabled()) {
43
        log.trace("Iterating through {} realms for PAM authentication", realms.size());
44
    }
45
46
    for (Realm realm : realms) {
47
48
        aggregate = strategy.beforeAttempt(realm, token, aggregate);
49
50
        if (realm.supports(token)) {
51
52
            log.trace("Attempting to authenticate token [{}] using realm [{}]", token, realm);
53
54
            AuthenticationInfo info = null;
55
            Throwable t = null;
56
            try {
57
                info = realm.getAuthenticationInfo(token);
58
            } catch (Throwable throwable) {
59
                t = throwable;
60
                if (log.isDebugEnabled()) {
61
                    String msg = "Realm [" + realm + "] threw an exception during a multi-realm authentication attempt:";
62
                    log.debug(msg, t);
63
                }
64
            }
65
66
            aggregate = strategy.afterAttempt(realm, token, info, aggregate, t);
67
68
        } else {
69
            log.debug("Realm [{}] does not support token {}.  Skipping realm.", realm, token);
70
        }
71
    }
72
73
    aggregate = strategy.afterAllAttempts(token, aggregate);
74
75
    return aggregate;
76
}
77
78
79
/**
80
 * Attempts to authenticate the given token by iterating over the internal collection of
81
 * {@link Realm}s.  For each realm, first the {@link Realm#supports(org.apache.shiro.authc.AuthenticationToken)}
82
 * method will be called to determine if the realm supports the {@code authenticationToken} method argument.
83
 * <p/>
84
 * If a realm does support
85
 * the token, its {@link Realm#getAuthenticationInfo(org.apache.shiro.authc.AuthenticationToken)}
86
 * method will be called.  If the realm returns a non-null account, the token will be
87
 * considered authenticated for that realm and the account data recorded.  If the realm returns {@code null},
88
 * the next realm will be consulted.  If no realms support the token or all supporting realms return null,
89
 * an {@link AuthenticationException} will be thrown to indicate that the user could not be authenticated.
90
 * <p/>
91
 * After all realms have been consulted, the information from each realm is aggregated into a single
92
 * {@link AuthenticationInfo} object and returned.
93
 *
94
 * @param authenticationToken the token containing the authentication principal and credentials for the
95
 *                            user being authenticated.
96
 * @return account information attributed to the authenticated user.
97
 * @throws IllegalStateException   if no realms have been configured at the time this method is invoked
98
 * @throws AuthenticationException if the user could not be authenticated or the user is denied authentication
99
 *                                 for the given principal and credentials.
100
 */
101
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
102
    // 判断是否配置了realms,否的话抛出异常
103
    assertRealmsConfigured();
104
    Collection<Realm> realms = getRealms();
105
    // 判断是单realm还是多realm,分别调用不同的方法处理
106
    if (realms.size() == 1) {
107
        return doSingleRealmAuthentication(realms.iterator().next(), authenticationToken);
108
    } else {
109
        return doMultiRealmAuthentication(realms, authenticationToken);
110
    }
111
}

先来看 doSingleRealmAuthentication(realm),它内部直接调用了 realm.getAuthenticationInfo(token)

3. AbstractAuthenticator | notifySuccess()

1
/**
2
     * Notifies any registered {@link AuthenticationListener AuthenticationListener}s that
3
     * authentication was successful for the specified {@code token} which resulted in the specified
4
     * {@code info}.  This implementation merely iterates over the internal {@code listeners} collection and
5
     * calls {@link AuthenticationListener#onSuccess(AuthenticationToken, AuthenticationInfo) onSuccess}
6
     * for each.
7
     *
8
     * @param token the submitted {@code AuthenticationToken} that resulted in a successful authentication.
9
     * @param info  the returned {@code AuthenticationInfo} resulting from the successful authentication.
10
     */
11
    protected void notifySuccess(AuthenticationToken token, AuthenticationInfo info) {
12
        for (AuthenticationListener listener : this.listeners) {
13
            listener.onSuccess(token, info);
14
        }
15
    }

小结

  1. 如果是单 realm ,则直接调用 realm 的 getAuthenticationInfo()
  2. 如果是多 realm ,则使用 AuthenticationStrategy 对象来调用 realm 的 getAuthenticationInfo() 方法

了解 SecurityManger

继承图

SecurityManager接口名称上按 Ctrl+H,显示它的继承层次
1571642320953

DefaultWebSecurityManager类名称上按 Shift+Ctrl+Alt+U,显示它的继承图
1571642933389

SecurityManager

SecurityManger结构

CachingSecurityManager

1571707959571

RealmSecurityManager

1571708033085

AuthenticatingSecurityManager

1571708079963

AuthorizingSecurityManager

1571708160461

SessionSecurityManager

1571708232562

DefaultSecurityManager

1571708376374

方法看上去很多,但我们只需关注其对顶层接口 SecurityManager 方法的实现

DefaultSecurityManager

1571708790577

小结

SecurityManager的继承结构大量使用了 装饰模式

理解 SecurityManager

从顶层接口 SecurityManger 暴露出的三个方法来入手,分别解析它们的逻辑流程

SecurityManger结构

login(Subject, AuthenticationToken): Subject

1. login(subject, token) | DefaultSecurityManager

1
// line 259
2
/**
3
 * First authenticates the {@code AuthenticationToken} argument, and if successful, constructs a
4
 * {@code Subject} instance representing the authenticated account's identity.
5
 * <p/>
6
 * Once constructed, the {@code Subject} instance is then {@link #bind bound} to the application for
7
 * subsequent access before being returned to the caller.
8
 *
9
 * @param token the authenticationToken to process for the login attempt.
10
 * @return a Subject representing the authenticated user.
11
 * @throws AuthenticationException if there is a problem authenticating the specified {@code token}.
12
 */
13
public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException {
14
    AuthenticationInfo info;
15
    try {
16
        info = authenticate(token);
17
    } catch (AuthenticationException ae) {
18
        try {
19
            onFailedLogin(token, ae, subject);
20
        } catch (Exception e) {
21
            if (log.isInfoEnabled()) {
22
                log.info("onFailedLogin method threw an " +
23
                        "exception.  Logging and propagating original AuthenticationException.", e);
24
            }
25
        }
26
        throw ae; //propagate
27
    }
28
29
    Subject loggedIn = createSubject(token, info, subject);
30
31
    onSuccessfulLogin(token, info, loggedIn);
32
33
    return loggedIn;
34
}

2. authencate(token) | AuthenticatingSecurityManager

1
// line 50
2
/**
3
 * Default no-arg constructor that initializes its internal
4
 * <code>authenticator</code> instance to a
5
 * {@link org.apache.shiro.authc.pam.ModularRealmAuthenticator ModularRealmAuthenticator}.
6
 */
7
public AuthenticatingSecurityManager() {
8
    super();
9
    // 默认 Authenticator
10
    this.authenticator = new ModularRealmAuthenticator();
11
}
12
...... 
13
// line 101
14
/**
15
 * Delegates to the wrapped {@link org.apache.shiro.authc.Authenticator Authenticator} for authentication.
16
 */
17
public AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {
18
    return this.authenticator.authenticate(token);
19
}

authenticator.authenticate(token) 的解析请参考 Shiro源码解析之Authenticator

3. createSubject(token, info, subject) | DefaultSecurityManager

1
// line 166
2
protected SubjectContext createSubjectContext() {
3
    return new DefaultSubjectContext();
4
}
5
/**
6
 * Creates a {@code Subject} instance for the user represented by the given method arguments.
7
 *
8
 * @param token    the {@code AuthenticationToken} submitted for the successful authentication.
9
 * @param info     the {@code AuthenticationInfo} of a newly authenticated user.
10
 * @param existing the existing {@code Subject} instance that initiated the authentication attempt
11
 * @return the {@code Subject} instance that represents the context and session data for the newly
12
 *         authenticated subject.
13
 */
14
protected Subject createSubject(AuthenticationToken token, AuthenticationInfo info, Subject existing) {
15
    SubjectContext context = createSubjectContext();
16
    context.setAuthenticated(true);
17
    // 将 token、authenticationInfo、subject 存入 subjectContext 的 backingMap 中
18
    context.setAuthenticationToken(token);
19
    context.setAuthenticationInfo(info);
20
    if (existing != null) {
21
        context.setSubject(existing);
22
    }
23
    return createSubject(context);
24
}

关于createSubject(context) 解析参考 createSubject(subjectContext): Subject: Subject)

createSubject(subjectContext): Subject

1. createSubject(subjectContext) | DefaultSecurityManager

1
/**
2
 * This implementation functions as follows:
3
 * <p/>
4
 * <ol>
5
 * <li>Ensures the {@code SubjectContext} is as populated as it can be, using heuristics to acquire
6
 * data that may not have already been available to it (such as a referenced session or remembered principals).</li>
7
 * <li>Calls {@link #doCreateSubject(org.apache.shiro.subject.SubjectContext)} to actually perform the
8
 * {@code Subject} instance creation.</li>
9
 * <li>calls {@link #save(org.apache.shiro.subject.Subject) save(subject)} to ensure the constructed
10
 * {@code Subject}'s state is accessible for future requests/invocations if necessary.</li>
11
 * <li>returns the constructed {@code Subject} instance.</li>
12
 * </ol>
13
 *
14
 * @param subjectContext any data needed to direct how the Subject should be constructed.
15
 * @return the {@code Subject} instance reflecting the specified contextual data.
16
 * @see #ensureSecurityManager(org.apache.shiro.subject.SubjectContext)
17
 * @see #resolveSession(org.apache.shiro.subject.SubjectContext)
18
 * @see #resolvePrincipals(org.apache.shiro.subject.SubjectContext)
19
 * @see #doCreateSubject(org.apache.shiro.subject.SubjectContext)
20
 * @see #save(org.apache.shiro.subject.Subject)
21
 * @since 1.0
22
 */
23
public Subject createSubject(SubjectContext subjectContext) {
24
    //create a copy so we don't modify the argument's backing map:
25
    SubjectContext context = copy(subjectContext);
26
27
    //ensure that the context has a SecurityManager instance, and if not, add one:
28
    context = ensureSecurityManager(context);
29
30
    //Resolve an associated Session (usually based on a referenced session ID), and place it in the context before
31
    //sending to the SubjectFactory.  The SubjectFactory should not need to know how to acquire sessions as the
32
    //process is often environment specific - better to shield the SF from these details:
33
    context = resolveSession(context);
34
35
    //Similarly, the SubjectFactory should not require any concept of RememberMe - translate that here first
36
    //if possible before handing off to the SubjectFactory:
37
    context = resolvePrincipals(context);
38
39
    Subject subject = doCreateSubject(context);
40
41
    //save this subject for future reference if necessary:
42
    //(this is needed here in case rememberMe principals were resolved and they need to be stored in the
43
    //session, so we don't constantly rehydrate the rememberMe PrincipalCollection on every operation).
44
    //Added in 1.2:
45
    save(subject);
46
47
    return subject;
48
}
49
50
/**
51
 * Actually creates a {@code Subject} instance by delegating to the internal
52
 * {@link #getSubjectFactory() subjectFactory}.  By the time this method is invoked, all possible
53
 * {@code SubjectContext} data (session, principals, et. al.) has been made accessible using all known heuristics
54
 * and will be accessible to the {@code subjectFactory} via the {@code subjectContext.resolve*} methods.
55
 *
56
 * @param context the populated context (data map) to be used by the {@code SubjectFactory} when creating a
57
 *                {@code Subject} instance.
58
 * @return a {@code Subject} instance reflecting the data in the specified {@code SubjectContext} data map.
59
 * @see #getSubjectFactory()
60
 * @see SubjectFactory#createSubject(org.apache.shiro.subject.SubjectContext)
61
 * @since 1.2
62
 */
63
protected Subject doCreateSubject(SubjectContext context) {
64
    return getSubjectFactory().createSubject(context);
65
}
66
67
/**
68
 * Saves the subject's state to a persistent location for future reference if necessary.
69
 * <p/>
70
 * This implementation merely delegates to the internal {@link #setSubjectDAO(SubjectDAO) subjectDAO} and calls
71
 * {@link SubjectDAO#save(org.apache.shiro.subject.Subject) subjectDAO.save(subject)}.
72
 *
73
 * @param subject the subject for which state will potentially be persisted
74
 * @see SubjectDAO#save(org.apache.shiro.subject.Subject)
75
 * @since 1.2
76
 */
77
protected void save(Subject subject) {
78
    this.subjectDAO.save(subject);
79
}

createSubject(subjectContext) 中那几个 resolveXxx 方法的解析,请看 Shiro源码解析之Subject和SubjectContext

2. getSubjectFacotry().createSubject()

这个方法在 DefaultSubjectFactoryDefaultWebSubjectFactory 分别有一个实现,如下:

1
public Subject createSubject(SubjectContext context) {
2
    SecurityManager securityManager = context.resolveSecurityManager();
3
    Session session = context.resolveSession();
4
    boolean sessionCreationEnabled = context.isSessionCreationEnabled();
5
    PrincipalCollection principals = context.resolvePrincipals();
6
    boolean authenticated = context.resolveAuthenticated();
7
    String host = context.resolveHost();
8
9
    return new DelegatingSubject(principals, authenticated, host, session, sessionCreationEnabled, securityManager);
10
}
1
public Subject createSubject(SubjectContext context) {
2
    if (!(context instanceof WebSubjectContext)) {
3
        return super.createSubject(context);
4
    }
5
    WebSubjectContext wsc = (WebSubjectContext) context;
6
    SecurityManager securityManager = wsc.resolveSecurityManager();
7
    Session session = wsc.resolveSession();
8
    boolean sessionEnabled = wsc.isSessionCreationEnabled();
9
    PrincipalCollection principals = wsc.resolvePrincipals();
10
    boolean authenticated = wsc.resolveAuthenticated();
11
    String host = wsc.resolveHost();
12
    // web 应用特有的
13
    ServletRequest request = wsc.resolveServletRequest();
14
    ServletResponse response = wsc.resolveServletResponse();
15
16
    return new WebDelegatingSubject(principals, authenticated, host, session, sessionEnabled,
17
            request, response, securityManager);
18
}

通过 debug 我们来看一下,第一次 getSubject() 得到的 subject 对象里包含哪些属性

image-20191025104928852

顺便看一下 SecurityManager 中的属性方法

image-20191025110723800

3. save(subject): void | DefaultSecurityManager

这个方法调用了 this.subjectDAO.save(subject)

1
// line 133
2
/**
3
 * Saves the subject's state to the subject's {@link org.apache.shiro.subject.Subject#getSession() session} only
4
 * if {@link #isSessionStorageEnabled(Subject) sessionStorageEnabled(subject)}.  If session storage is not enabled
5
 * for the specific {@code Subject}, this method does nothing.
6
 * <p/>
7
 * In either case, the argument {@code Subject} is returned directly (a new Subject instance is not created).
8
 *
9
 * @param subject the Subject instance for which its state will be created or updated.
10
 * @return the same {@code Subject} passed in (a new Subject instance is not created).
11
 */
12
public Subject save(Subject subject) {
13
    if (isSessionStorageEnabled(subject)) {
14
        saveToSession(subject);
15
    } else {
16
        log.trace("Session storage of subject state for Subject [{}] has been disabled: identity and " +
17
                "authentication state are expected to be initialized on every request or invocation.", subject);
18
    }
19
20
    return subject;
21
}
22
23
/**
24
 * Saves the subject's state (it's principals and authentication state) to its
25
 * {@link org.apache.shiro.subject.Subject#getSession() session}.  The session can be retrieved at a later time
26
 * (typically from a {@link org.apache.shiro.session.mgt.SessionManager SessionManager} to be used to recreate
27
 * the {@code Subject} instance.
28
 *
29
 * @param subject the subject for which state will be persisted to its session.
30
 */
31
protected void saveToSession(Subject subject) {
32
    //performs merge logic, only updating the Subject's session if it does not match the current state:
33
    mergePrincipals(subject);
34
    mergeAuthenticationState(subject);
35
}
36
37
// line 172
38
/**
39
 * Merges the Subject's current {@link org.apache.shiro.subject.Subject#getPrincipals()} with whatever may be in
40
 * any available session.  Only updates the Subject's session if the session does not match the current principals
41
 * state.
42
 *
43
 * @param subject the Subject for which principals will potentially be merged into the Subject's session.
44
 */
45
protected void mergePrincipals(Subject subject) {
46
    //merge PrincipalCollection state:
47
48
    PrincipalCollection currentPrincipals = null;
49
50
    //SHIRO-380: added if/else block - need to retain original (source) principals
51
    //This technique (reflection) is only temporary - a proper long term solution needs to be found,
52
    //but this technique allowed an immediate fix that is API point-version forwards and backwards compatible
53
    //
54
    //A more comprehensive review / cleaning of runAs should be performed for Shiro 1.3 / 2.0 +
55
    if (subject.isRunAs() && subject instanceof DelegatingSubject) {
56
        try {
57
            Field field = DelegatingSubject.class.getDeclaredField("principals");
58
            field.setAccessible(true);
59
            currentPrincipals = (PrincipalCollection)field.get(subject);
60
        } catch (Exception e) {
61
            throw new IllegalStateException("Unable to access DelegatingSubject principals property.", e);
62
        }
63
    }
64
    if (currentPrincipals == null || currentPrincipals.isEmpty()) {
65
        currentPrincipals = subject.getPrincipals();
66
    }
67
68
    Session session = subject.getSession(false);
69
70
    if (session == null) {
71
        if (!isEmpty(currentPrincipals)) {
72
            session = subject.getSession();
73
            session.setAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY, currentPrincipals);
74
        }
75
        // otherwise no session and no principals - nothing to save
76
    } else {
77
        PrincipalCollection existingPrincipals =
78
                (PrincipalCollection) session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
79
80
        if (isEmpty(currentPrincipals)) {
81
            if (!isEmpty(existingPrincipals)) {
82
                session.removeAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
83
            }
84
            // otherwise both are null or empty - no need to update the session
85
        } else {
86
            if (!currentPrincipals.equals(existingPrincipals)) {
87
                session.setAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY, currentPrincipals);
88
            }
89
            // otherwise they're the same - no need to update the session
90
        }
91
    }
92
}
93
94
/**
95
 * Merges the Subject's current authentication state with whatever may be in
96
 * any available session.  Only updates the Subject's session if the session does not match the current
97
 * authentication state.
98
 *
99
 * @param subject the Subject for which principals will potentially be merged into the Subject's session.
100
 */
101
protected void mergeAuthenticationState(Subject subject) {
102
103
    Session session = subject.getSession(false);
104
105
    if (session == null) {
106
        if (subject.isAuthenticated()) {
107
            session = subject.getSession();
108
            session.setAttribute(DefaultSubjectContext.AUTHENTICATED_SESSION_KEY, Boolean.TRUE);
109
        }
110
        //otherwise no session and not authenticated - nothing to save
111
    } else {
112
        Boolean existingAuthc = (Boolean) session.getAttribute(DefaultSubjectContext.AUTHENTICATED_SESSION_KEY);
113
114
        if (subject.isAuthenticated()) {
115
            if (existingAuthc == null || !existingAuthc) {
116
                session.setAttribute(DefaultSubjectContext.AUTHENTICATED_SESSION_KEY, Boolean.TRUE);
117
            }
118
            //otherwise authc state matches - no need to update the session
119
        } else {
120
            if (existingAuthc != null) {
121
                //existing doesn't match the current state - remove it:
122
                session.removeAttribute(DefaultSubjectContext.AUTHENTICATED_SESSION_KEY);
123
            }
124
            //otherwise not in the session and not authenticated - no need to update the session
125
        }
126
    }
127
}

理解 subject.login()

从众多 SecurityManager 的方法图中可知,只有 DefaultSecurityManager 实现了 login() 方法,所以从它的源码开始解析。

*1. DefaultSecurityManager | login(token) *

1
// line 259
2
/**
3
 * First authenticates the {@code AuthenticationToken} argument, and if successful, constructs a
4
 * {@code Subject} instance representing the authenticated account's identity.
5
 * <p/>
6
 * Once constructed, the {@code Subject} instance is then {@link #bind bound} to the application for
7
 * subsequent access before being returned to the caller.
8
 *
9
 * @param token the authenticationToken to process for the login attempt.
10
 * @return a Subject representing the authenticated user.
11
 * @throws AuthenticationException if there is a problem authenticating the specified {@code token}.
12
 */
13
public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException {
14
    AuthenticationInfo info;
15
    try {
16
        info = authenticate(token);
17
    } catch (AuthenticationException ae) {
18
        try {
19
            onFailedLogin(token, ae, subject);
20
        } catch (Exception e) {
21
            if (log.isInfoEnabled()) {
22
                log.info("onFailedLogin method threw an " +
23
                        "exception.  Logging and propagating original AuthenticationException.", e);
24
            }
25
        }
26
        throw ae; //propagate
27
    }
28
29
    Subject loggedIn = createSubject(token, info, subject);
30
31
    onSuccessfulLogin(token, info, loggedIn);
32
33
    return loggedIn;
34
}

** 2. AuthenticatingSecurityManager | authenticate(token) **

** 3. ModularRealmAuthenticator | authenticate(token)
查看源码的时候,一直弄不懂 ModularRealmAuthenticator 对象中的 Collection<Realm> realms 是什么时候赋值的。
因为我们只是 securityManager.setRealm(userRealm) 这样的方式来填充 realm,但是是什么时候给你 Authenticator 填充 realm 的呢?
通过debug才发现,AuthenticatingSecurityManager 重写了父类 RealmSecurityMangerafterRealmsSet() 方法,在里面给 authenticator 填充了 realms

1
// line 90
2
/**
3
 * Passes on the {@link #getRealms() realms} to the internal delegate <code>Authenticator</code> instance so
4
 * that it may use them during authentication attempts.
5
 */
6
protected void afterRealmsSet() {
7
    super.afterRealmsSet();
8
    if (this.authenticator instanceof ModularRealmAuthenticator) {
9
        ((ModularRealmAuthenticator) this.authenticator).setRealms(getRealms());
10
    }
11
}

Session

类继承图

1571641163441

Session

1571641545728

ValidatingSession

1571641575122

SimpleSession

SimpleSession 将Attribute 保存在 map对象中

1
/*
2
 * Licensed to the Apache Software Foundation (ASF) under one
3
 * or more contributor license agreements.  See the NOTICE file
4
 * distributed with this work for additional information
5
 * regarding copyright ownership.  The ASF licenses this file
6
 * to you under the Apache License, Version 2.0 (the
7
 * "License"); you may not use this file except in compliance
8
 * with the License.  You may obtain a copy of the License at
9
 *
10
 *     http://www.apache.org/licenses/LICENSE-2.0
11
 *
12
 * Unless required by applicable law or agreed to in writing,
13
 * software distributed under the License is distributed on an
14
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
 * KIND, either express or implied.  See the License for the
16
 * specific language governing permissions and limitations
17
 * under the License.
18
 */
19
package org.apache.shiro.session.mgt;
20
21
import org.apache.shiro.session.ExpiredSessionException;
22
import org.apache.shiro.session.InvalidSessionException;
23
import org.apache.shiro.session.StoppedSessionException;
24
import org.apache.shiro.util.CollectionUtils;
25
import org.slf4j.Logger;
26
import org.slf4j.LoggerFactory;
27
28
import java.io.IOException;
29
import java.io.ObjectInputStream;
30
import java.io.ObjectOutputStream;
31
import java.io.Serializable;
32
import java.text.DateFormat;
33
import java.util.*;
34
35
36
/**
37
 * Simple {@link org.apache.shiro.session.Session} JavaBeans-compatible POJO implementation, intended to be used on the
38
 * business/server tier.
39
 *
40
 * @since 0.1
41
 */
42
public class SimpleSession implements ValidatingSession, Serializable {
43
44
    // Serialization reminder:
45
    // You _MUST_ change this number if you introduce a change to this class
46
    // that is NOT serialization backwards compatible.  Serialization-compatible
47
    // changes do not require a change to this number.  If you need to generate
48
    // a new number in this case, use the JDK's 'serialver' program to generate it.
49
    private static final long serialVersionUID = -7125642695178165650L;
50
51
    //TODO - complete JavaDoc
52
    private transient static final Logger log = LoggerFactory.getLogger(SimpleSession.class);
53
54
    protected static final long MILLIS_PER_SECOND = 1000;
55
    protected static final long MILLIS_PER_MINUTE = 60 * MILLIS_PER_SECOND;
56
    protected static final long MILLIS_PER_HOUR = 60 * MILLIS_PER_MINUTE;
57
58
    //serialization bitmask fields. DO NOT CHANGE THE ORDER THEY ARE DECLARED!
59
    static int bitIndexCounter = 0;
60
    private static final int ID_BIT_MASK = 1 << bitIndexCounter++;
61
    private static final int START_TIMESTAMP_BIT_MASK = 1 << bitIndexCounter++;
62
    private static final int STOP_TIMESTAMP_BIT_MASK = 1 << bitIndexCounter++;
63
    private static final int LAST_ACCESS_TIME_BIT_MASK = 1 << bitIndexCounter++;
64
    private static final int TIMEOUT_BIT_MASK = 1 << bitIndexCounter++;
65
    private static final int EXPIRED_BIT_MASK = 1 << bitIndexCounter++;
66
    private static final int HOST_BIT_MASK = 1 << bitIndexCounter++;
67
    private static final int ATTRIBUTES_BIT_MASK = 1 << bitIndexCounter++;
68
69
    // ==============================================================
70
    // NOTICE:
71
    //
72
    // The following fields are marked as transient to avoid double-serialization.
73
    // They are in fact serialized (even though 'transient' usually indicates otherwise),
74
    // but they are serialized explicitly via the writeObject and readObject implementations
75
    // in this class.
76
    //
77
    // If we didn't declare them as transient, the out.defaultWriteObject(); call in writeObject would
78
    // serialize all non-transient fields as well, effectively doubly serializing the fields (also
79
    // doubling the serialization size).
80
    //
81
    // This finding, with discussion, was covered here:
82
    //
83
    // http://mail-archives.apache.org/mod_mbox/shiro-user/201109.mbox/%3C4E81BCBD.8060909@metaphysis.net%3E
84
    //
85
    // ==============================================================
86
    private transient Serializable id;
87
    private transient Date startTimestamp;
88
    private transient Date stopTimestamp;
89
    private transient Date lastAccessTime;
90
    private transient long timeout;
91
    private transient boolean expired;
92
    private transient String host;
93
    private transient Map<Object, Object> attributes;
94
95
    public SimpleSession() {
96
        this.timeout = DefaultSessionManager.DEFAULT_GLOBAL_SESSION_TIMEOUT; //TODO - remove concrete reference to DefaultSessionManager
97
        this.startTimestamp = new Date();
98
        this.lastAccessTime = this.startTimestamp;
99
    }
100
101
    public SimpleSession(String host) {
102
        this();
103
        this.host = host;
104
    }
105
106
    ......
107
    // getter setter
108
109
    public void touch() {
110
        this.lastAccessTime = new Date();
111
    }
112
113
    public void stop() {
114
        if (this.stopTimestamp == null) {
115
            this.stopTimestamp = new Date();
116
        }
117
    }
118
119
    protected boolean isStopped() {
120
        return getStopTimestamp() != null;
121
    }
122
123
    protected void expire() {
124
        stop();
125
        this.expired = true;
126
    }
127
128
    /**
129
     * @since 0.9
130
     */
131
    public boolean isValid() {
132
        return !isStopped() && !isExpired();
133
    }
134
135
    /**
136
     * Determines if this session is expired.
137
     *
138
     * @return true if the specified session has expired, false otherwise.
139
     */
140
    protected boolean isTimedOut() {
141
142
        if (isExpired()) {
143
            return true;
144
        }
145
146
        long timeout = getTimeout();
147
148
        if (timeout >= 0l) {
149
150
            Date lastAccessTime = getLastAccessTime();
151
152
            if (lastAccessTime == null) {
153
                String msg = "session.lastAccessTime for session with id [" +
154
                        getId() + "] is null.  This value must be set at " +
155
                        "least once, preferably at least upon instantiation.  Please check the " +
156
                        getClass().getName() + " implementation and ensure " +
157
                        "this value will be set (perhaps in the constructor?)";
158
                throw new IllegalStateException(msg);
159
            }
160
161
            // Calculate at what time a session would have been last accessed
162
            // for it to be expired at this point.  In other words, subtract
163
            // from the current time the amount of time that a session can
164
            // be inactive before expiring.  If the session was last accessed
165
            // before this time, it is expired.
166
            long expireTimeMillis = System.currentTimeMillis() - timeout;
167
            Date expireTime = new Date(expireTimeMillis);
168
            return lastAccessTime.before(expireTime);
169
        } else {
170
            if (log.isTraceEnabled()) {
171
                log.trace("No timeout for session with id [" + getId() +
172
                        "].  Session is not considered expired.");
173
            }
174
        }
175
176
        return false;
177
    }
178
179
    public void validate() throws InvalidSessionException {
180
        //check for stopped:
181
        if (isStopped()) {
182
            //timestamp is set, so the session is considered stopped:
183
            String msg = "Session with id [" + getId() + "] has been " +
184
                    "explicitly stopped.  No further interaction under this session is " +
185
                    "allowed.";
186
            throw new StoppedSessionException(msg);
187
        }
188
189
        //check for expiration
190
        if (isTimedOut()) {
191
            expire();
192
193
            //throw an exception explaining details of why it expired:
194
            Date lastAccessTime = getLastAccessTime();
195
            long timeout = getTimeout();
196
197
            Serializable sessionId = getId();
198
199
            DateFormat df = DateFormat.getInstance();
200
            String msg = "Session with id [" + sessionId + "] has expired. " +
201
                    "Last access time: " + df.format(lastAccessTime) +
202
                    ".  Current time: " + df.format(new Date()) +
203
                    ".  Session timeout is set to " + timeout / MILLIS_PER_SECOND + " seconds (" +
204
                    timeout / MILLIS_PER_MINUTE + " minutes)";
205
            if (log.isTraceEnabled()) {
206
                log.trace(msg);
207
            }
208
            throw new ExpiredSessionException(msg);
209
        }
210
    }
211
212
    private Map<Object, Object> getAttributesLazy() {
213
        Map<Object, Object> attributes = getAttributes();
214
        if (attributes == null) {
215
            attributes = new HashMap<Object, Object>();
216
            setAttributes(attributes);
217
        }
218
        return attributes;
219
    }
220
221
    public Collection<Object> getAttributeKeys() throws InvalidSessionException {
222
        Map<Object, Object> attributes = getAttributes();
223
        if (attributes == null) {
224
            return Collections.emptySet();
225
        }
226
        return attributes.keySet();
227
    }
228
229
    public Object getAttribute(Object key) {
230
        Map<Object, Object> attributes = getAttributes();
231
        if (attributes == null) {
232
            return null;
233
        }
234
        return attributes.get(key);
235
    }
236
237
    public void setAttribute(Object key, Object value) {
238
        if (value == null) {
239
            removeAttribute(key);
240
        } else {
241
            getAttributesLazy().put(key, value);
242
        }
243
    }
244
245
    public Object removeAttribute(Object key) {
246
        Map<Object, Object> attributes = getAttributes();
247
        if (attributes == null) {
248
            return null;
249
        } else {
250
            return attributes.remove(key);
251
        }
252
    }
253
254
    /**
255
     * Returns {@code true} if the specified argument is an {@code instanceof} {@code SimpleSession} and both
256
     * {@link #getId() id}s are equal.  If the argument is a {@code SimpleSession} and either 'this' or the argument
257
     * does not yet have an ID assigned, the value of {@link #onEquals(SimpleSession) onEquals} is returned, which
258
     * does a necessary attribute-based comparison when IDs are not available.
259
     * <p/>
260
     * Do your best to ensure {@code SimpleSession} instances receive an ID very early in their lifecycle to
261
     * avoid the more expensive attributes-based comparison.
262
     *
263
     * @param obj the object to compare with this one for equality.
264
     * @return {@code true} if this object is equivalent to the specified argument, {@code false} otherwise.
265
     */
266
    @Override
267
    public boolean equals(Object obj) {
268
        if (this == obj) {
269
            return true;
270
        }
271
        if (obj instanceof SimpleSession) {
272
            SimpleSession other = (SimpleSession) obj;
273
            Serializable thisId = getId();
274
            Serializable otherId = other.getId();
275
            if (thisId != null && otherId != null) {
276
                return thisId.equals(otherId);
277
            } else {
278
                //fall back to an attribute based comparison:
279
                return onEquals(other);
280
            }
281
        }
282
        return false;
283
    }
284
285
    /**
286
     * Provides an attribute-based comparison (no ID comparison) - incurred <em>only</em> when 'this' or the
287
     * session object being compared for equality do not have a session id.
288
     *
289
     * @param ss the SimpleSession instance to compare for equality.
290
     * @return true if all the attributes, except the id, are equal to this object's attributes.
291
     * @since 1.0
292
     */
293
    protected boolean onEquals(SimpleSession ss) {
294
        return (getStartTimestamp() != null ? getStartTimestamp().equals(ss.getStartTimestamp()) : ss.getStartTimestamp() == null) &&
295
                (getStopTimestamp() != null ? getStopTimestamp().equals(ss.getStopTimestamp()) : ss.getStopTimestamp() == null) &&
296
                (getLastAccessTime() != null ? getLastAccessTime().equals(ss.getLastAccessTime()) : ss.getLastAccessTime() == null) &&
297
                (getTimeout() == ss.getTimeout()) &&
298
                (isExpired() == ss.isExpired()) &&
299
                (getHost() != null ? getHost().equals(ss.getHost()) : ss.getHost() == null) &&
300
                (getAttributes() != null ? getAttributes().equals(ss.getAttributes()) : ss.getAttributes() == null);
301
    }
302
303
    /**
304
     * Returns the hashCode.  If the {@link #getId() id} is not {@code null}, its hashcode is returned immediately.
305
     * If it is {@code null}, an attributes-based hashCode will be calculated and returned.
306
     * <p/>
307
     * Do your best to ensure {@code SimpleSession} instances receive an ID very early in their lifecycle to
308
     * avoid the more expensive attributes-based calculation.
309
     *
310
     * @return this object's hashCode
311
     * @since 1.0
312
     */
313
    @Override
314
    public int hashCode() {
315
        Serializable id = getId();
316
        if (id != null) {
317
            return id.hashCode();
318
        }
319
        int hashCode = getStartTimestamp() != null ? getStartTimestamp().hashCode() : 0;
320
        hashCode = 31 * hashCode + (getStopTimestamp() != null ? getStopTimestamp().hashCode() : 0);
321
        hashCode = 31 * hashCode + (getLastAccessTime() != null ? getLastAccessTime().hashCode() : 0);
322
        hashCode = 31 * hashCode + Long.valueOf(Math.max(getTimeout(), 0)).hashCode();
323
        hashCode = 31 * hashCode + Boolean.valueOf(isExpired()).hashCode();
324
        hashCode = 31 * hashCode + (getHost() != null ? getHost().hashCode() : 0);
325
        hashCode = 31 * hashCode + (getAttributes() != null ? getAttributes().hashCode() : 0);
326
        return hashCode;
327
    }
328
329
    /**
330
     * Returns the string representation of this SimpleSession, equal to
331
     * <code>getClass().getName() + &quot;,id=&quot; + getId()</code>.
332
     *
333
     * @return the string representation of this SimpleSession, equal to
334
     *         <code>getClass().getName() + &quot;,id=&quot; + getId()</code>.
335
     * @since 1.0
336
     */
337
    @Override
338
    public String toString() {
339
        StringBuilder sb = new StringBuilder();
340
        sb.append(getClass().getName()).append(",id=").append(getId());
341
        return sb.toString();
342
    }
343
344
    /**
345
     * Serializes this object to the specified output stream for JDK Serialization.
346
     *
347
     * @param out output stream used for Object serialization.
348
     * @throws IOException if any of this object's fields cannot be written to the stream.
349
     * @since 1.0
350
     */
351
    private void writeObject(ObjectOutputStream out) throws IOException {
352
        out.defaultWriteObject();
353
        short alteredFieldsBitMask = getAlteredFieldsBitMask();
354
        out.writeShort(alteredFieldsBitMask);
355
        if (id != null) {
356
            out.writeObject(id);
357
        }
358
        if (startTimestamp != null) {
359
            out.writeObject(startTimestamp);
360
        }
361
        if (stopTimestamp != null) {
362
            out.writeObject(stopTimestamp);
363
        }
364
        if (lastAccessTime != null) {
365
            out.writeObject(lastAccessTime);
366
        }
367
        if (timeout != 0l) {
368
            out.writeLong(timeout);
369
        }
370
        if (expired) {
371
            out.writeBoolean(expired);
372
        }
373
        if (host != null) {
374
            out.writeUTF(host);
375
        }
376
        if (!CollectionUtils.isEmpty(attributes)) {
377
            out.writeObject(attributes);
378
        }
379
    }
380
381
    /**
382
     * Reconstitutes this object based on the specified InputStream for JDK Serialization.
383
     *
384
     * @param in the input stream to use for reading data to populate this object.
385
     * @throws IOException            if the input stream cannot be used.
386
     * @throws ClassNotFoundException if a required class needed for instantiation is not available in the present JVM
387
     * @since 1.0
388
     */
389
    @SuppressWarnings({"unchecked"})
390
    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
391
        in.defaultReadObject();
392
        short bitMask = in.readShort();
393
394
        if (isFieldPresent(bitMask, ID_BIT_MASK)) {
395
            this.id = (Serializable) in.readObject();
396
        }
397
        if (isFieldPresent(bitMask, START_TIMESTAMP_BIT_MASK)) {
398
            this.startTimestamp = (Date) in.readObject();
399
        }
400
        if (isFieldPresent(bitMask, STOP_TIMESTAMP_BIT_MASK)) {
401
            this.stopTimestamp = (Date) in.readObject();
402
        }
403
        if (isFieldPresent(bitMask, LAST_ACCESS_TIME_BIT_MASK)) {
404
            this.lastAccessTime = (Date) in.readObject();
405
        }
406
        if (isFieldPresent(bitMask, TIMEOUT_BIT_MASK)) {
407
            this.timeout = in.readLong();
408
        }
409
        if (isFieldPresent(bitMask, EXPIRED_BIT_MASK)) {
410
            this.expired = in.readBoolean();
411
        }
412
        if (isFieldPresent(bitMask, HOST_BIT_MASK)) {
413
            this.host = in.readUTF();
414
        }
415
        if (isFieldPresent(bitMask, ATTRIBUTES_BIT_MASK)) {
416
            this.attributes = (Map<Object, Object>) in.readObject();
417
        }
418
    }
419
420
    /**
421
     * Returns a bit mask used during serialization indicating which fields have been serialized. Fields that have been
422
     * altered (not null and/or not retaining the class defaults) will be serialized and have 1 in their respective
423
     * index, fields that are null and/or retain class default values have 0.
424
     *
425
     * @return a bit mask used during serialization indicating which fields have been serialized.
426
     * @since 1.0
427
     */
428
    private short getAlteredFieldsBitMask() {
429
        int bitMask = 0;
430
        bitMask = id != null ? bitMask | ID_BIT_MASK : bitMask;
431
        bitMask = startTimestamp != null ? bitMask | START_TIMESTAMP_BIT_MASK : bitMask;
432
        bitMask = stopTimestamp != null ? bitMask | STOP_TIMESTAMP_BIT_MASK : bitMask;
433
        bitMask = lastAccessTime != null ? bitMask | LAST_ACCESS_TIME_BIT_MASK : bitMask;
434
        bitMask = timeout != 0l ? bitMask | TIMEOUT_BIT_MASK : bitMask;
435
        bitMask = expired ? bitMask | EXPIRED_BIT_MASK : bitMask;
436
        bitMask = host != null ? bitMask | HOST_BIT_MASK : bitMask;
437
        bitMask = !CollectionUtils.isEmpty(attributes) ? bitMask | ATTRIBUTES_BIT_MASK : bitMask;
438
        return (short) bitMask;
439
    }
440
441
    /**
442
     * Returns {@code true} if the given {@code bitMask} argument indicates that the specified field has been
443
     * serialized and therefore should be read during deserialization, {@code false} otherwise.
444
     *
445
     * @param bitMask      the aggregate bitmask for all fields that have been serialized.  Individual bits represent
446
     *                     the fields that have been serialized.  A bit set to 1 means that corresponding field has
447
     *                     been serialized, 0 means it hasn't been serialized.
448
     * @param fieldBitMask the field bit mask constant identifying which bit to inspect (corresponds to a class attribute).
449
     * @return {@code true} if the given {@code bitMask} argument indicates that the specified field has been
450
     *         serialized and therefore should be read during deserialization, {@code false} otherwise.
451
     * @since 1.0
452
     */
453
    private static boolean isFieldPresent(short bitMask, int fieldBitMask) {
454
        return (bitMask & fieldBitMask) != 0;
455
    }
456
457
}

DelegatingSession

1
/*
2
 * Licensed to the Apache Software Foundation (ASF) under one
3
 * or more contributor license agreements.  See the NOTICE file
4
 * distributed with this work for additional information
5
 * regarding copyright ownership.  The ASF licenses this file
6
 * to you under the Apache License, Version 2.0 (the
7
 * "License"); you may not use this file except in compliance
8
 * with the License.  You may obtain a copy of the License at
9
 *
10
 *     http://www.apache.org/licenses/LICENSE-2.0
11
 *
12
 * Unless required by applicable law or agreed to in writing,
13
 * software distributed under the License is distributed on an
14
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
 * KIND, either express or implied.  See the License for the
16
 * specific language governing permissions and limitations
17
 * under the License.
18
 */
19
package org.apache.shiro.session.mgt;
20
21
import org.apache.shiro.session.InvalidSessionException;
22
import org.apache.shiro.session.Session;
23
24
import java.io.Serializable;
25
import java.util.Collection;
26
import java.util.Date;
27
28
/**
29
 * A DelegatingSession is a client-tier representation of a server side
30
 * {@link org.apache.shiro.session.Session Session}.
31
 * This implementation is basically a proxy to a server-side {@link NativeSessionManager NativeSessionManager},
32
 * which will return the proper results for each method call.
33
 * <p/>
34
 * <p>A <tt>DelegatingSession</tt> will cache data when appropriate to avoid a remote method invocation,
35
 * only communicating with the server when necessary.
36
 * <p/>
37
 * <p>Of course, if used in-process with a NativeSessionManager business POJO, as might be the case in a
38
 * web-based application where the web classes and server-side business pojos exist in the same
39
 * JVM, a remote method call will not be incurred.
40
 *
41
 * @since 0.1
42
 */
43
public class DelegatingSession implements Session, Serializable {
44
45
    //TODO - complete JavaDoc
46
47
    private final SessionKey key;
48
49
    //cached fields to avoid a server-side method call if out-of-process:
50
    private Date startTimestamp = null;
51
    private String host = null;
52
53
    /**
54
     * Handle to the target NativeSessionManager that will support the delegate calls.
55
     */
56
    private final transient NativeSessionManager sessionManager;
57
58
59
    public DelegatingSession(NativeSessionManager sessionManager, SessionKey key) {
60
        if (sessionManager == null) {
61
            throw new IllegalArgumentException("sessionManager argument cannot be null.");
62
        }
63
        if (key == null) {
64
            throw new IllegalArgumentException("sessionKey argument cannot be null.");
65
        }
66
        if (key.getSessionId() == null) {
67
            String msg = "The " + DelegatingSession.class.getName() + " implementation requires that the " +
68
                    "SessionKey argument returns a non-null sessionId to support the " +
69
                    "Session.getId() invocations.";
70
            throw new IllegalArgumentException(msg);
71
        }
72
        this.sessionManager = sessionManager;
73
        this.key = key;
74
    }
75
76
    /**
77
     * @see org.apache.shiro.session.Session#getId()
78
     */
79
    public Serializable getId() {
80
        return key.getSessionId();
81
    }
82
83
    /**
84
     * @see org.apache.shiro.session.Session#getStartTimestamp()
85
     */
86
    public Date getStartTimestamp() {
87
        if (startTimestamp == null) {
88
            startTimestamp = sessionManager.getStartTimestamp(key);
89
        }
90
        return startTimestamp;
91
    }
92
93
    /**
94
     * @see org.apache.shiro.session.Session#getLastAccessTime()
95
     */
96
    public Date getLastAccessTime() {
97
        //can't cache - only business pojo knows the accurate time:
98
        return sessionManager.getLastAccessTime(key);
99
    }
100
101
    public long getTimeout() throws InvalidSessionException {
102
        return sessionManager.getTimeout(key);
103
    }
104
105
    public void setTimeout(long maxIdleTimeInMillis) throws InvalidSessionException {
106
        sessionManager.setTimeout(key, maxIdleTimeInMillis);
107
    }
108
109
    public String getHost() {
110
        if (host == null) {
111
            host = sessionManager.getHost(key);
112
        }
113
        return host;
114
    }
115
116
    /**
117
     * @see org.apache.shiro.session.Session#touch()
118
     */
119
    public void touch() throws InvalidSessionException {
120
        sessionManager.touch(key);
121
    }
122
123
    /**
124
     * @see org.apache.shiro.session.Session#stop()
125
     */
126
    public void stop() throws InvalidSessionException {
127
        sessionManager.stop(key);
128
    }
129
130
    /**
131
     * @see org.apache.shiro.session.Session#getAttributeKeys
132
     */
133
    public Collection<Object> getAttributeKeys() throws InvalidSessionException {
134
        return sessionManager.getAttributeKeys(key);
135
    }
136
137
    /**
138
     * @see org.apache.shiro.session.Session#getAttribute(Object key)
139
     */
140
    public Object getAttribute(Object attributeKey) throws InvalidSessionException {
141
        return sessionManager.getAttribute(this.key, attributeKey);
142
    }
143
144
    /**
145
     * @see Session#setAttribute(Object key, Object value)
146
     */
147
    public void setAttribute(Object attributeKey, Object value) throws InvalidSessionException {
148
        if (value == null) {
149
            removeAttribute(attributeKey);
150
        } else {
151
            sessionManager.setAttribute(this.key, attributeKey, value);
152
        }
153
    }
154
155
    /**
156
     * @see Session#removeAttribute(Object key)
157
     */
158
    public Object removeAttribute(Object attributeKey) throws InvalidSessionException {
159
        return sessionManager.removeAttribute(this.key, attributeKey);
160
    }
161
}

这是个门面类,它所有的方法实现都是调用 sessionManager 的相应方法

SessionFactory

image-20191028104000531

只有一个方法: createSession()

1
/*
2
 * Licensed to the Apache Software Foundation (ASF) under one
3
 * or more contributor license agreements.  See the NOTICE file
4
 * distributed with this work for additional information
5
 * regarding copyright ownership.  The ASF licenses this file
6
 * to you under the Apache License, Version 2.0 (the
7
 * "License"); you may not use this file except in compliance
8
 * with the License.  You may obtain a copy of the License at
9
 *
10
 *     http://www.apache.org/licenses/LICENSE-2.0
11
 *
12
 * Unless required by applicable law or agreed to in writing,
13
 * software distributed under the License is distributed on an
14
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
 * KIND, either express or implied.  See the License for the
16
 * specific language governing permissions and limitations
17
 * under the License.
18
 */
19
package org.apache.shiro.session.mgt;
20
21
import org.apache.shiro.session.Session;
22
23
/**
24
 * {@code SessionFactory} implementation that generates {@link SimpleSession} instances.
25
 *
26
 * @since 1.0
27
 */
28
public class SimpleSessionFactory implements SessionFactory {
29
30
    /**
31
     * Creates a new {@link SimpleSession SimpleSession} instance retaining the context's
32
     * {@link SessionContext#getHost() host} if one can be found.
33
     *
34
     * @param initData the initialization data to be used during {@link Session} creation.
35
     * @return a new {@link SimpleSession SimpleSession} instance
36
     */
37
    public Session createSession(SessionContext initData) {
38
        if (initData != null) {
39
            String host = initData.getHost();
40
            if (host != null) {
41
                return new SimpleSession(host);
42
            }
43
        }
44
        return new SimpleSession();
45
    }
46
}

了解 SessionValidationScheduler

SessionValidationScheduler 会话验证调度器,定时调用方法 validateSessions() 验证session是否过期。其实例n在 AbstractValidatingSessionManager 中被声明。

1571299072379

sessionManager 默认使 用ExecutorServiceSessionValidationSchedule,类继承图如下:

1571299161013

类结构如下:

1571298835647

理解 SessionValidationScheduler

enableSessionValidation(): void

ExecutorServiceSessionValidationScheduler 中的实现:

1
/**
2
     * Creates a single thread {@link ScheduledExecutorService} to validate sessions at fixed intervals 
3
     * and enables this scheduler. The executor is created as a daemon thread to allow JVM to shut down
4
     */
5
    //TODO Implement an integration test to test for jvm exit as part of the standalone example
6
    // (so we don't have to change the unit test execution model for the core module)
7
    public void enableSessionValidation() {
8
        if (this.interval > 0l) {
9
            // 真正的定时器
10
            this.service = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() {  
11
	            private final AtomicInteger count = new AtomicInteger(1);
12
13
	            public Thread newThread(Runnable r) {  
14
	                Thread thread = new Thread(r);  
15
	                thread.setDaemon(true);  
16
	                thread.setName(threadNamePrefix + count.getAndIncrement());
17
	                return thread;  
18
	            }  
19
            });        
20
            // 启动定时器,看 `scheduleAtFixedRate` 源码注释理解该方法
21
            this.service.scheduleAtFixedRate(this, interval, interval, TimeUnit.MILLISECONDS);
22
        }
23
        this.enabled = true;
24
    }
25
26
    public void run() {
27
        if (log.isDebugEnabled()) {
28
            log.debug("Executing session validation...");
29
        }
30
        long startTime = System.currentTimeMillis();
31
        this.sessionManager.validateSessions();
32
        long stopTime = System.currentTimeMillis();
33
        if (log.isDebugEnabled()) {
34
            log.debug("Session validation completed successfully in " + (stopTime - startTime) + " milliseconds.");
35
        }
36
    }

简译:以固定的时间间隔创建一个单线程来验证会话,并且开启这个调度。
scheduleAtFixedRate() 方法第一个参数以 Runnable 实现对象,里面一定是调用了 Runnable 对象的 run()

那么,是在什么时候开启的定时器呢?

这时我使用 Alt+F7来查找调用了 ExecutorServiceSessionValidationScheduleenableSessionValidation() 方法。

AbstractVailidatingSessionManagerenableSessionValidation()

该方法调用了 SessionValidationSchedulerenableSessionValidation
而它自己又被本类中的 enableSessionValidationIfNecessary() 方法调用 ;
enableSessionValidationIfNecessary() 又被本类中的doGetSession()createSession()方法调用。

1
......
2
   // line 83    
3
private void enableSessionValidationIfNecessary() {
4
       SessionValidationScheduler scheduler = getSessionValidationScheduler();
5
       if (isSessionValidationSchedulerEnabled() && (scheduler == null || !scheduler.isEnabled())) {
6
           enableSessionValidation();
7
       }
8
   }
9
10
......
11
   // line 112
12
   @Override
13
   protected final Session doGetSession(final SessionKey key) throws InvalidSessionException {
14
       enableSessionValidationIfNecessary();
15
16
       log.trace("Attempting to retrieve session with key {}", key);
17
18
       Session s = retrieveSession(key);
19
       if (s != null) {
20
           validate(s, key);
21
       }
22
       return s;
23
   }
24
......
25
   // line 134
26
   protected Session createSession(SessionContext context) throws AuthorizationException {
27
       enableSessionValidationIfNecessary();
28
       return doCreateSession(context);
29
   }
30
......
31
   // line 223    
32
protected synchronized void enableSessionValidation() {
33
       SessionValidationScheduler scheduler = getSessionValidationScheduler();
34
       if (scheduler == null) {
35
           scheduler = createSessionValidationScheduler();
36
           setSessionValidationScheduler(scheduler);
37
       }
38
       // it is possible that that a scheduler was already created and set via 'setSessionValidationScheduler()'
39
       // but would not have been enabled/started yet
40
       if (!scheduler.isEnabled()) {
41
           if (log.isInfoEnabled()) {
42
               log.info("Enabling session validation scheduler...");
43
           }
44
           scheduler.enableSessionValidation();
45
           afterSessionValidationEnabled();
46
       }
47
   }

了解 SessionDAO

类继承图

image-20191028104313016

SessionDAO

1571367544804

AbstractSessionDAO

1571367836411
SessionIdGenerator sessionIdGenerator 用来生成 sessionId;

CachingSessionDAO

省略 getter 和 setter
1571368167696

EnterpriseCachingSessionDAO

1571368312829

MemorySessionDAO

1571369743342

理解 MemorySessionDAO

create(Session): Serializable

1. AbstractSessionDAO | create()

1
   // line 85
2
/**
3
   * Generates a new ID to be applied to the specified {@code session} instance.  This method is usually called
4
   * from within a subclass's {@link #doCreate} implementation where they assign the returned id to the session
5
   * instance and then create a record with this ID in the EIS data store.
6
   * <p/>
7
   * Subclass implementations backed by EIS data stores that auto-generate IDs during record creation, such as
8
   * relational databases, don't need to use this method or the {@link #getSessionIdGenerator() sessionIdGenerator}
9
   * attribute - they can simply return the data store's generated ID from the {@link #doCreate} implementation
10
   * if desired.
11
   * <p/>
12
   * This implementation uses the {@link #setSessionIdGenerator configured} {@link SessionIdGenerator} to create
13
   * the ID.
14
   *
15
   * @param session the new session instance for which an ID will be generated and then assigned
16
   * @return the generated ID to assign
17
   */
18
  protected Serializable generateSessionId(Session session) {
19
      if (this.sessionIdGenerator == null) {
20
          String msg = "sessionIdGenerator attribute has not been configured.";
21
          throw new IllegalStateException(msg);
22
      }
23
      return this.sessionIdGenerator.generateId(session);
24
  }
25
	
26
  /**
27
   * Creates the session by delegating EIS creation to subclasses via the {@link #doCreate} method, and then
28
   * asserting that the returned sessionId is not null.
29
   *
30
   * @param session Session object to create in the EIS and associate with an ID.
31
   */
32
  public Serializable create(Session session) {
33
// 调用 doCreate(),返回 sessionId
34
      Serializable sessionId = doCreate(session);
35
      verifySessionId(sessionId);
36
      return sessionId;
37
  }
38
  /**
39
   * Ensures the sessionId returned from the subclass implementation of {@link #doCreate} is not null and not
40
   * already in use.
41
   *
42
   * @param sessionId session id returned from the subclass implementation of {@link #doCreate}
43
   */
44
  private void verifySessionId(Serializable sessionId) {
45
      if (sessionId == null) {
46
          String msg = "sessionId returned from doCreate implementation is null.  Please verify the implementation.";
47
          throw new IllegalStateException(msg);
48
      }
49
  }
50
  ......
51
  // line 156
52
  /**
53
   * Subclass hook to actually persist the given <tt>Session</tt> instance to the underlying EIS.
54
   *
55
   * @param session the Session instance to persist to the EIS.
56
   * @return the id of the session created in the EIS (i.e. this is almost always a primary key and should be the
57
   *         value returned from {@link org.apache.shiro.session.Session#getId() Session.getId()}.
58
   */
59
  protected abstract Serializable doCreate(Session session);

2. MemorySessionDAO | doCreate()

1
  // line 66
2
  protected Serializable doCreate(Session session) {
3
// 生成sessionId 
4
      Serializable sessionId = generateSessionId(session);
5
// 将sessionId填充到 session对象中
6
      assignSessionId(session, sessionId);
7
// 将session存储到ConcurrentHashMap对象中
8
      storeSession(sessionId, session);
9
      return sessionId;
10
  }
11
12
  protected Session storeSession(Serializable id, Session session) {
13
      if (id == null) {
14
          throw new NullPointerException("id argument cannot be null.");
15
      }
16
      return sessions.putIfAbsent(id, session);
17
  }

ConcurrentHashMap 是线程安全的 HashMap

小结

  1. 生成 sessionId (MemorySessionDAO)
  2. 将 sessionId 填充到 session中(MemorySessionDAO)
  3. 将 session 存储到 ConcurrentHashMap 对象sessions中,sessionId 作为key(MemorySessionDAO)
  4. 验证 sessionId 是否为空
  5. 返回 sessionId

readSession(Serializable): Session

1. AbstractSessionDAO | readSession()

1
// line 158
2
/**
3
    * Retrieves the Session object from the underlying EIS identified by <tt>sessionId</tt> by delegating to
4
    * the {@link #doReadSession(java.io.Serializable)} method.  If {@code null} is returned from that method, an
5
    * {@link UnknownSessionException} will be thrown.
6
    *
7
    * @param sessionId the id of the session to retrieve from the EIS.
8
    * @return the session identified by <tt>sessionId</tt> in the EIS.
9
    * @throws UnknownSessionException if the id specified does not correspond to any session in the EIS.
10
    */
11
   public Session readSession(Serializable sessionId) throws UnknownSessionException {
12
       Session s = doReadSession(sessionId);
13
       if (s == null) {
14
           throw new UnknownSessionException("There is no session with id [" + sessionId + "]");
15
       }
16
       return s;
17
   }
18
19
   /**
20
    * Subclass implementation hook that retrieves the Session object from the underlying EIS or {@code null} if a
21
    * session with that ID could not be found.
22
    *
23
    * @param sessionId the id of the <tt>Session</tt> to retrieve.
24
    * @return the Session in the EIS identified by <tt>sessionId</tt> or {@code null} if a
25
    *         session with that ID could not be found.
26
    */
27
   protected abstract Session doReadSession(Serializable sessionId);

2. MemorySessionDAO | doReadSession()

1
// line 80
2
   protected Session doReadSession(Serializable sessionId) {
3
       return sessions.get(sessionId);
4
   }

update(Session): void

MemorySessionDAO | update()

1
// line 72
2
protected Session storeSession(Serializable id, Session session) {
3
    if (id == null) {
4
        throw new NullPointerException("id argument cannot be null.");
5
    }
6
    return sessions.putIfAbsent(id, session);
7
}	
8
// line 84
9
public void update(Session session) throws UnknownSessionException {
10
    storeSession(session.getId(), session);
11
}

delete(Session): void

MemorySessionDAO | delete()

1
// line 88
2
   public void delete(Session session) {
3
       if (session == null) {
4
           throw new NullPointerException("session argument cannot be null.");
5
       }
6
       Serializable id = session.getId();
7
       if (id != null) {
8
           sessions.remove(id);
9
       }
10
   }

理解 EnterpiseCachingSessionDAO

create(Session): Serializable

1. CachingSessionDAO 中重写了 readSession()

1
   // line 177
2
/**
3
    * Calls {@code super.create(session)}, then caches the session keyed by the returned {@code sessionId}, and then
4
    * returns this {@code sessionId}.
5
    *
6
    * @param session Session object to create in the EIS and then cache.
7
    */
8
   public Serializable create(Session session) {
9
       // 调用父类的create()方法,参考 MemorySessionDAO 中 AbstractSessionDAO 的 create() 方法
10
       // 内部调用 doCreate() ,然后验证 sessionId
11
       Serializable sessionId = super.create(session);
12
	// 缓存session
13
       cache(session, sessionId);
14
       return sessionId;
15
   }

2. EnterpriseCachingSessioDAO | doCreate()

1
// line 63
2
   protected Serializable doCreate(Session session) {
3
       Serializable sessionId = generateSessionId(session);
4
       assignSessionId(session, sessionId);
5
       return sessionId;
6
   }

3. CachingSessionDAO | cache(session, sessionId)

1
   ......
2
// line 141
3
/**
4
    * Returns the active sessions cache, but if that cache instance is null, first lazily creates the cache instance
5
    * via the {@link #createActiveSessionsCache()} method and then returns the instance.
6
    * <p/>
7
    * Note that this method will only return a non-null value code if the {@code CacheManager} has been set.  If
8
    * not set, there will be no cache.
9
    *
10
    * @return the active sessions cache instance.
11
    */
12
   private Cache<Serializable, Session> getActiveSessionsCacheLazy() {
13
       if (this.activeSessions == null) {
14
           this.activeSessions = createActiveSessionsCache();
15
       }
16
       return activeSessions;
17
   }
18
19
20
// line 157
21
/**
22
    * Creates a cache instance used to store active sessions.  Creation is done by first
23
    * {@link #getCacheManager() acquiring} the {@code CacheManager}.  If the cache manager is not null, the
24
    * cache returned is that resulting from the following call:
25
    * <pre>       String name = {@link #getActiveSessionsCacheName() getActiveSessionsCacheName()};
26
    * cacheManager.getCache(name);</pre>
27
    *
28
    * @return a cache instance used to store active sessions, or {@code null} if the {@code CacheManager} has
29
    *         not been set.
30
    */
31
   protected Cache<Serializable, Session> createActiveSessionsCache() {
32
       Cache<Serializable, Session> cache = null;
33
       CacheManager mgr = getCacheManager();
34
       if (mgr != null) {
35
           String name = getActiveSessionsCacheName();
36
           cache = mgr.getCache(name);
37
       }
38
       return cache;
39
   }
40
41
......
42
43
// line 220
44
/**
45
    * Caches the specified session under the cache entry key of {@code sessionId}.
46
    *
47
    * @param session   the session to cache
48
    * @param sessionId the session id, to be used as the cache entry key.
49
    * @since 1.0
50
    */
51
   protected void cache(Session session, Serializable sessionId) {
52
       if (session == null || sessionId == null) {
53
           return;
54
       }
55
       // 获取 cache 
56
       Cache<Serializable, Session> cache = getActiveSessionsCacheLazy();
57
       if (cache == null) {
58
           return;
59
       }
60
       // 将 session 存储到 cache 中,sessionId 作为 key
61
       cache(session, sessionId, cache);
62
   }
63
64
   /**
65
    * Caches the specified session in the given cache under the key of {@code sessionId}.  This implementation
66
    * simply calls {@code cache.put(sessionId,session)} and can be overridden for custom behavior.
67
    *
68
    * @param session   the session to cache
69
    * @param sessionId the id of the session, expected to be the cache key.
70
    * @param cache     the cache to store the session
71
    */
72
   protected void cache(Session session, Serializable sessionId, Cache<Serializable, Session> cache) {
73
       cache.put(sessionId, session);
74
   }

EnterpriseCachingSessionDAO 构造函数中设置了默认的 CacheManager

1
public EnterpriseCacheSessionDAO() {
2
    setCacheManager(new AbstractCacheManager() {
3
        @Override
4
        protected Cache<Serializable, Session> createCache(String name) throws CacheException {
5
            return new MapCache<Serializable, Session>(name, new ConcurrentHashMap<Serializable, Session>());
6
        }
7
    });
8
}

从中可以看出,实际中 MapCache 内部也是使用 ConcurrentHashMap对象来存储session的。

readSession(Serializable): Session

1. CachingSessionDAO | readSession()

1
   // line 250
2
/**
3
    * Attempts to acquire the Session from the cache first using the session ID as the cache key.  If no session
4
    * is found, {@code super.readSession(sessionId)} is called to perform the actual retrieval.
5
    *
6
    * @param sessionId the id of the session to retrieve from the EIS.
7
    * @return the session identified by {@code sessionId} in the EIS.
8
    * @throws UnknownSessionException if the id specified does not correspond to any session in the cache or EIS.
9
    */
10
   public Session readSession(Serializable sessionId) throws UnknownSessionException {
11
       // 根据 sessionId 获取缓存的 session
12
       Session s = getCachedSession(sessionId);
13
       if (s == null) {
14
           // 调用父类方法 readSession 获取 session
15
           // 其内部调用 doReadSession() 获取session
16
           s = super.readSession(sessionId);
17
       }
18
       return s;
19
   }
20
......
21
       
22
// line 189
23
   /**
24
    * Returns the cached session with the corresponding {@code sessionId} or {@code null} if there is
25
    * no session cached under that id (or if there is no Cache).
26
    *
27
    * @param sessionId the id of the cached session to acquire.
28
    * @return the cached session with the corresponding {@code sessionId}, or {@code null} if the session
29
    *         does not exist or is not cached.
30
    */
31
   protected Session getCachedSession(Serializable sessionId) {
32
       Session cached = null;
33
       if (sessionId != null) {
34
           Cache<Serializable, Session> cache = getActiveSessionsCacheLazy();
35
           if (cache != null) {
36
               cached = getCachedSession(sessionId, cache);
37
           }
38
       }
39
       return cached;
40
   }
41
42
   /**
43
    * Returns the Session with the specified id from the specified cache.  This method simply calls
44
    * {@code cache.get(sessionId)} and can be overridden by subclasses for custom acquisition behavior.
45
    *
46
    * @param sessionId the id of the session to acquire.
47
    * @param cache     the cache to acquire the session from
48
    * @return the cached session, or {@code null} if the session wasn't in the cache.
49
    */
50
   protected Session getCachedSession(Serializable sessionId, Cache<Serializable, Session> cache) {
51
       return cache.get(sessionId);
52
   }

EnterpriseCachingSessionDAO 中实现了 doReadSession()

1
protected Session doReadSession(Serializable sessionId) {
2
    return null; //should never execute because this implementation relies on parent class to access cache, which
3
    //is where all sessions reside - it is the cache implementation that determines if the
4
    //cache is memory only or disk-persistent, etc.
5
}

update(Session): void

1. CachingSessionDAO 中实现了 update()

1
// line 266
2
   /**
3
    * Updates the state of the given session to the EIS by first delegating to
4
    * {@link #doUpdate(org.apache.shiro.session.Session)}.  If the session is a {@link ValidatingSession}, it will
5
    * be added to the cache only if it is {@link ValidatingSession#isValid()} and if invalid, will be removed from the
6
    * cache.  If it is not a {@code ValidatingSession} instance, it will be added to the cache in any event.
7
    *
8
    * @param session the session object to update in the EIS.
9
    * @throws UnknownSessionException if no existing EIS session record exists with the
10
    *                                 identifier of {@link Session#getId() session.getId()}
11
    */
12
   public void update(Session session) throws UnknownSessionException {
13
       doUpdate(session);
14
	// 判断session是否是 ValidatingSession 实例
15
       if (session instanceof ValidatingSession) {
16
		// 判断 session 是否有效
17
           if (((ValidatingSession) session).isValid()) {
18
               cache(session, session.getId());
19
           } else {
20
               uncache(session);
21
           }
22
       } else {
23
           cache(session, session.getId());
24
       }
25
   }

2. EnterpriseCachingSessionDAO | doUpdate()

1
protected void doUpdate(Session session) {
2
    //does nothing - parent class persists to cache.
3
}

delete(Session): void

1. CachingSessionDAO | delete()

1
/**
2
 * Removes the specified session from any cache and then permanently deletes the session from the EIS by
3
 * delegating to {@link #doDelete}.
4
 *
5
 * @param session the session to remove from caches and permanently delete from the EIS.
6
 */
7
public void delete(Session session) {
8
	// 删除缓存中的session
9
    uncache(session);
10
    // 调用 doDelete()
11
    doDelete(session);
12
}

2. EnterpriseCachingSessionDAO | doDelete()

1
protected void doDelete(Session session) {
2
    //does nothing - parent class removes from cache.
3
}

总结

SessionDAO用于 session 存储,它有两个实现:

  • MemorySessionDAO 使用 ConcurrentHashMap 对象将 session 保存到内存中
  • EnterpriseCachingSessionDAO 将session保存到缓存中(该缓存默认使用 ConcurrentHashMap 保存到内存中)。其实 EnterpriseCachingSessionDAO 就是一个对外的接口,用户自定义SessionDAO时,只需继承他。

了解 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)

比较器的使用场景

  • 判断对象集合中是否包含某对象
  • 排序对象集合

当然可以直接通过“==”来判断两个对象的地址是否相同,地址相同那么对象肯定就相同了。但在实际情况中,通常根据对象的属性进行比较

从最新的JDK8来看,有三种实现对象比较的方法:

  1. 复写Object类的equals()方法

  2. 继承Comparable接口,实现compareTo()方法

  3. 定义一个对象比较器,继承Comparator接口,实现compare()方法

复写equals()方法

复写 equals() 方法, 一般用于自己实现对象数组排序的情况,而对于要使用java内置的排序算法时,使用后面两种方式都是可行的。

ps:复写 equals()方法怎么实现对象数组排序啊? equals()只能实现比较,不能排序吧。

实现Comparable接口

实现 compareTo() 方法

1
public class Employee implements Comparable<Employee>{
2
    private Integer id;
3
    private String name;
4
    private Integer age;
5
    private Double salary;
6
7
    public Employee() {
8
    }
9
10
    public Employee(Integer id, String name, Integer age, Double salary) {
11
        this.id = id;
12
        this.name = name;
13
        this.age = age;
14
        this.salary = salary;
15
    }
16
17
    @Override
18
    public String toString() {
19
        return "Employee{" +
20
                "id=" + id +
21
                ", name='" + name + '\'' +
22
                ", age=" + age +
23
                ", salary=" + salary +
24
                '}';
25
    }
26
27
    @Override
28
    public int compareTo(Employee o) {
29
        if (this.salary > o.salary) {
30
            // 由高到底排列
31
            return -1;
32
        } else if(this.salary < o.salary) {
33
            return 1;
34
        } else {
35
            if (this.age > o.age) {
36
                // 由低到高排列
37
                return 1;
38
            } else if(this.age < o.age) {
39
                return -1;
40
            } else {
41
                return 0;
42
            }
43
        }
44
    }
45
    // getter 和 setter
46
    
47
    public static void main(String[] args) {
48
        Employee[] emps = {
49
                new Employee(5, "田七", 20, 5000D),
50
                new Employee(4, "赵六", 46, 18000D),
51
                new Employee(3, "王五", 35, 15000D),
52
                new Employee(1, "张三", 24, 5000D),
53
                new Employee(2, "李四", 36, 15000D)
54
        };
55
        java.util.Arrays.sort(emps);
56
        for(Employee e : emps) {
57
            System.out.println(e);
58
        }
59
    }
60
}

Employee实现了Comparable接口的compareTo()方法,将员工先按照薪资salary从高到底排列,薪资相同时,按照年龄age从低到高排列。

输出结果:

1
Employee{id=4, name='赵六', age=46, salary=18000.0}
2
Employee{id=3, name='王五', age=35, salary=15000.0}
3
Employee{id=2, name='李四', age=36, salary=15000.0}
4
Employee{id=5, name='田七', age=20, salary=5000.0}
5
Employee{id=1, name='张三', age=24, salary=5000.0}

比较器Comparator对象

定义一个对象比较器类,继承Comparator接口,实现compare()方法

一般我们使用以上两种方法就能够满足实际的开发问题。但是当出现以下情况时,就需要用到Comparator接口:

要在已经开发好的代码基础上,完善对象的比较功能,又不想更改之前的代码,这种情况下,从JDK1.8之后出现了Comparator接口,是对这种情况的一个弥补。

1
public class Student {
2
    private Integer id;
3
    private String name;
4
    private Double score;
5
6
    public Student() {
7
    }
8
9
    public Student(Integer id, String name, Double score) {
10
        this.id = id;
11
        this.name = name;
12
        this.score = score;
13
    }
14
15
    @Override
16
    public String toString() {
17
        return "Employee{" +
18
                "id=" + id +
19
                ", name='" + name + '\'' +
20
                ", score=" + score +
21
                '}';
22
    }
23
24
    // getter 和 setter
25
26
    public static void main(String[] args) {
27
        Student[] sts = new Student[]{
28
                new Student(1, "小戴",60D),
29
                new Student(3,"小王",90D),
30
                new Student(2,"老王",80D),
31
                new Student(4, "小萱",95D)
32
        };
33
34
        Comparator<Student> studentComparator = new Comparator<Student>() {
35
            @Override
36
            public int compare(Student o1,Student o2) {
37
                if(o1.getScore() > o2.getScore()){
38
                    // 由小到大排列
39
                    return 1;
40
                }else if(o1.getScore() < o2.getScore()){
41
                    return -1;
42
                }else{
43
                    return 0;
44
                }
45
            }
46
        };
47
        java.util.Arrays.sort(sts, studentComparator);
48
49
        for(Student s : sts) {
50
            System.out.println(s);
51
        }
52
    }
53
}

线程成员 threadLocals

线程类Thread内部有一个变量:threadLocals

它是 ThreadLocal.ThreadLocalMap 对象

1
...
2
public
3
class Thread implements Runnable {
4
    ...
5
	// line 179
6
	/* ThreadLocal values pertaining to this thread. This map is maintained
7
     * by the ThreadLocal class. */
8
    ThreadLocal.ThreadLocalMap threadLocals = null;
9
    ...
10
}

类ThreadLocalMap

ThreadLocalMapThreadLocal 的内部类

1
public class ThreadLocal<T> {
2
    ...
3
	// line 287
4
	/**
5
     * ThreadLocalMap is a customized hash map suitable only for
6
     * maintaining thread local values. No operations are exported
7
     * outside of the ThreadLocal class. The class is package private to
8
     * allow declaration of fields in class Thread.  To help deal with
9
     * very large and long-lived usages, the hash table entries use
10
     * WeakReferences for keys. However, since reference queues are not
11
     * used, stale entries are guaranteed to be removed only when
12
     * the table starts running out of space.
13
     */
14
    static class ThreadLocalMap {
15
        // ...
16
    }
17
    ...
18
}

ThreadLocalMap is a customized hash map suitable only for maintaining thread local values.
大意:ThreadLocalMap 是一个自定义 HashMap,只适用于线程局部变量。
完全可以把它看做是一个 HashMap。

也就是说,Thread 有一个类似 HashMap 的成员变量

类ThreadLocal

1
/**
2
 * This class provides thread-local variables.  These variables differ from
3
 * their normal counterparts in that each thread that accesses one (via its
4
 * {@code get} or {@code set} method) has its own, independently initialized
5
 * copy of the variable.  {@code ThreadLocal} instances are typically private
6
 * static fields in classes that wish to associate state with a thread (e.g.,
7
 * a user ID or Transaction ID).
8
 *
9
 * <p>For example, the class below generates unique identifiers local to each
10
 * thread.
11
 * A thread's id is assigned the first time it invokes {@code ThreadId.get()}
12
 * and remains unchanged on subsequent calls.
13
 * 
14
 * ...
15
 *
16
 * @author  Josh Bloch and Doug Lea
17
 * @since   1.2
18
 */
19
public class ThreadLocal<T> {
20
    //...
21
}

注释大意:ThreadLocal 提供线程局部的变量,这些变量在每个线程中都有不同的副本,通过get和set方法就可以获取和设置它们。

ThreadLocal的结构如下:
类ThreadLocal结构

重要的方法只有两个:set 和 get

ThreadLocal.set(value)

ThreadLocal.set(value)Thread 内部的 threadLocals 变量添加一个元素,它的实现如下:

1
// line 189
2
/**
3
 * Sets the current thread's copy of this thread-local variable
4
 * to the specified value.  Most subclasses will have no need to
5
 * override this method, relying solely on the {@link #initialValue}
6
 * method to set the values of thread-locals.
7
 *
8
 * @param value the value to be stored in the current thread's copy of
9
 *        this thread-local.
10
 */
11
public void set(T value) {
12
	// 获取当前线程
13
    Thread t = Thread.currentThread();
14
    // 获取线程内部 ThreadLocalMap 对象
15
    ThreadLocalMap map = getMap(t);
16
    if (map != null)
17
        map.set(this, value);
18
    else// 给线程threadLocals变量new一个ThreadLocalMap对象,并添加第一个键值对
19
        createMap(t, value);
20
}
21
......
22
// line 225
23
/**
24
 * Get the map associated with a ThreadLocal. Overridden in
25
 * InheritableThreadLocal.
26
 *
27
 * @param  t the current thread
28
 * @return the map
29
 */
30
ThreadLocalMap getMap(Thread t) {
31
    return t.threadLocals;
32
}
33
34
/**
35
 * Create the map associated with a ThreadLocal. Overridden in
36
 * InheritableThreadLocal.
37
 *
38
 * @param t the current thread
39
 * @param firstValue value for the initial entry of the map
40
 */
41
void createMap(Thread t, T firstValue) {
42
    t.threadLocals = new ThreadLocalMap(this, firstValue);
43
}

ThreadLocal.get()

1
public T get() {
2
    Thread t = Thread.currentThread();
3
    ThreadLocalMap map = getMap(t);
4
    if (map != null) {
5
        ThreadLocalMap.Entry e = map.getEntry(this);
6
        if (e != null) {
7
            @SuppressWarnings("unchecked")
8
            T result = (T)e.value;
9
            return result;
10
        }
11
    }
12
    return setInitialValue();
13
}

小结

ThreadLocal 和 Synchronized 都是为了解决多线程中相同变量的访问冲突问题,不同的是:

  • Synchronized 是通过线程等待,牺牲时间来解决访问冲突
  • ThreadLocal 是通过每个线程单独一份存储空间,牺牲空间来解决冲突,并且相比于Synchronized,ThreadLocal 具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问到想要的值。

正因为 ThreadLocal 的线程隔离特性,使他的应用场景相对来说更为特殊一些。

当某些数据是 以线程为作用域并且不同线程具有不同的数据副本 的时候,就可以考虑采用ThreadLocal。

比如:在多数据源切换时,就使用了 ThreadLocal 来设置当前使用的数据源

Thread 变量 threadLocals

线程类Thread内部有一个名为threadLocalsThreadLocal.ThreadLocalMap 变量,它定义如下:

1
/* ThreadLocal values pertaining to this thread. This map is maintained
2
     * by the ThreadLocal class. */
3
    ThreadLocal.ThreadLocalMap threadLocals = null;

ThreadLocal.ThreadLocalMap

ThreadLocal.ThreadLocalMapThreadLocal 的内部类

1
/**
2
 * ThreadLocalMap is a customized hash map suitable only for
3
 * maintaining thread local values. No operations are exported
4
 * outside of the ThreadLocal class. The class is package private to
5
 * allow declaration of fields in class Thread.  To help deal with
6
 * very large and long-lived usages, the hash table entries use
7
 * WeakReferences for keys. However, since reference queues are not
8
 * used, stale entries are guaranteed to be removed only when
9
 * the table starts running out of space.
10
 */
11
static class ThreadLocalMap {
12
    // ...
13
}

看注释的第一句话 ThreadLocalMap is a customized hash map suitable only for maintaining thread local values. 意思是 ThreadLocalMap 是一个自定义 HashMap,只适用于线程局部变量。完全可以把它看做是一个 HashMap。

ThreadLocal

1
/**
2
 * This class provides thread-local variables.  These variables differ from
3
 * their normal counterparts in that each thread that accesses one (via its
4
 * {@code get} or {@code set} method) has its own, independently initialized
5
 * copy of the variable.  {@code ThreadLocal} instances are typically private
6
 * static fields in classes that wish to associate state with a thread (e.g.,
7
 * a user ID or Transaction ID).
8
 *
9
 * <p>For example, the class below generates unique identifiers local to each
10
 * thread.
11
 * A thread's id is assigned the first time it invokes {@code ThreadId.get()}
12
 * and remains unchanged on subsequent calls.
13
 * 
14
 * ...
15
 *
16
 * @author  Josh Bloch and Doug Lea
17
 * @since   1.2
18
 */
19
public class ThreadLocal<T> {
20
    //...
21
}

大致意思就是,ThreadLocal 提供线程局部的变量,这些变量在每个线程中都有不同的副本,通过get和set方法就可以得到或设置它们。

ThreadLocal的结构如下:
类`ThreadLocal`的结构

重要的方法只有两个:set 和 get

ThreadLocal.set(value)

ThreadLocal.set(value)Thread 内部的 threadLocals 变量添加值,它的实现如下:

1
// line 189
2
/**
3
 * Sets the current thread's copy of this thread-local variable
4
 * to the specified value.  Most subclasses will have no need to
5
 * override this method, relying solely on the {@link #initialValue}
6
 * method to set the values of thread-locals.
7
 *
8
 * @param value the value to be stored in the current thread's copy of
9
 *        this thread-local.
10
 */
11
public void set(T value) {
12
    Thread t = Thread.currentThread();
13
    // 获取线程内部 ThreadLocalMap 对象
14
    ThreadLocalMap map = getMap(t);
15
    if (map != null)
16
        map.set(this, value);
17
    else// 给线程threadLocals变量new一个ThreadLocalMap对象,并添加第一个键值对
18
        createMap(t, value);
19
}
20
......
21
// line 225
22
/**
23
 * Get the map associated with a ThreadLocal. Overridden in
24
 * InheritableThreadLocal.
25
 *
26
 * @param  t the current thread
27
 * @return the map
28
 */
29
ThreadLocalMap getMap(Thread t) {
30
    return t.threadLocals;
31
}
32
33
/**
34
 * Create the map associated with a ThreadLocal. Overridden in
35
 * InheritableThreadLocal.
36
 *
37
 * @param t the current thread
38
 * @param firstValue value for the initial entry of the map
39
 */
40
void createMap(Thread t, T firstValue) {
41
    t.threadLocals = new ThreadLocalMap(this, firstValue);
42
}

ThreadLocal.get()

1
public T get() {
2
    Thread t = Thread.currentThread();
3
    ThreadLocalMap map = getMap(t);
4
    if (map != null) {
5
        ThreadLocalMap.Entry e = map.getEntry(this);
6
        if (e != null) {
7
            @SuppressWarnings("unchecked")
8
            T result = (T)e.value;
9
            return result;
10
        }
11
    }
12
    return setInitialValue();
13
}

小结

ThreadLocalSynchronized都是为了解决多线程中相同变量的访问冲突问题,不同的是:

  • Synchronized 是通过线程等待,牺牲时间来解决访问冲突
  • ThreadLocal 是通过每个线程单独一份存储空间,牺牲空间来解决冲突,并且相比于SynchronizedThreadLocal 具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问到想要的值。

正因为ThreadLocal的线程隔离特性,使他的应用场景相对来说更为特殊一些。在android中Looper、ActivityThread以及AMS中都用到了ThreadLocal。当某些数据是以线程为作用域并且不同线程具有不同的数据副本的时候,就可以考虑采用ThreadLocal。