《Spring 中上下文传递的那些事儿》Part 8:构建统一上下文框架设计与实现(实战篇)

发布于:2025-07-13 ⋅ 阅读:(15) ⋅ 点赞:(0)

📝 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 联合使用

📌 参考链接


网站公告

今日签到

点亮在社区的每一天
去签到