微服务中使用阿里开源的TTL,优雅的实现身份信息的线程间复用二
简单应用
在 Spring Security 往往需要存储用户登录的详细信息,这样在业务方法中能够随时获取用户的信息。
在前面的Spring Cloud Gateway整合OAuth2.0实现统一认证鉴权 文章中笔者是将用户信息直接存储在Request中,这样每次请求都能获取到对应的信息。
其实Request中的信息存储也是通过ThreadLocal完成的,在异步执行的时候还是需要重新转存,这样一来代码就变得复杂。
那么了解了TransmittableThreadLocal 之后,完全可以使用这个存储用户的登录信息,实现如下:
* @author 公众号:码猿技术专栏 * @url: www.java-family.cn * @description 使用TransmittableThreadLocal存储用户身份信息LoginVal public class SecurityContextHolder { //使用TTL存储身份信息 private static final TransmittableThreadLocal<LoginVal> THREAD_LOCAL = new TransmittableThreadLocal<>(); public static void set(LoginVal loginVal){ THREAD_LOCAL.set(loginVal); public static LoginVal get(){ return THREAD_LOCAL.get(); public static void remove(){ THREAD_LOCAL.remove();由于mvc中的一次请求对应一个线程,因此只需要在拦截器中的设置和移除TransmittableThreadLocal中的信息,代码如下:
* @author 公众号:码猿技术专栏 * @url: www.java-family.cn * @description 拦截器,在preHandle中解析请求头的中的token信息,将其放入SecurityContextHolder中 * 在afterCompletion方法中移除对应的ThreadLocal中信息 * 确保每个请求的用户信息独立 @Component public class AuthInterceptor implements AsyncHandlerInterceptor { * 在执行controller方法之前将请求头中的token信息解析出来,放入SecurityContextHolder中(TransmittableThreadLocal) @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { if (!(handler instanceof HandlerMethod)) return true; //获取请求头中的加密的用户信息 String token = request.getHeader(OAuthConstant.TOKEN_NAME); if (StrUtil.isBlank(token)) return true; String json = Base64.decodeStr(token); //将json解析成LoginVal LoginVal loginVal = TokenUtils.parseJsonToLoginVal(json); //封装数据到ThreadLocal中 SecurityContextHolder.set(loginVal); return true; * 在视图渲染之后执行,意味着一次请求结束,清除TTL中的身份信息 @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex){ SecurityContextHolder.remove();
原理
从定义来看,TransimittableThreadLocal继承于InheritableThreadLocal,并实现TtlCopier接口,它里面只有一个copy方法。所以主要是对InheritableThreadLocal的扩展。
public class TransmittableThreadLocal<T> extends InheritableThreadLocal<T> implements TtlCopier<T>
在TransimittableThreadLocal中添加holder属性。这个属性的作用就是被标记为具备线程传递资格的对象都会被添加到这个对象中。
要标记一个类,比较容易想到的方式,就是给这个类新增一个Type字段,还有一个方法就是将具备这种类型的的对象都添加到一个静态全局集合中。之后使用时,这个集合里的所有值都具备这个标记。
// 1. holder本身是一个InheritableThreadLocal对象
// 2. 这个holder对象的value是WeakHashMap<TransmittableThreadLocal<Object>, ?>
// 2.1 WeekHashMap的value总是null,且不可能被使用。
// 2.2 WeekHasshMap支持value=null
private static InheritableThreadLocal<WeakHashMap<TransmittableThreadLocal<Object>, ?>> holder = new InheritableThreadLocal<WeakHashMap<TransmittableThreadLocal<Object>, ?>>() {
@Override
protected WeakHashMap<TransmittableThreadLocal<Object>, ?> initialValue() {
return new WeakHashMap<TransmittableThreadLocal<Object>, Object>();
* 重写了childValue方法,实现上直接将父线程的属性作为子线程的本地变量对象。
@Override
protected WeakHashMap<TransmittableThreadLocal<Object>, ?> childValue(WeakHashMap<TransmittableThreadLocal<Object>, ?> parentValue) {
return new WeakHashMap<TransmittableThreadLocal<Object>, Object>(parentValue);
应用代码是通过TtlExecutors工具类对线程池对象进行包装。工具类只是简单的判断,输入的线程池是否已经被包装过、非空校验等,然后返回包装类ExecutorServiceTtlWrapper。根据不同的线程池类型,有不同和的包装类。
@Nullable
public static ExecutorService getTtlExecutorService(@Nullable ExecutorService executorService) {
if (TtlAgent.isTtlAgentLoaded() || executorService == null || executorService instanceof TtlEnhanced) {
return executorService;
return new ExecutorServiceTtlWrapper(executorService);
进入包装类ExecutorServiceTtlWrapper。可以注意到不论是通过ExecutorServiceTtlWrapper#submit方法或者是ExecutorTtlWrapper#execute方法,都会将线程对象包装成TtlCallable或者TtlRunnable,用于在真正执行run方法前做一些业务逻辑。
* 在ExecutorServiceTtlWrapper实现submit方法
@NonNull
@Override
public <T> Future<T> submit(@NonNull Callable<T> task) {
return executorService.submit(TtlCallable.get(task));
* 在ExecutorTtlWrapper实现execute方法
@Override
public void execute(@NonNull Runnable command) {
executor.execute(TtlRunnable.get(command));
文章转自公众号:码猿技术专栏
标签
已于2022-7-13 15:18:26修改
收藏
回复
删除帖子
回复
添加资源
添加资源将有机会获得更多曝光,你也可以直接关联已上传资源
去关联
添加资源
相关推荐
-
模板方法模式——看看 JDK 和 Spring 是如何
优雅
复用
代码
的
davisl
• 6963浏览
• 0回复
-
如何
使用
BLE
实现
设备
间
的
通信
奶盖
• 1.3w浏览
• 0回复
-
HarmonyOS AI语音播报、
线程
间
通信和计时器
的
使用
奶盖
• 8246浏览
• 1回复
-
Hi3516
的
SAMGR--系统
服务
框架子系统-7-
线程
/进程
间
通信模型
liangkz_梁开祝
• 1.1w浏览
• 4回复
-
用户程序框架之
线程
间
通信EventHandler
九重霄99
• 1.1w浏览
• 1回复
-
「SpringCloud」
微服务
数据权限
的
设计与
实现
ponymar
• 1.1w浏览
• 0回复
-
微服务
中使用
阿里
开源
的
TTL
,
优雅
的
实现身份
信息
的
线程
间
复用
一
love374
• 2403浏览
• 0回复
-
微服务
中使用
阿里
开源
的
TTL
,
优雅
的
实现身份
信息
的
线程
间
复用
三
love374
• 2669浏览
• 0回复
-
3 个注解,
优雅
的
实现
微服务
鉴权(一)
love374
• 2738浏览
• 0回复
-
3 个注解,
优雅
的
实现
微服务
鉴权(
二
)
love374
• 1626浏览
• 0回复
-
3 个注解,
优雅
的
实现
微服务
鉴权(三)
love374
• 1822浏览
• 0回复
-
图解设计模式:
身份
认证场景
的
应用(
二
)
Handpc
• 1689浏览
• 0回复
-
#夏日挑战赛# 基于OpenHarmony
的
阿里
云IoT
服务
实现
福州市凌睿智捷电子有限公司
• 5999浏览
• 0回复
-
基于OpenHarmony
的
阿里
云IoT
服务
实现
福州市凌睿智捷电子有限公司
• 3423浏览
• 0回复
-
如何
使用
Sentinel 保护您
的
微服务
(上)
大家好我是佩奇
• 1676浏览
• 0回复
-
如何
使用
Sentinel 保护您
的
微服务
(下)
大家好我是佩奇
• 1557浏览
• 0回复
-
微服务
架构:Nacos本地缓存 PK
微服务
优雅
下线
梦叶草789
• 1980浏览
• 0回复
-
Go
微服务
实战之如何
使用
go-micro 写
微服务
应用
宇宙之一粟
• 5345浏览
• 4回复
-
使用
Guava-Retry
优雅
的
实现
重处理
Handpc
• 443浏览
• 0回复
帖子
视频
声望
粉丝
最近发布
-
探索 Elasticsearch 8.X Terms Set 检索的应用与原理
2023-05-19 17:37:10发布
-
干货 | Elasticsearch 8.X 性能优化实战
2023-05-19 17:36:18发布
相关问题