ThreadLocal与ScopedValue对比讲解
1 ThreadLocal与ScopedValue
在工作中,一提到线程内数据传递就想到ThreadLocal,但真正用起来却遇到各种坑:内存泄漏、数据污染、性能问题等等。其实,ScopedValue就像ThreadLocal的升级版,既保留了优点,又解决了痛点。
点击了解 ThreadLocal,InheritableThreadLocal,TransmittableThreadLocal 用法
1.1 ThreadLocal缺点
在介绍ScopedValue之前,我们先回顾一下ThreadLocal的常见问题。其实,ThreadLocal 在设计上存在一些固有缺陷。
1.1.1 ThreadLocal 内存泄漏问题
为了更直观地理解ThreadLocal的内存泄漏问题,参考内存泄漏的示意图:

1.1.2 ThreadLocal 典型问题代码
/**
* ThreadLocal典型问题演示
*/
public class ThreadLocalProblems {
private static final ThreadLocal<UserContext> userContext = new ThreadLocal<>();
private static final ThreadLocal<Connection> connectionHolder = new ThreadLocal<>();
/**
* 问题1:内存泄漏 - 忘记调用remove()
*/
public void processRequest(HttpServletRequest request) {
// 设置用户上下文
UserContext context = new UserContext(request.getHeader("X-User-Id"));
userContext.set(context);
try {
// 业务处理
businessService.process();
// 问题:忘记调用 userContext.remove()
// 在线程池中,这个线程被重用时,还会保留之前的用户信息
} catch (Exception e) {
// 异常处理
}
}
/**
* 问题2:数据污染 - 线程复用导致数据混乱
*/
public void processMultipleRequests() {
// 线程池处理多个请求
ExecutorService executor = Executors.newFixedThreadPool(5);
for (int i = 0; i < 10; i++) {
finalint userId = i;
executor.submit(() -> {
// 设置用户上下文
userContext.set(new UserContext("user_" + userId));
try {
// 模拟业务处理
Thread.sleep(100);
// 问题:如果线程被复用,这里可能读取到错误的用户信息
String currentUser = userContext.get().getUserId();
System.out.println("处理用户: " + currentUser);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
// 即使调用remove,也可能因为异常跳过
userContext.remove(); // 不保证一定执行
}
});
}
executor.shutdown();
}
/**
* 问题3:继承性问题 - 子线程无法继承父线程数据
*/
public void parentChildThreadProblem() {
userContext.set(new UserContext("parent_user"));
Thread childThread = new Thread(() -> {
// 这里获取不到父线程的ThreadLocal值
UserContext context = userContext.get(); // null
System.out.println("子线程用户: " + context); // 输出null
// 需要手动传递数据
});
childThread.start();
}
/**
* 问题4:性能问题 - 大量ThreadLocal影响性能
*/
public void performanceProblem() {
long startTime = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
ThreadLocal<String> tl = new ThreadLocal<>();
tl.set("value_" + i);
String value = tl.get();
tl.remove();
}
long endTime = System.currentTimeMillis();
System.out.println("ThreadLocal操作耗时: " + (endTime - startTime) + "ms");
}
}
/**
* 用户上下文
*/
class UserContext {
private final String userId;
private final long timestamp;
//省去get 全参 toString
}
1.1.3 ThreadLocal问题根本原因
- 生命周期管理复杂:需要手动调用
set/remove,容易遗漏 - 内存泄漏风险:线程池中线程复用,
Value无法被GC - 继承性差:子线程无法自动继承父线程数据
- 性能开销:
ThreadLocalMap的哈希表操作有开销
1.2 ScopedValue 新一代线程局部变量
ScopedValue 是 Java 20中引入的预览特性,在Java 21中成为正式特性。它旨在解决ThreadLocal的痛点,提供更安全、更高效的线程内数据传递方案。
1.2.1 ScopedValue的核心设计理念
为了更直观地理解ScopedValue的工作原理,参考ScopedValue的架构图:

ScopedValue 的核心优势:

