news 2026/3/7 22:17:24

再见了ThreadLocal,我决定用ScopedValue!

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
再见了ThreadLocal,我决定用ScopedValue!

今天我们来聊聊一个即将改变我们编程习惯的新特性——ScopedValue。

有些小伙伴在工作中,一提到线程内数据传递就想到ThreadLocal,但真正用起来却遇到各种坑:内存泄漏、数据污染、性能问题等等。

其实,ScopedValue就像ThreadLocal的升级版,既保留了优点,又解决了痛点。

我们一起聊聊ScopedValue的优势和用法,希望对你会有所帮助。

加苏三的工作内推群

一、ThreadLocal的痛点

在介绍ScopedValue之前,我们先回顾一下ThreadLocal的常见问题。

有些小伙伴可能会想:"ThreadLocal用得好好的,为什么要换?"

其实,ThreadLocal在设计上存在一些固有缺陷。

ThreadLocal的内存泄漏问题

为了更直观地理解ThreadLocal的内存泄漏问题,我画了一个内存泄漏的示意图:

image

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++) {

final int 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;

public UserContext(String userId) {

this.userId = userId;

this.timestamp = System.currentTimeMillis();

}

public String getUserId() {

return userId;

}

public long getTimestamp() {

return timestamp;

}

@Override

public String toString() {

return "UserContext{userId='" + userId + "', timestamp=" + timestamp + "}";

}

}

ThreadLocal问题的根本原因

生命周期管理复杂:需要手动调用set/remove,容易遗漏

内存泄漏风险:线程池中线程复用,Value无法被GC

继承性差:子线程无法自动继承父线程数据

性能开销:ThreadLocalMap的哈希表操作有开销

有些小伙伴可能会问:"我们用InheritableThreadLocal不就能解决继承问题了吗?"

我的经验是:InheritableThreadLocal只是缓解了问题,但带来了新的复杂度,而且性能更差。

二、ScopedValue:新一代线程局部变量

ScopedValue是Java 20中引入的预览特性,在Java 21中成为正式特性。

它旨在解决ThreadLocal的痛点,提供更安全、更高效的线程内数据传递方案。

ScopedValue的核心设计理念

为了更直观地理解ScopedValue的工作原理,我画了一个ScopedValue的架构图:

image

ScopedValue的核心优势:

image

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()) {

throw new 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;

}

}

三、ScopedValue vs ThreadLocal:全面对比

有些小伙伴可能还想知道ScopedValue到底比ThreadLocal强在哪里。

让我们通过详细的对比来看看。

3.1 内存管理对比

为了更直观地理解两者的内存管理差异,我画了几张图做对比。

ThreadLocal的内存模型图:

image

ScopedValue的内存模型图:

image

二者的关键差异如下图:

image

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) {} // 异常处理

}

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);

}

}

四、ScopedValue高级特性

有些小伙伴掌握了基础用法后,还想了解更高级的特性。

ScopedValue确实提供了很多强大的功能。

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;

public RequestInfo(String requestId, long timestamp) {

this.requestId = requestId;

this.timestamp = timestamp;

}

public String getRequestId() { return requestId; }

public long getTimestamp() { return timestamp; }

}

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");

}

});

}

}

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());

});

}

}

五、实战案例

有些小伙伴可能还想看更复杂的实战案例。

让我们用一个Web应用中的用户上下文管理来展示ScopedValue在真实项目中的应用。

为了更直观地理解Web应用中ScopedValue的应用,我画了一个请求处理流程的架构图:

image

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

image

优势如下图所示:

image

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;

public UserContext(String userId, String username, List<String> roles,

Map<String, Object> attributes, Locale locale) {

this.userId = userId;

this.username = username;

this.roles = Collections.unmodifiableList(new ArrayList<>(roles));

this.attributes = Collections.unmodifiableMap(new HashMap<>(attributes));

this.locale = locale;

}

// Getter方法

public String getUserId() { return userId; }

public String getUsername() { return username; }

public List<String> getRoles() { return roles; }

public Map<String, Object> getAttributes() { return attributes; }

public Locale getLocale() { return locale; }

public boolean hasRole(String role) {

return roles.contains(role);

}

public Object getAttribute(String key) {

return attributes.get(key);

}

}

/**

* 请求信息

*/

class RequestInfo {

private final String requestId;

private final String method;

private final String path;

private final String clientIp;

private final Map<String, String> headers;

public RequestInfo(String requestId, String method, String path,

String clientIp, Map<String, String> headers) {

this.requestId = requestId;

this.method = method;

this.path = path;

this.clientIp = clientIp;

this.headers = Collections.unmodifiableMap(new HashMap<>(headers));

}

// Getter方法

public String getRequestId() { return requestId; }

public String getMethod() { return method; }

public String getPath() { return path; }

public String getClientIp() { return clientIp; }

public Map<String, String> getHeaders() { return headers; }

}

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()

);

}

}

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);

}

}

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();

}

六、迁移指南:从ThreadLocal到ScopedValue

有些小伙伴可能担心迁移成本,其实从ThreadLocal迁移到ScopedValue并不复杂。

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");

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/2/25 14:10:48

软件测试面试题集合

软件测试面试题,这是一份集锦&#xff0c;也是一份软件测试人员 学习的好工具书&#xff0c;非常实用。 01. 为什么要在一个团队中开展软件测试 工作&#xff1f; 因为没有经过测试的软件很难在发布之前知道该软件的质量&#xff0c;就好比 ISO 质量认证一样&#xff0c;测试同…

作者头像 李华
网站建设 2026/3/5 9:41:53

OpenVSCode Server终极性能调优与资源管理完整指南

OpenVSCode Server终极性能调优与资源管理完整指南 【免费下载链接】openvscode-server 项目地址: https://gitcode.com/gh_mirrors/op/openvscode-server OpenVSCode Server作为基于浏览器的代码编辑器服务器&#xff0c;其性能表现直接影响开发效率。本文将为您提供一…

作者头像 李华
网站建设 2026/3/7 0:02:44

【系统微服务化】

微服务化改造的关键步骤 圈定服务边界与数据表 确定微服务包含哪些数据表是改造的第一步。库存服务涉及15张表&#xff0c;包括自营库存表、商家虚拟库存表等。这些表与商品基本信息表关联较弱&#xff0c;便于独立拆分。业务架构师和数据架构师需深入分析业务场景和表关系&…

作者头像 李华
网站建设 2026/3/4 20:16:03

高可用架构(一)

高可用架构改造要点总结 针对小程序点餐平台的高并发场景&#xff08;10万QPS、500万日订单、99.99%可用性&#xff09;&#xff0c;以下是关键改造措施&#xff1a; 前端接入优化CDN加速静态资源 商品图片等静态数据通过多地CDN节点分发&#xff0c;减少服务端负载。Nginx集群…

作者头像 李华
网站建设 2026/2/25 5:27:04

终极指南:如何为泉盛UV-K5对讲机刷入开源固件实现专业功能

终极指南&#xff1a;如何为泉盛UV-K5对讲机刷入开源固件实现专业功能 【免费下载链接】uv-k5-firmware-custom This is a fork of Egzumer https://github.com/egzumer/uv-k5-firmware-custom 项目地址: https://gitcode.com/gh_mirrors/uvk/uv-k5-firmware-custom 想要…

作者头像 李华