TransmittableThreadLocal实现上下文传递-笔记

1.TransmittableThreadLocal简介

com.alibaba.ttl.TransmittableThreadLocal(简称 TTL)是阿里巴巴开源的一个工具类,旨在解决 ThreadLocal 在线程池中无法传递上下文变量 的问题。它是对 InheritableThreadLocal 的增强,尤其适用于异步编程、线程池任务调度等场景。


1.1 核心功能

  1. 上下文传递:在普通线程池中,ThreadLocal 的值不会自动传递到新线程。TransmittableThreadLocal 通过 任务包装机制,将主线程的上下文变量传递到子线程(如线程池中的线程)中。

  2. 兼容性:它兼容 ThreadLocal 和 InheritableThreadLocal 的行为,同时支持 Java 8+ 的异步编程模型(如 CompletableFutureForkJoinPool 等)。

  3. 生命周期管理:支持在任务执行前后自动绑定/解绑上下文,避免内存泄漏。


1.2 典型使用场景

  • 分布式追踪(如 SkyWalking、Zipkin):在异步调用链中传递 Trace ID。
  • 日志上下文(如 MDC):在异步任务中保留请求标识(如用户 ID、请求 ID)。
  • 事务传播:在线程池中传递事务上下文(如 Seata 分布式事务)。
  • 权限验证:在异步任务中传递用户身份信息。

2.TransmittableThreadLocal基本用法

step1.引入依赖

<!-- Maven -->
<dependency><groupId>com.alibaba</groupId><artifactId>transmittable-thread-local</artifactId><version>2.12.1</version> <!-- 使用最新版本 -->
</dependency>

step2. 定义 TransmittableThreadLocal

import com.alibaba.ttl.TransmittableThreadLocal;public class Context {private static final TransmittableThreadLocal<String> CONTEXT = new TransmittableThreadLocal<>();public static void set(String value) {CONTEXT.set(value);}public static String get() {return CONTEXT.get();}
}

step3. 各种用法

import com.alibaba.ttl.TtlRunnable;ExecutorService executor = Executors.newFixedThreadPool(2);
// 主线程设置上下文
Context.set("Hello TTL");// 提交任务前包装 Runnable
// TtlRunnable 是 Runnable 的任务包装器
executor.submit(TtlRunnable.get(() -> {System.out.println("子线程: " + Context.get());
}, true)); // 第二个参数表示是否复制上下文//使用 TtlCallable 包装 Future 任务
// TtlCallable 是 Callable 的任务包装器
Future<String> future = executor.submit(TtlCallable.get(() -> {return "Result with context: " + Context.get();
}));//使用于 CompletableFuture
CompletableFuture.runAsync(TtlRunnable.get(() -> {System.out.println("CompletableFuture: " + Context.get());
}, true), executor);//如果使用自定义线程池,建议通过 TtlExecutors 包装:
Executor ttlExecutor = TtlExecutors.getTtlExecutorService(Executors.newFixedThreadPool(5));

关键特性

1. 自动传播:如果线程池本身是 TTL 兼容的(如使用 TtlExecutors 包装),则无需手动包装任务。

2. 线程池改造:如果使用自定义线程池,建议通过 TtlExecutors 包装:

ExecutorService ttlExecutor = TtlExecutors.getTtlExecutorService(Executors.newFixedThreadPool(2));

3. 清除机制:需要显式调用 remove() 避免内存泄漏:

try {Context.set("value");// 执行任务
} finally {Context.remove();
}

3. TransmittableThreadLocal实现上下文传递-完整demo

3.1 场景说明:

有一个“查询用户成绩”场景,需要将用户上下文(如用户ID、租户信息、AccessKey、Locale)在 Controller、Service 层甚至异步调用中传递。

3.2 实现目标

  • 用户请求进入系统后,从请求头中提取上下文信息。
  • 上下文信息在整个请求链路中自动传递,包括异步调用。
  • 上下文信息不会被多个请求交叉污染。
  • 上下文信息在请求结束后自动清理。

3.3 代码

step1. 定义用户上下文(UserContext)