核心优势:
- 内存安全:自动生命周期管理,彻底解决内存泄漏
- 使用简单:结构化绑定,无需手动清理
- 性能优异:专为虚拟线程优化,性能更好
- 并发友好:完美支持结构化并发和虚拟线程
技术对比:
| 特性 | ThreadLocal | ScopedValue |
|---|---|---|
| 内存管理 | 手动remove | 自动管理 |
| 内存泄漏 | 高风险 | 无风险 |
| 使用复杂度 | 高(需要try-finally) | 低(结构化绑定) |
| 性能 | 较好 | 更优(虚拟线程) |
| 继承性 | 需要InheritableThreadLocal | 自动继承 |
| 虚拟线程支持 | 有问题 | 完美支持 |
1.2.2 ScopedValue基础用法
/**
* ScopedValue基础用法演示
*/
public class ScopedValueBasics {
// 1. 定义ScopedValue(相当于ThreadLocal)
private static final ScopedValue<UserContext> USER_CONTEXT = ScopedValue.newInstance();
private static final ScopedValue<Connection> DB_CONNECTION = ScopedValue.newInstance();
private static final ScopedValue<String> REQUEST_ID = ScopedValue.newInstance();
/**
* 基础用法:在作用域内使用ScopedValue
*/
public void basicUsage() {
UserContext user = new UserContext("user_123");
// 在作用域内绑定值
ScopedValue.runWhere(USER_CONTEXT, user, () -> {
// 在这个作用域内,USER_CONTEXT.get()返回user_123
System.out.println("当前用户: " + USER_CONTEXT.get().getUserId());
// 可以嵌套使用
ScopedValue.runWhere(REQUEST_ID, "req_456", () -> {
System.out.println("请求ID: " + REQUEST_ID.get());
System.out.println("用户: " + USER_CONTEXT.get().getUserId());
});
// 这里REQUEST_ID已经超出作用域,获取会抛出异常
});
// 这里USER_CONTEXT已经超出作用域
}
/**
* 带返回值的作用域
*/
public String scopedValueWithReturn() {
UserContext user = new UserContext("user_789");
// 使用callWhere获取返回值
String result = ScopedValue.callWhere(USER_CONTEXT, user, () -> {
// 业务处理
String userId = USER_CONTEXT.get().getUserId();
return"处理用户: " + userId;
});
return result;
}
/**
* 多个ScopedValue同时使用
*/
public void multipleScopedValues() {
UserContext user = new UserContext("user_multi");
Connection conn = createConnection();
// 同时绑定多个ScopedValue
ScopedValue.runWhere(
ScopedValue.where(USER_CONTEXT, user)
.where(DB_CONNECTION, conn)
.where(REQUEST_ID, "multi_req"),
() -> {
// 在这个作用域内可以访问所有绑定的值
processBusinessLogic();
}
);
// 作用域结束后自动清理
}
/**
* 异常处理示例
*/
public void exceptionHandling() {
UserContext user = new UserContext("user_exception");
try {
ScopedValue.runWhere(USER_CONTEXT, user, () -> {
// 业务处理
processBusinessLogic();
// 如果抛出异常,作用域也会正常结束
if (someCondition()) {
thrownew RuntimeException("业务异常");
}
});
} catch (RuntimeException e) {
// 异常处理
System.out.println("捕获异常: " + e.getMessage());
}
// 即使发生异常,USER_CONTEXT也会自动清理
}
private Connection createConnection() {
// 创建数据库连接
return null;
}
private void processBusinessLogic() {
// 业务逻辑处理
UserContext user = USER_CONTEXT.get();
System.out.println("处理业务逻辑,用户: " + user.getUserId());
}
private boolean someCondition() {
return Math.random() > 0.5;
}
}
1.3 ScopedValue vs ThreadLocal:全面对比
如果想知道ScopedValue到底比ThreadLocal强在哪里
1.3.1 内存管理对比
为了更直观地理解两者的内存管理差异,用几张图做对比。
ThreadLocal的内存模型图:

ScopedValue的内存模型图:

二者的关键差异如下图:

