项目上使用Shiro框架,在使用多线程进行业务处理的时候,发现用shiro获取到的session不对
源码追踪,查找原因
Shiro获取session
Session session = SecurityUtils.getSubject().getSession()
然后我们往下看 SecurityUtils
的 getSubject()
方法
/**
* Returns the currently accessible {@code Subject} available to the calling code depending on
* runtime environment.
* <p/>
* This method is provided as a way of obtaining a {@code Subject} without having to resort to
* implementation-specific methods. It also allows the Shiro team to change the underlying implementation of
* this method in the future depending on requirements/updates without affecting your code that uses it.
*
* @return the currently accessible {@code Subject} accessible to the calling code.
* @throws IllegalStateException if no {@link Subject Subject} instance or
* {@link SecurityManager SecurityManager} instance is available with which to obtain
* a {@code Subject}, which which is considered an invalid application configuration
* - a Subject should <em>always</em> be available to the caller.
*/
public static Subject getSubject() {
Subject subject = ThreadContext.getSubject();
if (subject == null) {
subject = (new Subject.Builder()).buildSubject();
ThreadContext.bind(subject);
}
return subject;
}
由上面的方法,我们可以看到subject是从 ThreadContext
中获取出来的,我们再来看下这个subject是怎么存的:
- 从
ThreadContext
的subject获取,可以看到用户信息其实是存在InheritableThreadLocalMap
对象中的,而InheritableThreadLocalMap
继承了InheritableThreadLocal
对象。 InheritableThreadLocal
对象会在创建子线程时,将其在父线程中的值复制到子线程中去- 线程池默认会在第一次使用线程时,根据最小线程数创建出线程
public abstract class ThreadContext {
/**
* Private internal log instance.
*/
private static final Logger log = LoggerFactory.getLogger(ThreadContext.class);
public static final String SECURITY_MANAGER_KEY = ThreadContext.class.getName() + "_SECURITY_MANAGER_KEY";
public static final String SUBJECT_KEY = ThreadContext.class.getName() + "_SUBJECT_KEY";
private static final ThreadLocal<Map<Object, Object>> resources = new InheritableThreadLocalMap<Map<Object, Object>>();
/**
* 中间去掉一大波代码,只留下 getSubject 的追踪链
*/
/**
* Convenience method that simplifies retrieval of a thread-bound Subject. If there is no
* Subject bound to the thread, this method returns <tt>null</tt>. It is merely a convenient wrapper
* for the following:
* <p/>
* <code>return (Subject)get( SUBJECT_KEY );</code>
* <p/>
* This method only returns the bound value if it exists - it does not remove it
* from the thread. To remove it, one must call {@link #unbindSubject() unbindSubject()} instead.
*
* @return the Subject object bound to the thread, or <tt>null</tt> if there isn't one bound.
* @since 0.2
*/
public static Subject getSubject() {
return (Subject) get(SUBJECT_KEY);
}
/**
* Returns the object for the specified <code>key</code> that is bound to
* the current thread.
*
* @param key the key that identifies the value to return
* @return the object keyed by <code>key</code> or <code>null</code> if
* no value exists for the specified <code>key</code>
*/
public static Object get(Object key) {
if (log.isTraceEnabled()) {
String msg = "get() - in thread [" + Thread.currentThread().getName() + "]";
log.trace(msg);
}
Object value = getValue(key);
if ((value != null) && log.isTraceEnabled()) {
String msg = "Retrieved value of type [" + value.getClass().getName() + "] for key [" +
key + "] " + "bound to thread [" + Thread.currentThread().getName() + "]";
log.trace(msg);
}
return value;
}
/**
* Returns the value bound in the {@code ThreadContext} under the specified {@code key}, or {@code null} if there
* is no value for that {@code key}.
*
* @param key the map key to use to lookup the value
* @return the value bound in the {@code ThreadContext} under the specified {@code key}, or {@code null} if there
* is no value for that {@code key}.
* @since 1.0
*/
private static Object getValue(Object key) {
Map<Object, Object> perThreadResources = resources.get();
return perThreadResources != null ? perThreadResources.get(key) : null;
}
private static final class InheritableThreadLocalMap<T extends Map<Object, Object>> extends InheritableThreadLocal<Map<Object, Object>> {
/**
* This implementation was added to address a
* <a href="http://jsecurity.markmail.org/search/?q=#query:+page:1+mid:xqi2yxurwmrpqrvj+state:results">
* user-reported issue</a>.
* @param parentValue the parent value, a HashMap as defined in the {@link #initialValue()} method.
* @return the HashMap to be used by any parent-spawned child threads (a clone of the parent HashMap).
*/
@SuppressWarnings({"unchecked"})
protected Map<Object, Object> childValue(Map<Object, Object> parentValue) {
if (parentValue != null) {
return (Map<Object, Object>) ((HashMap<Object, Object>) parentValue).clone();
} else {
return null;
}
}
}
}
结论总结
也就是说,子线程的session一直都是他的创建者的session,假如第一个用户A使用线程池,创建出10个线程,后续用户B、C、D,如果直接复用线程,拿到的都是用户A的session。