📝 Part 8:构建统一上下文框架设计与实现(实战篇)
在实际项目中,我们往往需要处理多种上下文来源,例如:
- Web 请求上下文(
RequestContextHolder
) - 日志追踪上下文(
MDC
) - Dubbo RPC 上下文(
RpcContext
) - 分布式链路追踪(
Sleuth
) - 自定义业务上下文(如租户、用户信息等)
如果每个组件都单独维护自己的上下文逻辑,不仅容易出错,而且难以扩展和维护。因此,构建一个统一的上下文管理框架 是非常有必要的。
本文将带你从零开始设计并实现一个可插拔、支持多数据源、自动传播的统一上下文框架,并结合 Spring Boot 实现完整的集成方案。
一、目标设计
我们希望这个框架具备以下能力:
功能 | 描述 |
---|---|
✅ 多上下文来源兼容 | 支持 Web、RPC、日志、异步任务等多种上下文来源 |
✅ 自动传播机制 | 在线程切换、异步调用时自动传播上下文 |
✅ 配置化扩展 | 可通过配置启用或禁用特定上下文模块 |
✅ 易于接入 Spring | 支持与 Spring Boot 的无缝集成 |
✅ 支持跨服务透传 | 如 Dubbo、Feign 等微服务通信场景 |
✅ 日志自动注入 | traceId、userId 等字段自动写入 MDC |
二、整体架构设计图
+-----------------------------+
| ContextManager |
| - set(key, value) |
| - get(key) |
| - clear() |
+------------+----------------+
|
+--------v---------+
| ContextProvider |
| (接口) |
| - readFrom() |
| - writeTo() |
+--------------------+
/ | \
/ | \
+---------+ +----------+ +-----------+
|WebContext | |RpcContext| |LogContext |
|Provider | |Provider | |Provider |
+-----------+ +----------+ +-----------+
三、核心接口定义
1. ContextManager
(上下文管理器)
public interface ContextManager {
void set(String key, String value);
String get(String key);
void clear();
}
2. ContextProvider
(上下文提供者)
public interface ContextProvider {
void readFrom(Map<String, String> contextMap); // 从当前上下文中读取数据到 map
void writeTo(Map<String, String> contextMap); // 将 map 中的数据写入当前上下文
}
四、具体实现示例
1. WebContextProvider(基于 RequestContextHolder)
@Component
public class WebContextProvider implements ContextProvider {
@Override
public void readFrom(Map<String, String> contextMap) {
RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
if (attrs != null) {
for (String key : attrs.getAttributeNames(RequestAttributes.SCOPE_REQUEST)) {
Object val = attrs.getAttribute(key, RequestAttributes.SCOPE_REQUEST);
if (val instanceof String) {
contextMap.put(key, (String) val);
}
}
}
}
@Override
public void writeTo(Map<String, String> contextMap) {
RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
if (attrs != null) {
contextMap.forEach((key, value) -> attrs.setAttribute(key, value, RequestAttributes.SCOPE_REQUEST));
}
}
}
2. RpcContextProvider(基于 Dubbo RpcContext)
@Component
@ConditionalOnClass(name = "org.apache.dubbo.rpc.RpcContext")
public class RpcContextProvider implements ContextProvider {
@Override
public void readFrom(Map<String, String> contextMap) {
RpcContext context = RpcContext.getContext();
context.getAttachments().forEach(contextMap::put);
}
@Override
public void writeTo(Map<String, String> contextMap) {
RpcContext context = RpcContext.getContext();
contextMap.forEach(context::setAttachment);
}
}
3. LogContextProvider(基于 MDC)
@Component
public class LogContextProvider implements ContextProvider {
@Override
public void readFrom(Map<String, String> contextMap) {
Map<String, String> mdcMap = MDC.getCopyOfContextMap();
if (mdcMap != null) {
contextMap.putAll(mdcMap);
}
}
@Override
public void writeTo(Map<String, String> contextMap) {
MDC.setContextMap(contextMap);
}
}
}
五、统一上下文管理器实现
@Component
public class DefaultContextManager implements ContextManager {
private final List<ContextProvider> providers;
public DefaultContextManager(List<ContextProvider> providers) {
this.providers = providers;
}
@Override
public void set(String key, String value) {
Map<String, String> contextMap = new HashMap<>();
contextMap.put(key, value);
providers.forEach(p -> p.writeTo(contextMap));
}
@Override
public String get(String key) {
Map<String, String> contextMap = new HashMap<>();
providers.forEach(p -> p.readFrom(contextMap));
return contextMap.get(key);
}
@Override
public void clear() {
providers.forEach(p -> p.writeTo(new HashMap<>()));
}
}
六、Spring Boot 自动装配
你可以创建一个自动装配模块,用于注册所有上下文提供者。
示例配置类:
@Configuration
public class ContextAutoConfiguration {
@Bean
public ContextManager contextManager(List<ContextProvider> providers) {
return new DefaultContextManager(providers);
}
@Bean
public ContextProvider webContextProvider() {
return new WebContextProvider();
}
@Bean
@ConditionalOnClass(name = "org.apache.dubbo.rpc.RpcContext")
public ContextProvider rpcContextProvider() {
return new RpcContextProvider();
}
@Bean
public ContextProvider logContextProvider() {
return new LogContextProvider();
}
}
七、拦截器自动注入上下文(可选)
为了确保上下文在请求开始时就已加载,你可以编写一个拦截器自动注入上下文。
@Component
public class ContextInterceptor implements HandlerInterceptor {
@Autowired
private ContextManager contextManager;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String traceId = UUID.randomUUID().toString();
contextManager.set("traceId", traceId);
contextManager.set("userId", request.getHeader("X-User-ID"));
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
contextManager.clear();
}
}
八、使用方式(Controller 层)
@RestController
public class UserController {
@Autowired
private ContextManager contextManager;
@GetMapping("/user")
public String getCurrentUser() {
String userId = contextManager.get("userId");
String traceId = contextManager.get("traceId");
return String.format("User ID: %s, Trace ID: %s", userId, traceId);
}
}
九、异步任务中的自动传播(TTL 支持)
为了保证异步任务也能正确传递上下文,我们可以对 ContextManager
做一层封装:
@Component
public class TtlContextManager implements ContextManager {
private static final TransmittableThreadLocal<Map<String, String>> contextHolder = new TransmittableThreadLocal<>();
private final ContextManager delegate;
public TtlContextManager(ContextManager delegate) {
this.delegate = delegate;
}
@Override
public void set(String key, String value) {
Map<String, String> map = contextHolder.get();
if (map == null) {
map = new HashMap<>();
}
map.put(key, value);
contextHolder.set(map);
delegate.set(key, value);
}
@Override
public String get(String key) {
Map<String, String> map = contextHolder.get();
if (map != null) {
return map.get(key);
}
return delegate.get(key);
}
@Override
public void clear() {
contextHolder.remove();
delegate.clear();
}
}
然后替换默认的 ContextManager
Bean:
@Bean
public ContextManager contextManager(List<ContextProvider> providers) {
return new TtlContextManager(new DefaultContextManager(providers));
}
十、总结建议
场景 | 推荐方案 |
---|---|
多上下文来源统一管理 | 设计通用 ContextManager 接口抽象 |
异步任务上下文丢失 | 使用 TTL 包装上下文管理器 |
日志自动注入 | 结合 MDC 和 ContextManager |
Dubbo 微服务上下文透传 | 使用 RpcContextProvider |
Web 请求上下文 | 使用 WebContextProvider |
统一日志追踪 | Sleuth + MDC + ContextManager 联合使用 |
📌 参考链接