import com.alibaba.ttl.TransmittableThreadLocal;
import lombok.Builder;
import lombok.Data;@Data
@Builder
public class UserContext {private static final TransmittableThreadLocal<UserContext> USER_CONTEXT= new TransmittableThreadLocal<UserContext>(){@Overrideprotected UserContext initialValue() {return new UserContext();}};/*** 用户ID*/private String userId;/*** 租户ID*/private String tenantId;/*** 访问密钥*/private String accessKey;/*** 语言*/private String locale;public UserContext() {}public UserContext(String userId, String tenantId, String accessKey, String locale) {this.userId = userId;this.tenantId = tenantId;this.accessKey = accessKey;this.locale = locale;}public static UserContext get() {return USER_CONTEXT.get();}public static void set(UserContext userContext) {USER_CONTEXT.set(userContext);}public static void remove() {USER_CONTEXT.remove();}}

step2. 配置拦截器(注入上下文UserContext)

定义一个UserContextInterceptor拦截器,用于从request Header中获取用户信息:

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;public class UserContextInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {// 从请求头中提取用户信息(示例)String userId = request.getHeader("Custom-User-Id");String tenantId = request.getHeader("Custom-Tenant-Id");String accessKey = request.getHeader("Custom-Access-Key");String locale = request.getHeader("Custom-Accept-Language");// 设置用户信息UserContext userContext = UserContext.builder().userId(userId).tenantId(tenantId).accessKey(accessKey).locale(locale).build();UserContext.set(userContext);return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)throws Exception {// 务必在请求结束时清理上下文,避免内存泄漏UserContext.remove();}
}

注册UserContextInterceptor拦截器:

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Configuration
public class UserContextWebConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new UserContextInterceptor()).addPathPatterns("/**");}
}

step3.在 Service 层使用上下文(UserContext)

import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import com.alibaba.ttl.threadpool.TtlExecutors;
import org.springframework.stereotype.Service;@Service
public class ScoreService {//配置异步线程池(使用 TtlExecutors),用于测试多线程场景下,子线程也能获取到主线程的上下文信息private Executor taskExecutor = TtlExecutors.getTtlExecutorService(Executors.newFixedThreadPool(5));public void queryScore() {String userId = UserContext.get().getUserId();String tenantId = UserContext.get().getTenantId();String accessKey = UserContext.get().getAccessKey();String locale = UserContext.get().getLocale();System.out.println("Sync start ====== ");System.out.println("current thead: " + Thread.currentThread().getName());System.out.println("Sync: Querying score for user: " + userId + ", tenant: " + tenantId +" accessKey: " + accessKey + ", locale: " + locale);}//多线程场景下,子线程也能获取到主线程的上下文信息public void queryScoreAsync() {taskExecutor.execute(() -> {String userId = UserContext.get().getUserId();String tenantId = UserContext.get().getTenantId();String accessKey = UserContext.get().getAccessKey();String locale = UserContext.get().getLocale();System.out.println("ASync start ====== ");System.out.println("current thead: " + Thread.currentThread().getName());System.out.println("Async: Querying score for user: " + userId + ", tenant: " + tenantId +" accessKey: " + accessKey + ", locale: " + locale);});}
}

step4. Controller层

@RestController
public class ScoreController {@Autowiredprivate ScoreService scoreService;@GetMapping("ScoreController/queryScore")public String queryScore() {scoreService.queryScore();scoreService.queryScoreAsync();return "ScoreController queryScore success!";}@GetMapping("ScoreController/hello")public String hello() {return "ScoreController say hello";}
}

step5.测试

启动应用后,在postman中做测试。

case1: 不设置Header,UserContext获取的值为null:

case2: 设置Header,UserContext获取的值为设置的值:

case3:使用 ThreadLocal 做对比测试,新建的子线程中无法获取到UserContext设置的值

将UserContext.USER_CONTEXT设置为 ThreadLocal:

