相信大家都使用过 Jwt ,在其生成的 token 中存储用户信息。在 aop 或者 拦截器 中将对用户的授权token进行认证,验证成功之后将解析出来的用户信息保存在线程的上下文中,以便于在之后的操作中能够随时使用。那么如何保存该值呢?在使用中有遇什么样的问题?你是如何解决的。接下来我讲讲我的做法。
起初我是想通过 request.setAttribute()
完成信息的暂存的 ,的确这种方法也可行。但是如果方法中存在调用异步方法的情况,那么这种方式就会存在问题。此时可以通过传递参数的方式去解决。但是有没有更加优雅的方式呢?于是我继续寻找其它方法:通过了 ThreadLocal 的方式去存储。由于此类的副本数据不会在线程池中传递。于是我使用到了阿里的 TransmittableThreadLocal 去解决线程池无法传递线程本地副本的问题。但在使用的过程中发现了问题,线程池虽传递了本地副本,但是在下一次请求的时候还是保留了旧值。这个结果是由于线程复用导致的,于是我上网查找了相关资料。找到了在创建线程池的时候使用装饰器封装线程,以防止线程池只会在线程初始化的时候将父线程的值拷贝到子线程,也就是一直保留旧值的原因。executor.setTaskDecorator(TtlRunnable::get);
由于想知道TtlRunnable是如何解决问题的,于是查看了它的源码。
- TtlRunnable::get 将 Runnable 参数传递到 TtlRunnable 的构造方法去创建 TtlRunnable 实例
@Nullable
public static TtlRunnable get(@Nullable Runnable runnable) {
return get(runnable, false, false);
}
public static TtlRunnable get(@Nullable Runnable runnable, boolean releaseTtlValueReferenceAfterRun, boolean idempotent) {
...
return new TtlRunnable(runnable, releaseTtlValueReferenceAfterRun);
}
private TtlRunnable(@NonNull Runnable runnable, boolean releaseTtlValueReferenceAfterRun) {
// capturedRef:拷贝副本的引用
this.capturedRef = new AtomicReference<Object>(capture());
// 执行逻辑对象
this.runnable = runnable;
this.releaseTtlValueReferenceAfterRun = releaseTtlValueReferenceAfterRun;
}
- 进入 capture() 方法 - 拷贝副本引用
public static Object capture() {
// 生成快照
return new Snapshot(captureTtlValues(), captureThreadLocalValues());
}
// 拷贝 ttl 值
private static HashMap<TransmittableThreadLocal<Object>, Object> captureTtlValues() {
HashMap<TransmittableThreadLocal<Object>, Object> ttl2Value = new HashMap<TransmittableThreadLocal<Object>, Object>();
for (TransmittableThreadLocal<Object> threadLocal : holder.get().keySet()) {
ttl2Value.put(threadLocal, threadLocal.copyValue());
}
return ttl2Value;
}
// 拷贝 threadLocal 值
private static HashMap<ThreadLocal<Object>, Object> captureThreadLocalValues() {
final HashMap<ThreadLocal<Object>, Object> threadLocal2Value = new HashMap<ThreadLocal<Object>, Object>();
for (Map.Entry<ThreadLocal<Object>, TtlCopier<Object>> entry : threadLocalHolder.entrySet()) {
final ThreadLocal<Object> threadLocal = entry.getKey();
final TtlCopier<Object> copier = entry.getValue();
threadLocal2Value.put(threadLocal, copier.copy(threadLocal.get()));
}
return threadLocal2Value;
}
- 执行任务
public void run() {
// 获取上一步的快照,主线程传递下来的ThreadLocal的值
final Object captured = capturedRef.get();
if (captured == null || releaseTtlValueReferenceAfterRun && !capturedRef.compareAndSet(captured, null)) {
throw new IllegalStateException("TTL value reference is released after run!");
}
// 在执行任务之前,将 captured 的 ThreadLocal值设置到当前任务线程,返回初始化时的备份值 *
final Object backup = replay(captured);
try {
runnable.run();
} finally {
// 回滚为初始化备份值
restore(backup);
}
}
以上是我的做法,你的做法是怎样的呢?或者我的方法有没有不足的地方?不吝赐教。