shiro中获取当前user出错

原地址:https://blog.csdn.net/ITbasketplayer/article/details/70813844

 

准确场景描述应该是: 
1、在应用层使用“线程池等会缓存线程的组件”,比如Executors.newFixedThreadPool(n);在线程里进行 
getUser(); 
2、可能A用户获取到B用户。 
下面分析一下原理: 
顺着shiro源码去找,获取当前用户方法SecurityUtils.getSubject();

/*ThreadContext线程上下文环境,主要靠InheritableThreadLocal保存线程变量;这里使用InheritableThreadLocal而不是普通的ThreadLocal,保证在servlet分配的每个线程,获取
到父线程的ThreadLocal变量,因为servlet分配线程也是用了线程池的*/
public static Subject getSubject() {
        Subject subject = ThreadContext.getSubject();
        if (subject == null) {
            subject = (new Subject.Builder()).buildSubject();
            //获取不到,直接bind,调用ThreadContext put方法
            ThreadContext.bind(subject);
        }
        return subject;
    }

首先聊一下ThreadLocal的作用,ThreadLocal使得各线程能够保持各自独立的一个对象,并不是通过ThreadLocal.set()来实现的,而是通过每个线程中的new 对象 的操作来创建的对象,每个线程创建一个,不是什么对象的拷贝或副本。通过ThreadLocal.set()将这个新创建的对象的引用保存到各线程的自己的一个map中,每个线程都有这样一个map,执行ThreadLocal.get()时,各线程从自己的map中取出放进去的对象,因此取出来的是各自自己线程中的对象,ThreadLocal实例是作为map的key来使用的。

如果ThreadLocal.set()进去的东西本来就是多个线程共享的同一个对象,那么多个线程的ThreadLocal.get()取得的还是这个共享对象本身,还是有并发访问问题。

接下来继续看代码,进getSubject(),再进get();

public static Subject getSubject() {
        return (Subject) get(SUBJECT_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;
    }
//resources是private static final ThreadLocal<Map<Object, Object>> resources = new InheritableThreadLocalMap<Map<Object, Object>>();
private static Object getValue(Object key) {
        return resources.get().get(key);
    }

//getValue和put都是操作resources而已
public static void put(Object key, Object value) {
        if (key == null) {
            throw new IllegalArgumentException("key cannot be null");
        }

        if (value == null) {
            remove(key);
            return;
        }

        resources.get().put(key, value);

        if (log.isTraceEnabled()) {
            String msg = "Bound value of type [" + value.getClass().getName() + "] for key [" +
                    key + "] to thread " + "[" + Thread.currentThread().getName() + "]";
            log.trace(msg);
        }
    }

造成这个问题的原因是什么呢?如何让任务之间使用缓存的线程不受影响呢?实际原因是,我们的线程在执行完毕的时候并没有清除ThreadLocal中的值,导致后面的任务重用现在的localMap。

解决办法由2个 
1、在程序中使用多线程的情况下,子线程无法获取上层线程的shiro相关变量,当前用户需要在外层设置进去,比如我的程序里是:user作为子线程成员变量,在子线程中不再执行shiro的getUser()获取当前用户 
disPatchCompletionService.submit(new DisPatchCallable(crmCase, assign, user, dispatchType)); 
2、如果我们能够,在使用完这个线程的时候清除所有的localMap,在submit新任务的时候在重新重父线程中copy所有的Entry。然后重新给当前线程的t.inhertableThreadLocal赋值。这样就能够解决在线程池中每一个新的任务都能够获得父线程中ThreadLocal中的值而不受其他任务的影响,因为在生命周期完成的时候会自动clear所有的数据。Alibaba的一个库解决了这个问题github:alibaba/transmittable-thread-local

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值