1.3.2 代码对比示例
/**
* ThreadLocal vs ScopedValue 对比演示
*/
public class ThreadLocalVsScopedValue {
// ThreadLocal方式
private static final ThreadLocal<UserContext> TL_USER_CONTEXT = new ThreadLocal<>();
private static final ThreadLocal<Connection> TL_CONNECTION = new ThreadLocal<>();
// ScopedValue方式
private static final ScopedValue<UserContext> SV_USER_CONTEXT = ScopedValue.newInstance();
private static final ScopedValue<Connection> SV_CONNECTION = ScopedValue.newInstance();
/**
* ThreadLocal方式 - 传统实现
*/
public void processRequestThreadLocal(HttpServletRequest request) {
// 设置上下文
UserContext userContext = new UserContext(request.getHeader("X-User-Id"));
TL_USER_CONTEXT.set(userContext);
Connection conn = null;
try {
// 获取数据库连接
conn = dataSource.getConnection();
TL_CONNECTION.set(conn);
// 业务处理
processBusinessLogic();
} catch (SQLException e) {
// 异常处理
handleException(e);
} finally {
// 必须手动清理 - 容易忘记!
TL_USER_CONTEXT.remove();
TL_CONNECTION.remove();
// 关闭连接
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
// 日志记录
}
}
}
}
/**
* ScopedValue方式 - 现代实现
*/
public void processRequestScopedValue(HttpServletRequest request) {
UserContext userContext = new UserContext(request.getHeader("X-User-Id"));
// 使用try-with-resources管理连接
try (Connection conn = dataSource.getConnection()) {
// 在作用域内执行,自动管理生命周期
ScopedValue.runWhere(
ScopedValue.where(SV_USER_CONTEXT, userContext)
.where(SV_CONNECTION, conn),
() -> {
// 业务处理
processBusinessLogic();
}
);
// 作用域结束后自动清理,无需手动remove
} catch (SQLException e) {
handleException(e);
}
}
/**
* 业务逻辑处理 - 两种方式对比
*/
private void processBusinessLogic() {
// ThreadLocal方式 - 需要处理null值
UserContext tlUser = TL_USER_CONTEXT.get();
if (tlUser == null) {
throw new IllegalStateException("用户上下文未设置");
}
Connection tlConn = TL_CONNECTION.get();
if (tlConn == null) {
throw new IllegalStateException("数据库连接未设置");
}
// ScopedValue方式 - 在作用域内保证不为null
UserContext svUser = SV_USER_CONTEXT.get(); // 不会为null
Connection svConn = SV_CONNECTION.get(); // 不会为null
// 实际业务处理...
System.out.println("处理用户: " + svUser.getUserId());
}
/**
* 线程池场景对比
*/
public void threadPoolComparison() {
ExecutorService executor = Executors.newFixedThreadPool(5);
// ThreadLocal方式 - 容易出问题
for (int i = 0; i < 10; i++) {
final int userId = i;
executor.submit(() -> {
TL_USER_CONTEXT.set(new UserContext("user_" + userId));
try {
processBusinessLogic();
} finally {
TL_USER_CONTEXT.remove(); // 容易忘记或异常跳过
}
});
}
// ScopedValue方式 - 更安全
for (int i = 0; i < 10; i++) {
final int userId = i;
executor.submit(() -> {
UserContext user = new UserContext("user_" + userId);
ScopedValue.runWhere(SV_USER_CONTEXT, user, () -> {
processBusinessLogic(); // 自动管理生命周期
});
});
}
executor.shutdown();
}
private Connection getConnectionFromTL() {
return TL_CONNECTION.get();
}
private DataSource dataSource = null; // 模拟数据源
private void handleException(SQLException e) {} // 异常处理
}
1.3.3 性能对比测试
/**
* 性能对比测试
*/
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@State(Scope.Thread)
public class PerformanceComparison {
private static final ThreadLocal<String> THREAD_LOCAL = new ThreadLocal<>();
private static final ScopedValue<String> SCOPED_VALUE = ScopedValue.newInstance();
private static final int ITERATIONS = 100000;
/**
* ThreadLocal性能测试
*/
@Benchmark
public void threadLocalPerformance() {
for (int i = 0; i < ITERATIONS; i++) {
THREAD_LOCAL.set("value_" + i);
String value = THREAD_LOCAL.get();
THREAD_LOCAL.remove();
}
}
/**
* ScopedValue性能测试
*/
@Benchmark
public void scopedValuePerformance() {
for (int i = 0; i < ITERATIONS; i++) {
ScopedValue.runWhere(SCOPED_VALUE, "value_" + i, () -> {
String value = SCOPED_VALUE.get();
// 自动清理,无需remove
});
}
}
/**
* 实际场景性能测试
*/
public void realScenarioTest() {
long tlStart = System.nanoTime();
// ThreadLocal场景
THREAD_LOCAL.set("initial_value");
for (int i = 0; i < ITERATIONS; i++) {
String current = THREAD_LOCAL.get();
THREAD_LOCAL.set(current + "_" + i);
}
THREAD_LOCAL.remove();
long tlEnd = System.nanoTime();
// ScopedValue场景
long svStart = System.nanoTime();
ScopedValue.runWhere(SCOPED_VALUE, "initial_value", () -> {
String current = SCOPED_VALUE.get();
for (int i = 0; i < ITERATIONS; i++) {
// ScopedValue是不可变的,需要重新绑定
String newValue = current + "_" + i;
ScopedValue.runWhere(SCOPED_VALUE, newValue, () -> {
// 嵌套作用域
String nestedValue = SCOPED_VALUE.get();
});
}
});
long svEnd = System.nanoTime();
System.out.printf("ThreadLocal耗时: %d ns%n", tlEnd - tlStart);
System.out.printf("ScopedValue耗时: %d ns%n", svEnd - svStart);
}
}
1.4 ScopedValue高级特性
1.4.1 结构化并发支持
结构化并发:适合“并发任务管理”场景(比如一次请求内部并行调多个服务),但是不适合作为长期复用的线程池机制,创建销毁很消耗性能,不过可以用虚线程完美结合,因为 虚拟线程是用完即弃,但不需要关闭
ScopedValue与虚拟线程和结构化并发完美配合:
/**
* ScopedValue与结构化并发
*/
public class StructuredConcurrencyExample {
private static final ScopedValue<UserContext> USER_CONTEXT = ScopedValue.newInstance();
private static final ScopedValue<RequestInfo> REQUEST_INFO = ScopedValue.newInstance();
/**
* 结构化并发中的ScopedValue使用
*/
public void structuredConcurrencyWithScopedValue() throws Exception {
UserContext user = new UserContext("structured_user");
RequestInfo request = new RequestInfo("req_123", System.currentTimeMillis());
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
ScopedValue.runWhere(
ScopedValue.where(USER_CONTEXT, user)
.where(REQUEST_INFO, request),
() -> {
// 在作用域内提交子任务
Future<String> userTask = scope.fork(this::fetchUserData);
Future<String> orderTask = scope.fork(this::fetchOrderData);
Future<String> paymentTask = scope.fork(this::fetchPaymentData);
try {
// 等待所有任务完成
scope.join();
scope.throwIfFailed();
// 处理结果
String userData = userTask.resultNow();
String orderData = orderTask.resultNow();
String paymentData = paymentTask.resultNow();
System.out.println("聚合结果: " + userData + ", " + orderData + ", " + paymentData);
} catch (InterruptedException | ExecutionException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("任务执行失败", e);
}
}
);
}
}
private String fetchUserData() {
// 可以访问ScopedValue,无需参数传递
UserContext user = USER_CONTEXT.get();
RequestInfo request = REQUEST_INFO.get();
return "用户数据: " + user.getUserId() + ", 请求: " + request.getRequestId();
}
private String fetchOrderData() {
UserContext user = USER_CONTEXT.get();
return "订单数据: " + user.getUserId();
}
private String fetchPaymentData() {
UserContext user = USER_CONTEXT.get();
return "支付数据: " + user.getUserId();
}
}
class RequestInfo {
private final String requestId;
private final long timestamp;
//省去get 全参
}
1.4.2 继承和嵌套作用域
/**
* ScopedValue继承和嵌套
*/
public class ScopedValueInheritance {
private static final ScopedValue<String> PARENT_VALUE = ScopedValue.newInstance();
private static final ScopedValue<String> CHILD_VALUE = ScopedValue.newInstance();
/**
* 作用域嵌套
*/
public void nestedScopes() {
ScopedValue.runWhere(PARENT_VALUE, "parent_value", () -> {
System.out.println("外层作用域: " + PARENT_VALUE.get());
// 内层作用域可以访问外层值
ScopedValue.runWhere(CHILD_VALUE, "child_value", () -> {
System.out.println("内层作用域 - 父值: " + PARENT_VALUE.get());
System.out.println("内层作用域 - 子值: " + CHILD_VALUE.get());
// 可以重新绑定父值(遮蔽)
ScopedValue.runWhere(PARENT_VALUE, "shadowed_parent", () -> {
System.out.println("遮蔽作用域 - 父值: " + PARENT_VALUE.get());
System.out.println("遮蔽作用域 - 子值: " + CHILD_VALUE.get());
});
// 恢复原来的父值
System.out.println("恢复作用域 - 父值: " + PARENT_VALUE.get());
});
// 子值已超出作用域
try {
System.out.println(CHILD_VALUE.get()); // 抛出异常
} catch (Exception e) {
System.out.println("子值已超出作用域: " + e.getMessage());
}
});
}
/**
* 虚拟线程中的继承
*/
public void virtualThreadInheritance() throws Exception {
ScopedValue.runWhere(PARENT_VALUE, "virtual_parent", () -> {
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
// 虚拟线程自动继承ScopedValue
for (int i = 0; i < 3; i++) {
final int taskId = i;
scope.fork(() -> {
// 可以访问父线程的ScopedValue
String parentVal = PARENT_VALUE.get();
return "任务" + taskId + " - 父值: " + parentVal;
});
}
scope.join();
scope.throwIfFailed();
}
});
}
/**
* 条件绑定
*/
public void conditionalBinding() {
String condition = Math.random() > 0.5 ? "case_a" : "case_b";
ScopedValue.runWhere(PARENT_VALUE, condition, () -> {
String value = PARENT_VALUE.get();
if ("case_a".equals(value)) {
System.out.println("处理情况A");
} else {
System.out.println("处理情况B");
}
});
}
}
1.4.3 错误处理和调试
/**
* ScopedValue错误处理和调试
*/
public class ScopedValueErrorHandling {
private static final ScopedValue<String> MAIN_VALUE = ScopedValue.newInstance();
private static final ScopedValue<Integer> COUNT_VALUE = ScopedValue.newInstance();
/**
* 异常处理
*/
public void exceptionHandling() {
try {
ScopedValue.runWhere(MAIN_VALUE, "test_value", () -> {
// 业务逻辑
processWithError();
});
} catch (RuntimeException e) {
System.out.println("捕获异常: " + e.getMessage());
// ScopedValue已自动清理,无需额外处理
}
// 验证值已清理
try {
String value = MAIN_VALUE.get();
System.out.println("不应该执行到这里: " + value);
} catch (Exception e) {
System.out.println("值已正确清理: " + e.getMessage());
}
}
/**
* 调试信息
*/
public void debugInformation() {
ScopedValue.runWhere(
ScopedValue.where(MAIN_VALUE, "debug_value")
.where(COUNT_VALUE, 42),
() -> {
// 获取当前绑定的所有ScopedValue
System.out.println("当前作用域绑定:");
System.out.println("MAIN_VALUE: " + MAIN_VALUE.get());
System.out.println("COUNT_VALUE: " + COUNT_VALUE.get());
// 模拟复杂调试
debugComplexScenario();
}
);
}
/**
* 资源清理保证
*/
public void resourceCleanupGuarantee() {
List<String> cleanupLog = new ArrayList<>();
ScopedValue.runWhere(MAIN_VALUE, "resource_value", () -> {
// 注册清理钩子
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
cleanupLog.add("资源清理完成");
}));
// 即使这里发生异常,ScopedValue也会清理
if (Math.random() > 0.5) {
throw new RuntimeException("模拟异常");
}
});
// 检查清理情况
System.out.println("清理日志: " + cleanupLog);
}
private void processWithError() {
throw new RuntimeException("业务处理异常");
}
private void debugComplexScenario() {
// 复杂的调试场景
ScopedValue.runWhere(COUNT_VALUE, COUNT_VALUE.get() + 1, () -> {
System.out.println("嵌套调试 - COUNT_VALUE: " + COUNT_VALUE.get());
});
}
}
1.5 实战案例
用一个Web应用中的用户上下文管理来展示ScopedValue在真实项目中的应用。
为了更直观地理解Web应用中ScopedValue的应用,参考如下图所示:

