Spring + Shiro 线程复用时session获取问题

项目上使用Shiro框架,在使用多线程进行业务处理的时候,发现用shiro获取到的session不对

源码追踪,查找原因

Shiro获取session

Session session = SecurityUtils.getSubject().getSession()

然后我们往下看 SecurityUtilsgetSubject() 方法

    /**
     * Returns the currently accessible {@code Subject} available to the calling code depending on
     * runtime environment.
     * 

* 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 always 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是怎么存的:

  1. ThreadContext 的subject获取,可以看到用户信息其实是存在 InheritableThreadLocalMap 对象中的,而 InheritableThreadLocalMap 继承了 InheritableThreadLocal 对象。
  2. InheritableThreadLocal 对象会在创建子线程时,将其在父线程中的值复制到子线程中去
  3. 线程池默认会在第一次使用线程时,根据最小线程数创建出线程
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 null.  It is merely a convenient wrapper
     * for the following:
     * 

* return (Subject)get( SUBJECT_KEY ); *

* 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 null if there isn't one bound. * @since 0.2 */ public static Subject getSubject() { return (Subject) get(SUBJECT_KEY); } /** * Returns the object for the specified key that is bound to * the current thread. * * @param key the key that identifies the value to return * @return the object keyed by key or null if * no value exists for the specified key */ 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 * * user-reported issue. * @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。

你可能感兴趣的:(并发编程)