# Java子线程访问Attributes问题的分析与解决方案
## 引言:线程上下文与Attributes传递的挑战
在Java Web开发和并发编程中,线程上下文(Thread Context)的管理是一个常见但易被忽视的问题。特别是在多线程环境下,子线程无法直接访问父线程中的Attributes(如HttpServletRequest属性、ThreadLocal变量等),这会导致数据丢失、上下文信息不一致等问题。理解这一问题的本质并提供有效的解决方案,对于构建健壮的并发应用至关重要。
## 问题根源分析
### 线程隔离机制
Java中每个线程都有独立的执行栈和上下文环境。当创建新线程时,子线程不会自动继承父线程的所有状态。
```java
public class ThreadInheritanceDemo {
private static final ThreadLocal<String> threadLocal = new ThreadLocal<>();
public static void main(String[] args) throws InterruptedException {
// 主线程设置ThreadLocal值
threadLocal.set("主线程的值");
Thread childThread = new Thread(() -> {
// 子线程尝试访问ThreadLocal
String value = threadLocal.get();
System.out.println("子线程获取的值: " + value); // 输出:null
});
childThread.start();
childThread.join();
System.out.println("主线程的值: " + threadLocal.get()); // 输出:主线程的值
}
}
```
### Servlet容器的线程模型
在Web应用中,每个HTTP请求通常由独立的线程处理。Servlet容器维护的Attributes与特定请求线程绑定。
```java
@WebServlet("/test")
public class ThreadAttributesServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 在请求线程中设置属性
request.setAttribute("requestKey", "请求级别的值");
request.getSession().setAttribute("sessionKey", "会话级别的值");
// 创建子线程
new Thread(() -> {
// 子线程无法直接访问request对象
// HttpServletRequest request2 = ??? // 无法获取
// 也无法访问ServletContext
// ServletContext context = ??? // 需要特殊处理
}).start();
}
}
```
## 核心解决方案
### 方案一:显式参数传递
最直接的解决方案是在创建子线程时,显式传递所需的数据。
```java
import java.util.concurrent.*;
import javax.servlet.http.*;
public class ExplicitParameterPassing {
/**
* 通过构造函数传递参数
*/
static class RequestProcessingTask implements Runnable {
private final String requestParam;
private final Map<String, Object> attributes;
public RequestProcessingTask(String requestParam,
Map<String, Object> attributes) {
this.requestParam = requestParam;
this.attributes = new ConcurrentHashMap<>(attributes);
}
@Override
public void run() {
System.out.println("处理请求参数: " + requestParam);
System.out.println("访问属性: " + attributes.get("userInfo"));
// 处理业务逻辑...
}
}
/**
* 从HttpServletRequest提取必要信息
*/
public static Map<String, Object> extractAttributes(HttpServletRequest request) {
Map<String, Object> attributes = new HashMap<>();
// 提取请求参数
request.getParameterMap().forEach((key, values) -> {
if (values != null && values.length > 0) {
attributes.put("param_" + key, values[0]);
}
});
// 提取请求属性
Enumeration<String> attrNames = request.getAttributeNames();
while (attrNames.hasMoreElements()) {
String name = attrNames.nextElement();
attributes.put("attr_" + name, request.getAttribute(name));
}
// 提取会话属性(如果存在)
HttpSession session = request.getSession(false);
if (session != null) {
Enumeration<String> sessionAttrNames = session.getAttributeNames();
while (sessionAttrNames.hasMoreElements()) {
String name = sessionAttrNames.nextElement();
attributes.put("session_" + name, session.getAttribute(name));
}
}
return attributes;
}
/**
* 在Servlet中使用
*/
public void processInBackground(HttpServletRequest request) {
// 提取并封装必要信息
Map<String, Object> attributes = extractAttributes(request);
String importantParam = request.getParameter("importantParam");
// 创建并执行任务
ExecutorService executor = Executors.newFixedThreadPool(5);
RequestProcessingTask task = new RequestProcessingTask(
importantParam, attributes);
executor.submit(task);
executor.shutdown();
}
}
```
### 方案二:使用线程池和上下文包装器
对于复杂的场景,可以创建支持上下文传递的线程池。
```java
import java.util.Map;
import java.util.concurrent.*;
public class ContextAwareThreadPool {
/**
* 支持上下文传递的Runnable包装器
*/
public static class ContextAwareRunnable implements Runnable {
private final Runnable task;
private final Map<Class<?>, Object> context;
public ContextAwareRunnable(Runnable task,
Map<Class<?>, Object> context) {
this.task = task;
this.context = new ConcurrentHashMap<>(context);
}
@Override
public void run() {
// 保存当前线程的原始上下文
Map<Class<?>, Object> originalContext =
ThreadContext.getContextSnapshot();
try {
// 设置新的上下文
ThreadContext.setContext(context);
// 执行任务
task.run();
} finally {
// 恢复原始上下文
ThreadContext.restoreContext(originalContext);
}
}
}
/**
* 线程上下文管理器
*/
public static class ThreadContext {
private static final ThreadLocal<Map<Class<?>, Object>>
threadContext = ThreadLocal.withInitial(ConcurrentHashMap::new);
public static void put(Class<?> key, Object value) {
threadContext.get().put(key, value);
}
@SuppressWarnings("unchecked")
public static <T> T get(Class<T> key) {
return (T) threadContext.get().get(key);
}
public static Map<Class<?>, Object> getContextSnapshot() {
return new ConcurrentHashMap<>(threadContext.get());
}
public static void setContext(Map<Class<?>, Object> context) {
threadContext.set(new ConcurrentHashMap<>(context));
}
public static void restoreContext(Map<Class<?>, Object> context) {
threadContext.set(context != null ?
new ConcurrentHashMap<>(context) : new ConcurrentHashMap<>());
}
public static void clear() {
threadContext.remove();
}
}
/**
* 支持上下文传递的线程池
*/
public static class ContextAwareExecutorService
extends ThreadPoolExecutor {
public ContextAwareExecutorService(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
super(corePoolSize, maximumPoolSize, keepAliveTime,
unit, workQueue);
}
@Override
public void execute(Runnable command) {
// 捕获当前线程上下文
Map<Class<?>, Object> context =
ThreadContext.getContextSnapshot();
// 包装原始任务
ContextAwareRunnable wrappedTask =
new ContextAwareRunnable(command, context);
super.execute(wrappedTask);
}
@Override
public <T> Future<T> submit(Callable<T> task) {
// 对于Callable任务也需要类似处理
Map<Class<?>, Object> context =
ThreadContext.getContextSnapshot();
Callable<T> wrappedTask = () -> {
Map<Class<?>, Object> originalContext =
ThreadContext.getContextSnapshot();
try {
ThreadContext.setContext(context);
return task.call();
} finally {
ThreadContext.restoreContext(originalContext);
}
};
return super.submit(wrappedTask);
}
}
/**
* 使用示例
*/
public static void main(String[] args) {
// 初始化上下文
ThreadContext.put(HttpServletRequest.class,
mockRequest()); // 模拟请求对象
ThreadContext.put(UserInfo.class, new UserInfo("user123"));
// 创建支持上下文的线程池
ContextAwareExecutorService executor =
new ContextAwareExecutorService(
5, 10, 60, TimeUnit.SECONDS,
new LinkedBlockingQueue<>()
);
// 提交任务
executor.execute(() -> {
// 在子线程中可以访问上下文
UserInfo user = ThreadContext.get(UserInfo.class);
System.out.println("在子线程中访问用户信息: " + user.getUserId());
// 处理业务逻辑...
});
executor.shutdown();
}
// 模拟请求对象
private static Object mockRequest() {
return new Object(); // 实际应为HttpServletRequest实例
}
static class UserInfo {
private final String userId;
public UserInfo(String userId) {
this.userId = userId;
}
public String getUserId() {
return userId;
}
}
}
```
### 方案三:使用TransmittableThreadLocal
对于更复杂的需求,可以使用阿里巴巴开源的TransmittableThreadLocal。
```xml
<!-- Maven依赖 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
<version>2.14.2</version>
</dependency>
```
```java
import com.alibaba.ttl.TransmittableThreadLocal;
import com.alibaba.ttl.TtlRunnable;
import java.util.concurrent.*;
public class TransmittableThreadLocalSolution {
/**
* 使用TransmittableThreadLocal管理上下文
*/
public static class RequestContext {
// 使用TransmittableThreadLocal代替普通ThreadLocal
private static final TransmittableThreadLocal<RequestContext>
currentContext = new TransmittableThreadLocal<>();
private String requestId;
private String userId;
private Map<String, Object> attributes;
public RequestContext(String requestId, String userId) {
this.requestId = requestId;
this.userId = userId;
this.attributes = new ConcurrentHashMap<>();
}
public static void set(RequestContext context) {
currentContext.set(context);
}www.tv.wandaihu.cn>www.hy.wandaihu.cn
public static RequestContext get() {
return currentContext.get();
}
public static void clear() {
currentContext.remove();
}
public void setAttribute(String key, Object value) {
attributes.put(key, value);
}
public Object getAttribute(String key) {
return attributes.get(key);
}
// Getter方法...
}
/**
* 使用TTL包装的线程池
*/
public static ExecutorService createTtlExecutorService() {
// 创建基础线程池
ExecutorService executor = Executors.newFixedThreadPool(5);
// 使用TTL包装,使线程池支持上下文传递
return com.alibaba.ttl.threadpool.TtlExecutors
.getTtlExecutorService(executor);
}
/**
* 使用TTL包装Runnable
*/
public static Runnable wrapWithTtl(Runnable runnable) {
return TtlRunnable.get(runnable);
}
/**
* 完整示例
*/
public static void main(String[] args) throws Exception {
// 1. 初始化请求上下文
RequestContext context = new RequestContext("req-123", "user-456");
context.setAttribute("locale", "zh_CN");
context.setAttribute("timezone", "Asia/Shanghai");
RequestContext.set(context);
// 2. 创建支持TTL的线程池
ExecutorService executor = createTtlExecutorService();
// 3. 提交任务
Future<?> future = executor.submit(() -> {
// 子线程中可以直接访问上下文
RequestContext ctx = RequestContext.get();
if (ctx != null) {
System.out.println("请求ID: " + ctx.getRequestId());
System.out.println("用户ID: " + ctx.getUserId());
System.out.println("Locale: " + ctx.getAttribute("locale"));
// 可以设置新的属性
ctx.setAttribute("processed", true);
}
});
// 等待任务完成
future.get();
// 4. 验证主线程上下文
System.out.println("主线程中processed属性: " +
RequestContext.get().getAttribute("processed"));
executor.shutdown();
}
/**
* 在Web应用中的使用
*/
public static class RequestContextFilter implements Filter {
@Override
public void doFilter(ServletRequest request,
ServletResponse response,
FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
try {
// 初始化请求上下文
RequestContext context = new RequestContext(
httpRequest.getRequestId(), // 假设有requestId
getUserIdFromRequest(httpRequest)
);
// 复制请求属性
httpRequest.getAttributeNames().asIterator()
.forEachRemaining(name ->
context.setAttribute(name,
httpRequest.getAttribute(name)));
RequestContext.set(context);
chain.doFilter(request, response);
} finally {
RequestContext.clear();
}
}
private String getUserIdFromRequest(HttpServletRequest request) {
// 从请求中获取用户ID的逻辑
return request.getRemoteUser(); // 示例
}
// 其他Filter方法...
}
}
```
### 方案四:Spring框架的解决方案
在Spring生态中,有多种处理线程上下文传递的方式。
```java
import org.springframework.core.task.TaskDecorator;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import java.util.concurrent.Callable;
public class SpringThreadContextSolution {
/**
* 使用TaskDecorator传递RequestAttributes
*/
public static ThreadPoolTaskExecutor createContextPropagatingExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("context-aware-");
// 关键:设置TaskDecorator来传递上下文
executor.setTaskDecorator(new RequestContextPropagatingDecorator());
executor.initialize();
return executor;
}
/**
* 请求上下文传递装饰器
*/
public static class RequestContextPropagatingDecorator
implements TaskDecorator {
@Override
public Runnable decorate(Runnable runnable) {
// 捕获当前请求上下文
RequestAttributes requestAttributes =
RequestContextHolder.getRequestAttributes();
return () -> {
try {
// 在子线程中设置请求上下文
RequestContextHolder.setRequestAttributes(
requestAttributes, true);
runnable.run();
} finally {
// 清理线程局部变量
RequestContextHolder.resetRequestAttributes();
}
};
}
}
/**
* 使用@Async时的上下文传递
*/
@Configuration
@EnableAsync
public static class AsyncConfig implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("async-");
// 设置TaskDecorator
executor.setTaskDecorator(new RequestContextPropagatingDecorator());
executor.initialize();
return executor;
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new SimpleAsyncUncaughtExceptionHandler();
}
}
/**
* 业务服务示例
*/
@Service
public static class BusinessService {
@Autowired
private ThreadPoolTaskExecutor contextAwareExecutor;
/**
* 手动使用线程池执行
*/
public CompletableFuture<Void> processInBackground() {
return CompletableFuture.runAsync(() -> {
// 这里可以直接访问RequestAttributes
RequestAttributes attributes =
RequestContextHolder.getRequestAttributes();
if (attributes != null) {
String requestId = (String) attributes.getAttribute(
"requestId", RequestAttributes.SCOPE_REQUEST);
System.out.println("处理请求: " + requestId);
}
// 业务逻辑...
}, contextAwareExecutor);
}
/**
* 使用@Async注解
*/
@Async
public CompletableFuture<String> asyncProcess(String data) {
// 自动获取RequestAttributes
RequestAttributes attributes =
RequestContextHolder.getRequestAttributes();
// 处理逻辑...
return CompletableFuture.completedFuture("处理完成");
}
}
/**
* 自定义上下文对象传递
*/
public static class SecurityContextPropagatingDecorator
implements TaskDecorator {
@Override
public Runnable decorate(Runnable runnable) {
// 捕获安全上下文
SecurityContext securityContext =
SecurityContextHolder.getContext();
return () -> {
try {
// 设置安全上下文
SecurityContextHolder.setContext(securityContext);
// 执行任务
runnable.run();
} finally {
SecurityContextHolder.clearContext();
}
};
}
}
}
```
## 实战案例:分布式追踪上下文传递
```java
import io.opentelemetry.context.Context;
import io.opentelemetry.context.ContextKey;
import io.opentelemetry.context.Scope;
import java.util.concurrent.*;
public class DistributedTraceContextSolution {
/**
* 分布式追踪上下文管理器
*/
public static class TraceContext {
private static final ContextKey<String> TRACE_ID_KEY =
ContextKey.named("trace-id");
private static final ContextKey<String> SPAN_ID_KEY =
ContextKey.named("span-id");
public static Context getCurrentContext() {
return Context.current();
}
public static String getTraceId() {
return getCurrentContext().get(TRACE_ID_KEY);
}
public static String getSpanId() {
return getCurrentContext().get(SPAN_ID_KEY);
}
public static Context createContext(String traceId, String spanId) {
return getCurrentContext()
.with(TRACE_ID_KEY, traceId)
.with(SPAN_ID_KEY, spanId);
}
}
/**
* 支持OpenTelemetry上下文的线程池
*/
public static class TraceAwareExecutorService
extends ThreadPoolExecutor {
public TraceAwareExecutorService(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
super(corePoolSize, maximumPoolSize, keepAliveTime,
unit, workQueue);
}
@Override
public void execute(Runnable command) {
// 捕获当前追踪上下文
Context context = TraceContext.getCurrentContext();
Runnable wrappedCommand = () -> {
try (Scope scope = context.makeCurrent()) {
command.run();
}
};
super.execute(wrappedCommand);
}
@Override
public <T> Future<T> submit(Callable<T> task) {
Context context = TraceContext.getCurrentContext();
Callable<T> wrappedTask = () -> {
try (Scope scope = context.makeCurrent()) {
return task.call();
}
};
return super.submit(wrappedTask);
}
}
/**
* 使用CompletableFuture的上下文传递
*/
public static class ContextPropagatingCompletableFuture {
public static <T> CompletableFuture<T> supplyAsync(
Supplier<T> supplier, Executor executor) {
Context context = TraceContext.getCurrentContext();
return CompletableFuture.supplyAsync(() -> {
try (Scope scope = context.makeCurrent()) {
return supplier.get();
}
}, executor);
}
public static CompletableFuture<Void> runAsync(
Runnable runnable, Executor executor) {
Context context = TraceContext.getCurrentContext();
return CompletableFuture.runAsync(() -> {
try (Scope scope = context.makeCurrent()) {
runnable.run();
}www.gd.zizhitong.cn>www.tu.zizhitong.cn
}, executor);
}
}
/**
* 在Web应用中的集成
*/
public static class TraceContextFilter implements Filter {
@Override
public void doFilter(ServletRequest request,
ServletResponse response,
FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
// 从请求头获取追踪信息
String traceId = httpRequest.getHeader("X-Trace-ID");
String spanId = httpRequest.getHeader("X-Span-ID");
if (traceId != null && spanId != null) {
// 创建追踪上下文
Context traceContext = TraceContext.createContext(traceId, spanId);
try (Scope scope = traceContext.makeCurrent()) {
chain.doFilter(request, response);
}
} else {
chain.doFilter(request, response);
}
}
}
}
```
## 最佳实践与注意事项
### 线程上下文管理规范
```java
import java.util.*;
import java.util.concurrent.*;
public class ThreadContextBestPractices {
/**
* 1. 明确上下文生命周期
*/
public static class RequestScopeContext {
private final Map<String, Object> attributes = new ConcurrentHashMap<>();
private final String requestId;
private final long startTime;
public RequestScopeContext(String requestId) {
this.requestId = requestId;
this.startTime = System.currentTimeMillis();
}
public void setAttribute(String key, Object value) {
attributes.put(key, value);
}
public Object getAttribute(String key) {
return attributes.get(key);
}
public void cleanup() {
attributes.clear();
// 释放资源...
}
}
/**
* 2. 使用try-with-resources模式确保清理
*/
public static class ContextScope implements AutoCloseable {
private final Runnable cleanupAction;
public ContextScope(Runnable setupAction, Runnable cleanupAction) {
this.cleanupAction = cleanupAction;
setupAction.run();
}
@Override
public void close() {
cleanupAction.run();
}
}
/**
* 3. 线程安全的上下文访问
*/
public static class ThreadSafeContextManager {
private static final ThreadLocal<Map<String, Object>> context =
ThreadLocal.withInitial(ConcurrentHashMap::new);
public static void set(String key, Object value) {
context.get().put(key, value);
}
public static Object get(String key) {
return context.get().get(key);
}
public static Map<String, Object> getSnapshot() {
return new ConcurrentHashMap<>(context.get());
}
public static void restore(Map<String, Object> snapshot) {
context.set(new ConcurrentHashMap<>(snapshot));
}
public static void clear() {
context.remove();
}
}
/**
* 4. 避免内存泄漏
*/
public static class LeakPreventionExample {
private static final ThreadLocal<byte[]> largeBuffer =
new ThreadLocal<>();
public void processWithCleanup() {
try {
largeBuffer.set(new byte[1024 * 1024]); // 1MB buffer
// 处理逻辑...
} finally {
// 必须清理ThreadLocal
largeBuffer.remove();
}
}
}
/**
* 5. 性能优化建议
*/
public static class PerformanceOptimizedContext {
// 使用预定义的key减少字符串创建
public static final String KEY_USER_ID = "userId";
public static final String KEY_REQUEST_ID = "requestId";
public static final String KEY_LOCALE = "locale";
private static final ThreadLocal<Object[]> context =
ThreadLocal.withInitial(() -> new Object[10]);
public static void set(int index, Object value) {
context.get()[index] = value;
}
public static Object get(int index) {
return context.get()[index];
}
}
}
/**
* 6. 统一的上下文传递接口
*/
interface ContextPropagator<T> {
T capture();
void restore(T context);
void clear();
}
class RequestContextPropagator implements ContextPropagator<Map<String, Object>> {
@Override
public Map<String, Object> capture() {
Map<String, Object> context = new HashMap<>();
// 捕获各种上下文信息
RequestAttributes requestAttributes =
RequestContextHolder.getRequestAttributes();
if (requestAttributes != null) {
context.put("requestAttributes", requestAttributes);
}
// 捕获安全上下文
SecurityContext securityContext =
SecurityContextHolder.getContext();
if (securityContext != null) {
context.put("securityContext", securityContext);
}
return context;
}
@Override
@SuppressWarnings("unchecked")
public void restore(Map<String, Object> context) {
if (context.containsKey("requestAttributes")) {
RequestContextHolder.setRequestAttributes(
(RequestAttributes) context.get("requestAttributes"), true);
}
if (context.containsKey("securityContext")) {
SecurityContextHolder.setContext(
(SecurityContext) context.get("securityContext"));
}
}
@Override
public void clear() {
RequestContextHolder.resetRequestAttributes();
SecurityContextHolder.clearContext();
}
}
```
## 测试与验证
```java
import org.junit.jupiter.api.*;
import java.util.concurrent.*;
import static org.junit.jupiter.api.Assertions.*;
public class ThreadContextTest {
@Test
@DisplayName("测试上下文在子线程中的传递")
void testContextPropagation() throws Exception {
// 设置父线程上下文
ThreadLocal<String> threadLocal = new ThreadLocal<>();
threadLocal.set("parent-value");
// 创建支持上下文传递的线程池
ExecutorService executor = Executors.newFixedThreadPool(1);
// 提交任务
Future<String> future = executor.submit(() -> {
// 验证子线程无法直接访问父线程的ThreadLocal
assertNull(threadLocal.get());
// 需要显式传递
return "processed";
});
assertEquals("processed", future.get());
executor.shutdown();
}
@Test
@DisplayName("测试TransmittableThreadLocal")
void testTransmittableThreadLocal() throws Exception {
TransmittableThreadLocal<String> ttl = new TransmittableThreadLocal<>();
ttl.set("main-value");
ExecutorService executor = Executors.newFixedThreadPool(1);
// 包装任务
Runnable task = TtlRunnable.get(() -> {
assertEquals("main-value", ttl.get());
});
executor.submit(task).get();
executor.shutdown();
}
@Test
@DisplayName("测试上下文清理")
void testContextCleanup() {
ThreadLocal<byte[]> resourceHolder = new ThreadLocal<>();
// 模拟资源分配
resourceHolder.set(new byte[1024 * 1024]);
// 创建新线程
Thread thread = new Thread(() -> {
// 新线程不应访问到资源
assertNull(resourceHolder.get());
});
thread.start();
try {
thread.join();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
// 清理资源
resourceHolder.remove();
assertNull(resourceHolder.get());
}
}
```
## 总结
Java子线程无法获取父线程Attributes的问题源于Java的线程隔离机制。解决这一问题的核心思路是在创建子线程时显式传递必要的上下文信息。根据不同的应用场景和需求,可以选择:
1. **简单场景**:通过构造函数或方法参数显式传递数据
2. **复杂场景**:使用上下文包装器或专门的线程池
3. **Spring应用**:利用TaskDecorator和@Async的上下文传播
4. **分布式系统**:结合分布式追踪框架(如OpenTelemetry)
无论选择哪种方案,都需要注意上下文生命周期的管理、内存泄漏的预防以及性能优化。良好的线程上下文管理不仅能解决数据传递问题,还能提高系统的可维护性和可观测性。在实际开发中,应根据具体需求选择最合适的方案,并建立统一的上下文管理规范。
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/19545/viewspace-3105619/ ,如需转载,请注明出处,否则将追究法律责任。