ScopedValue的生命周期如下图所示:

优势如下图所示:

1.5.1 定义Web应用中的ScopedValue
/**
* Web应用ScopedValue定义
*/
public class WebScopedValues {
// 用户上下文
public static final ScopedValue<UserContext> USER_CONTEXT = ScopedValue.newInstance();
// 请求信息
public static final ScopedValue<RequestInfo> REQUEST_INFO = ScopedValue.newInstance();
// 数据库连接(可选)
public static final ScopedValue<Connection> DB_CONNECTION = ScopedValue.newInstance();
// 追踪ID
public static final ScopedValue<String> TRACE_ID = ScopedValue.newInstance();
}
/**
* 用户上下文详细信息
*/
class UserContext {
private final String userId;
private final String username;
private final List<String> roles;
private final Map<String, Object> attributes;
private final Locale locale;
//省去get 全参
}
/**
* 请求信息
*/
class RequestInfo {
private final String requestId;
private final String method;
private final String path;
private final String clientIp;
private final Map<String, String> headers;
//省去get 全参
}
1.5.2 过滤器实现
/**
* 认证过滤器 - 使用ScopedValue
*/
@Component
@Slf4j
public class AuthenticationFilter implements Filter {
@Autowired
private UserService userService;
@Autowired
private JwtTokenProvider tokenProvider;
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
// 生成请求ID
String requestId = generateRequestId();
// 提取请求信息
RequestInfo requestInfo = extractRequestInfo(httpRequest, requestId);
// 认证用户
UserContext userContext = authenticateUser(httpRequest);
// 在作用域内执行请求处理
ScopedValue.runWhere(
ScopedValue.where(WebScopedValues.REQUEST_INFO, requestInfo)
.where(WebScopedValues.USER_CONTEXT, userContext)
.where(WebScopedValues.TRACE_ID, requestId),
() -> {
try {
chain.doFilter(request, response);
} catch (Exception e) {
log.error("请求处理异常", e);
throw new RuntimeException("过滤器异常", e);
}
}
);
// 作用域结束后自动清理所有ScopedValue
log.info("请求处理完成: {}", requestId);
}
private String generateRequestId() {
return "req_" + System.currentTimeMillis() + "_" + ThreadLocalRandom.current().nextInt(1000, 9999);
}
private RequestInfo extractRequestInfo(HttpServletRequest request, String requestId) {
Map<String, String> headers = new HashMap<>();
Enumeration<String> headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
String headerName = headerNames.nextElement();
headers.put(headerName, request.getHeader(headerName));
}
return new RequestInfo(requestId,request.getMethod(),request.getRequestURI(),request.getRemoteAddr(),headers);
}
private UserContext authenticateUser(HttpServletRequest request) {
String authHeader = request.getHeader("Authorization");
if (authHeader != null && authHeader.startsWith("Bearer ")) {
String token = authHeader.substring(7);
return tokenProvider.validateToken(token);
}
// 返回 匿名用户
return new UserContext("anonymous", "Anonymous User",List.of("GUEST"),Map.of("source", "web"),request.getLocale());
}
}
1.5.3 业务层使用
/**
* 用户服务 - 使用ScopedValue
*/
@Service
@Slf4j
@Transactional
public class UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private OrderService orderService;
/**
* 获取当前用户信息
*/
public UserProfile getCurrentUserProfile() {
UserContext userContext = WebScopedValues.USER_CONTEXT.get();
RequestInfo requestInfo = WebScopedValues.REQUEST_INFO.get();
String traceId = WebScopedValues.TRACE_ID.get();
log.info("[{}] 获取用户资料: {}", traceId, userContext.getUserId());
// 根据用户ID查询用户信息
User user = userRepository.findById(userContext.getUserId())
.orElseThrow(() -> new UserNotFoundException("用户不存在: " + userContext.getUserId()));
// 构建用户资料
return UserProfile.builder().userId(user.getId()).username(user.getUsername())
.email(user.getEmail()).roles(userContext.getRoles()).locale(userContext.getLocale())
.lastLogin(user.getLastLoginTime()).build();
}
/**
* 更新用户信息
*/
public void updateUserProfile(UpdateProfileRequest request) {
UserContext userContext = WebScopedValues.USER_CONTEXT.get();
String traceId = WebScopedValues.TRACE_ID.get();
log.info("[{}] 更新用户资料: {}", traceId, userContext.getUserId());
// 验证权限
if (!userContext.getUserId().equals(request.getUserId())) {
throw new PermissionDeniedException("无权更新其他用户资料");
}
// 更新用户信息
User user = userRepository.findById(request.getUserId())
.orElseThrow(() -> new UserNotFoundException("用户不存在: " + request.getUserId()));
user.setEmail(request.getEmail());
user.setUpdateTime(LocalDateTime.now());
userRepository.save(user);
log.info("[{}] 用户资料更新成功: {}", traceId, userContext.getUserId());
}
/**
* 获取用户订单列表
*/
public List<Order> getUserOrders() {
UserContext userContext = WebScopedValues.USER_CONTEXT.get();
// 调用订单服务,无需传递用户ID
return orderService.getUserOrders();
}
}
/**
* 订单服务
*/
@Service
@Slf4j
@Transactional
public class OrderService {
@Autowired
private OrderRepository orderRepository;
public List<Order> getUserOrders() {
UserContext userContext = WebScopedValues.USER_CONTEXT.get();
String traceId = WebScopedValues.TRACE_ID.get();
log.info("[{}] 查询用户订单: {}", traceId, userContext.getUserId());
// 直接从ScopedValue获取用户ID,无需参数传递
return orderRepository.findByUserId(userContext.getUserId());
}
/**
* 创建订单
*/
public Order createOrder(CreateOrderRequest request) {
UserContext userContext = WebScopedValues.USER_CONTEXT.get();
String traceId = WebScopedValues.TRACE_ID.get();
log.info("[{}] 创建订单: 用户={}", traceId, userContext.getUserId());
// 创建订单
Order order = new Order();
order.setOrderId(generateOrderId());
order.setUserId(userContext.getUserId());
order.setAmount(request.getTotalAmount());
order.setStatus(OrderStatus.CREATED);
order.setCreateTime(LocalDateTime.now());
Order savedOrder = orderRepository.save(order);
log.info("[{}] 订单创建成功: {}", traceId, savedOrder.getOrderId());
return savedOrder;
}
private String generateOrderId() {
return "ORD" + System.currentTimeMillis() + ThreadLocalRandom.current().nextInt(1000, 9999);
}
}
1.5.4 Controller层
/**
* 用户控制器 - 使用ScopedValue
*/
@RestController
@RequestMapping("/api/users")
@Slf4j
public class UserController {
@Autowired
private UserService userService;
/**
* 获取当前用户资料
*/
@GetMapping("/profile")
public ResponseEntity<UserProfile> getCurrentUserProfile() {
// 无需传递用户ID,直接从ScopedValue获取
UserProfile profile = userService.getCurrentUserProfile();
return ResponseEntity.ok(profile);
}
/**
* 更新用户资料
*/
@PutMapping("/profile")
public ResponseEntity<Void> updateUserProfile(@RequestBody @Valid UpdateProfileRequest request) {
userService.updateUserProfile(request);
return ResponseEntity.ok().build();
}
/**
* 获取用户订单
*/
@GetMapping("/orders")
public ResponseEntity<List<Order>> getUserOrders() {
List<Order> orders = userService.getUserOrders();
return ResponseEntity.ok(orders);
}
/**
* 异常处理
*/
@ExceptionHandler({UserNotFoundException.class, PermissionDeniedException.class})
public ResponseEntity<ErrorResponse> handleUserExceptions(RuntimeException e) {
// 可以从ScopedValue获取请求信息用于日志
String traceId = WebScopedValues.TRACE_ID.get();
log.error("[{}] 用户操作异常: {}", traceId, e.getMessage());
ErrorResponse error = new ErrorResponse(
e.getClass().getSimpleName(),
e.getMessage(),
traceId
);
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error);
}
}
/**
* 错误响应
*/
@Data
@AllArgsConstructor
class ErrorResponse {
private String error;
private String message;
private String traceId;
private long timestamp = System.currentTimeMillis();
}
1.6 迁移指南:从ThreadLocal到ScopedValue
有些小伙伴可能担心迁移成本,其实从ThreadLocal迁移到ScopedValue并不复杂
1.6.1 迁移步骤
/**
* ThreadLocal到ScopedValue迁移指南
*/
public class MigrationGuide {
// ThreadLocal定义(旧方式)
private static final ThreadLocal<UserContext> TL_USER = new ThreadLocal<>();
private static final ThreadLocal<Connection> TL_CONN = new ThreadLocal<>();
private static final ThreadLocal<String> TL_TRACE = new ThreadLocal<>();
// ScopedValue定义(新方式)
private static final ScopedValue<UserContext> SV_USER = ScopedValue.newInstance();
private static final ScopedValue<Connection> SV_CONN = ScopedValue.newInstance();
private static final ScopedValue<String> SV_TRACE = ScopedValue.newInstance();
/**
* 迁移前:ThreadLocal方式
*/
public void beforeMigration() {
// 设置值
TL_USER.set(new UserContext("user_old"));
TL_TRACE.set("trace_old");
Connection conn = null;
try {
conn = createConnection();
TL_CONN.set(conn);
// 业务处理
processBusinessOld()
} catch (Exception e) {
// 异常处理
} finally {
// 必须手动清理
TL_USER.remove();
TL_TRACE.remove();
TL_CONN.remove();
if (conn != null) {
closeConnection(conn);
}
}
}
/**
* 迁移后:ScopedValue方式
*/
public void afterMigration() {
UserContext user = new UserContext("user_new");
String trace = "trace_new";
// 使用try-with-resources管理连接
try (Connection conn = createConnection()) {
// 在作用域内执行
ScopedValue.runWhere(
ScopedValue.where(SV_USER, user)
.where(SV_TRACE, trace)
.where(SV_CONN, conn),
() -> {
// 业务处理
processBusinessNew();
}
);
// 自动清理,无需finally块
} catch (Exception e) {
// 异常处理
}
}
/**
* 业务处理 - 旧方式
*/
private void processBusinessOld() {
// 需要处理null值
UserContext user = TL_USER.get();
if (user == null) {
thrownew IllegalStateException("用户上下文未设置");
}
Connection conn = TL_CONN.get();
if (conn == null) {
thrownew IllegalStateException("数据库连接未设置");
}
String trace = TL_TRACE.get();
// 业务逻辑...
System.out.println("处理用户: " + user.getUserId() + ", 追踪: " + trace);
}
/**
* 业务处理 - 新方式
*/
private void processBusinessNew() {
// 在作用域内保证不为null
UserContext user = SV_USER.get();
Connection conn = SV_CONN.get();
String trace = SV_TRACE.get();
// 业务逻辑...
System.out.println("处理用户: " + user.getUserId() + ", 追踪: " + trace);
}
/**
* 复杂迁移场景:嵌套ThreadLocal
*/
public void complexMigration() {
// 旧方式:嵌套ThreadLocal
TL_USER.set(new UserContext("outer_user"));
try {
// 内层逻辑
TL_USER.set(new UserContext("inner_user"));
try {
processBusinessOld();
} finally {
// 恢复外层值
TL_USER.set(new UserContext("outer_user"));
}
} finally {
TL_USER.remove();
}
// 新方式:嵌套ScopedValue
ScopedValue.runWhere(SV_USER, new UserContext("outer_user"), () -> {
ScopedValue.runWhere(SV_USER, new UserContext("inner_user"), () -> {
processBusinessNew(); // 使用内层值
});
// 自动恢复外层值
processBusinessNew(); // 使用外层值
});
}
private Connection createConnection() {
// 创建连接
return null;
}
private void closeConnection(Connection conn) {
// 关闭连接
}
}
1.6.2 兼容性处理
/**
* 兼容性处理 - 逐步迁移
*/
public class CompatibilityLayer {
// 新代码使用ScopedValue
private static final ScopedValue<UserContext> SV_USER = ScopedValue.newInstance();
// 旧代码可能还在使用ThreadLocal
private static final ThreadLocal<UserContext> TL_USER = new ThreadLocal<>();
/**
* 桥接模式:同时支持两种方式
*/
public void bridgePattern() {
UserContext user = new UserContext("bridge_user");
// 在新作用域内执行
ScopedValue.runWhere(SV_USER, user, () -> {
// 同时设置ThreadLocal以兼容旧代码
TL_USER.set(user);
try {
// 执行业务逻辑(新旧代码都可以工作)
processMixedBusiness();
} finally {
// 清理ThreadLocal
TL_USER.remove();
}
});
}
/**
* 适配器:让旧代码使用ScopedValue
*/
public static class ThreadLocalAdapter {
private final ScopedValue<UserContext> scopedValue;
public ThreadLocalAdapter(ScopedValue<UserContext> scopedValue) {
this.scopedValue = scopedValue;
}
public void set(UserContext user) {
// 对于set操作,需要在适当的作用域调用
throw new UnsupportedOperationException("请使用ScopedValue.runWhere");
}
public UserContext get() {
try {
return scopedValue.get();
} catch (Exception e) {
// 如果不在作用域内,返回null(模拟ThreadLocal行为)
returnnull;
}
}
public void remove() {
// 无需操作,ScopedValue自动管理
}
}
/**
* 混合业务处理
*/
private void processMixedBusiness() {
// 新代码使用ScopedValue
UserContext svUser = SV_USER.get();
System.out.println("ScopedValue用户: " + svUser.getUserId());
// 旧代码使用ThreadLocal(通过桥接设置)
UserContext tlUser = TL_USER.get();
System.out.println("ThreadLocal用户: " + tlUser.getUserId());
// 两者应该相同
assert svUser == tlUser;
}
/**
* 逐步迁移策略
*/
public void gradualMigrationStrategy() {
// 阶段1:引入ScopedValue,与ThreadLocal共存
// 阶段2:新代码使用ScopedValue,旧代码逐步迁移
// 阶段3:移除ThreadLocal,完全使用ScopedValue
System.out.println("建议的迁移阶段:");
System.out.println("1. 引入ScopedValue,建立桥接");
System.out.println("2. 新功能使用ScopedValue");
System.out.println("3. 逐步迁移旧代码");
System.out.println("4. 移除ThreadLocal相关代码");
System.out.println("5. 清理桥接层");
}
}

浙公网安备 33010602011771号