@Data
@Builder
public class UserContext {//private static final TransmittableThreadLocal<UserContext> USER_CONTEXT//    = new TransmittableThreadLocal<UserContext>(){//    @Override//    protected UserContext initialValue() {//        return new UserContext();//    }//};// 改用 ThreadLocal,其他代码不变private static final ThreadLocal<UserContext> USER_CONTEXT = new ThreadLocal<UserContext>(){@Overrideprotected UserContext initialValue() {return new UserContext();}};
}

设置Header后,发送请求,新建的子线程中无法获取到UserContext设置的值:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/80645.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

TDengine 安全部署配置建议

背景 TDengine 的分布式、多组件特性导致 TDengine 的安全配置是生产系统中比较关注的问题。本文档旨在对 TDengine 各组件及在不同部署方式下的安全问题进行说明&#xff0c;并提供部署和配置建议&#xff0c;为用户的数据安全提供支持。 安全配置涉及组件 TDengine 包含多…

在Cursor中启用WebStorm/IntelliJ风格快捷键

在Cursor中启用WebStorm/IntelliJ风格快捷键 方法一&#xff1a;使用预置快捷键方案 打开快捷键设置 Windows/Linux: Ctrl K → Ctrl SmacOS: ⌘ K → ⌘ S 搜索预设方案 在搜索框中输入keyboard shortcuts&#xff0c;选择Preferences: Open Keyboard Shortcuts (JSON) …

python打卡day30@浙大疏锦行

知识点回顾&#xff1a; 导入官方库的三种手段导入自定义库/模块的方式导入库/模块的核心逻辑&#xff1a;找到根目录&#xff08;python解释器的目录和终端的目录不一致&#xff09; 作业&#xff1a;自己新建几个不同路径文件尝试下如何导入 具体操作步骤&#xff1a; 在桌面…

【kafka】基本命令

创建 Kafka Topic 的命令 以下是创建 Kafka Topic 的几种常用方法&#xff1a; 1. 使用 kafka-topics.sh 基础命令&#xff08;Kafka 自带工具&#xff09; bin/kafka-topics.sh --create \--bootstrap-server <broker地址:端口> \--topic <topic名称> \--parti…

编程速递:适用于 Delphi 12.3 的 FMX Linux 现已推出

Embarcadero非常高兴地宣布&#xff0c;用于使用Delphi构建Linux客户端应用程序的FMX Linux UI库再次在RAD Studio 12.3版本以及RAD Studio 12.2版本中提供支持&#xff0c;同时也适用于更早的版本。 作为RAD Studio的一个附加库&#xff0c;FMX Linux为开发面向Linux的图形用…

通过实例讲解螺旋模型

目录 一、螺旋模型的核心概念 二、螺旋模型在电子商城系统开发中的应用示例 第 1 次螺旋:项目启动与风险初探

vue3 vite 路由

如路由是这种格式 http://localhost:7058/admin/product/brand路由配置如下 import { createRouter, createWebHistory } from vue-router import HomeView from ../views/HomeView.vue import NProgress from nprogress; import nprogress/nprogress.css; import {errorRour…

【Redis】Hash 存储相比 String 存储的优势

在 Redis 中&#xff0c;Hash 存储相比 String 存储具有以下 优势&#xff0c;特别适用于某些特定场景&#xff1a; ✅ 1. 更节省内存&#xff08;尤其适合存储对象&#xff09; Hash 内部使用压缩列表&#xff08;ziplist&#xff09;或哈希表实现&#xff0c;在数据量较小时…

CSS详解:特性、选择器与优先级

CSS详解&#xff1a;特性、选择器与优先级 目录 CSS详解&#xff1a;特性、选择器与优先级一、CSS的核心特性1. 层叠性&#xff08;Cascading&#xff09;2. 继承性&#xff08;Inheritance&#xff09;3. 优先级&#xff08;Specificity&#xff09;4. 响应式设计5. 动画与过渡…

《算法导论(第4版)》阅读笔记:p86-p90

《算法导论(第4版)》学习第 19 天&#xff0c;p83-p85 总结&#xff0c;总计 3 页。 一、技术总结 无。 二、英语总结(生词&#xff1a;2) 1. inkling (1)inkling: inclen(“utter in an undertone&#xff0c;低声说话”) c. a hint(提示)&#xff1b;a slight knowledg…

nginx概念及使用

一、Nginx 核心概念 Nginx&#xff08;发音为 "engine-x"&#xff09;是一个高性能、开源的 Web 服务器和反向代理服务器&#xff0c;由俄罗斯工程师伊戈尔・赛索耶夫&#xff08;Igor Sysoev&#xff09;于 2004 年开发&#xff0c;最初用于解决当时高并发场景下 Ap…

2025蓝桥杯JAVA编程题练习Day8

1. 路径 题目描述 小蓝学习了最短路径之后特别高兴&#xff0c;他定义了一个特别的图&#xff0c;希望找到图 中的最短路径。 小蓝的图由 2021 个结点组成&#xff0c;依次编号 1 至 2021。 对于两个不同的结点 a, b&#xff0c;如果 a 和 b 的差的绝对值大于 21&#xff0…

【赵渝强老师】Memcached的路由算法

Memcached支持两种不同方式的客户端路由算法&#xff0c;即&#xff1a;求余数Hash算法和一致性Hash算法。下面分别进行介绍。 一、 求余数的路由算法 求余数Hash算法的客户端路由是对插入数据的键进行求余数&#xff0c;根据余数来决定存储到哪个Memcached实例。 视频讲解如…

NLP学习路线图(一): 线性代数(矩阵运算、特征值分解等)

引言&#xff1a;语言与矩阵的奇妙邂逅 在自然语言处理&#xff08;NLP&#xff09;的魔法世界里&#xff0c;每个词语都像被施了变形术的精灵&#xff0c;在数学的殿堂中翩翩起舞。当我们用"king - man woman queen"这样的向量魔法破解语义密码时&#xff0c;线性…

BUUCTF PWN刷题笔记(持续更新!!)

ciscn_2019_c_1 64位&#xff0c;没有开启保护。点进去没发现明显的漏洞函数&#xff0c;考虑泄露libc基地址的rop构造。先看看有多少gadget 估计也够用了。puts函数只接受一个参数&#xff0c;观看汇编看看用的哪个寄存器传输的参数。 用的是edi。但是我们怎么找到so的版本呢…

Java EE初阶——线程安全

1. 线程的状态 1. 线程状态分类&#xff08;Thread.State 枚举&#xff09; Java 定义了 6 种线程状态&#xff0c;这些状态均由 java.lang.Thread.State 枚举表示&#xff1a; NEW&#xff08;新建&#xff09; 线程对象已创建&#xff0c;但尚未调用 start() 方法。此时线程…

Vue 3.0中响应式依赖和更新

响应式依赖和更新是Vue 3.0中最重要的机制&#xff0c;其核心代码如下&#xff0c;本文将结合代码对这个设计机制作出一些解释。 // 全局依赖存储&#xff1a;WeakMap<target, Map<key, Set<effect>>> const targetMap new WeakMap();// 当前活动的副作用函…

一、内存调优

一、内存调优 什么是内存泄漏 监控Java内存的常用工具 内存泄露的常见场景 内存泄露的解决方案 内存泄露与内存溢出的区别 内存泄露&#xff1a;在Java中如果不再使用一个对象&#xff0c;但是该对象依然在GC ROOT的引用链上&#xff0c;这个对象就不会被垃圾回收器回收&…

Linux /etc/rc.d/init.d/

在传统的 SysV init 系统中&#xff0c;服务启动脚本通常位于 /etc/rc.d/init.d/ 目录下。这些脚本可以直接执行以启动、停止或重启服务&#xff0c;并且可以接受参数如 start, stop, status 等。 如果你想知道位于 /etc/rc.d/init.d/ 目录下的某个脚本文件实际上指向哪里,如果…

S7 200 smart连接Profinet转ModbusTCP网关与西门子1200PLC配置案例

控制要求&#xff1a;使用MODBUSTCP通信进行两台PLC之间的数据交换&#xff0c;由于改造现场不能改动程序&#xff0c;只留出了对应的IQ地址。于是客户决定使用网关进行通讯把数据传到plc。 1、读取服务器端40001~40005地址中的数据&#xff0c;放入到VW200~VW208中&#xff1…