Spring 全局上下文实现指南:单机→异步→分布式
一、单机上下文传递思路
在 Spring 中实现用户上下文传递,最常用且安全的方式是利用 ThreadLocal(线程本地变量)结合拦截器 / 过滤器。ThreadLocal 可以为每个线程存储独立的变量副本,正好匹配 HTTP 请求的线程模型(一个请求对应一个处理线程),能保证用户信息在当前请求的整个处理链路中可访问,且线程安全。
流程:
否
是
否
是
👤 用户发起HTTP请求
🛡️ 拦截器preHandle
🔍 解析请求头Token
✅ Token验证是否通过?
📌 从认证服务/JWT获取用户信息
🗂️ UserContextHolder.setUser存入ThreadLocal
🚀 放行请求到Controller
📤 Controller/Service获取用户信息
💼 执行业务逻辑
⚡ 是否有异步操作?
🧹 拦截器afterCompletion
🗑️ UserContextHolder.clear清除ThreadLocal
📩 返回响应给用户
📸 TaskDecorator捕获用户信息
🔌 异步线程注入上下文
⚙️ 异步线程执行业务
🧼 异步线程清除上下文
二、实现方案
1. 定义用户上下文工具类(核心)
首先创建一个基于 ThreadLocal 的工具类,用于存储和获取当前线程的用户信息:
java
import org.springframework.stereotype.Component;
/**
* 用户上下文工具类,用于存储和获取当前线程的用户信息
*/
@Component
public class UserContextHolder {
// ThreadLocal 存储用户信息,泛型可替换为你的实际用户实体类
private static final ThreadLocal<UserInfo> USER_CONTEXT = new ThreadLocal<>();
/**
* 设置当前线程的用户信息
*/
public static void setUser(UserInfo userInfo) {
USER_CONTEXT.set(userInfo);
}
/**
* 获取当前线程的用户信息
*/
public static UserInfo getUser() {
return USER_CONTEXT.get();
}
/**
* 清除当前线程的用户信息(关键:防止内存泄漏)
*/
public static void clear() {
USER_CONTEXT.remove();
}
// 简化的用户信息实体(你可替换为项目中的真实用户类)
public static class UserInfo {
private Long userId; // 用户ID
private String username; // 用户名
private String token; // 登录令牌
// 构造器、getter/setter
public UserInfo(Long userId, String username, String token) {
this.userId = userId;
this.username = username;
this.token = token;
}
// getter/setter 方法
public Long getUserId() { return userId; }
public void setUserId(Long userId) { this.userId = userId; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getToken() { return token; }
public void setToken(String token) { this.token = token; }
}
}
2. 实现拦截器 / 过滤器(自动注入用户上下文)
通过拦截器拦截所有请求,从请求头 / 参数中解析用户令牌,验证后将用户信息存入 ThreadLocal:
java
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 用户上下文拦截器:解析用户信息并注入ThreadLocal
*/
public class UserContextInterceptor implements HandlerInterceptor {
/**
* 请求处理前执行:解析用户信息并设置到上下文
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 1. 从请求头获取登录令牌(实际项目中可替换为Token、JWT等)
String token = request.getHeader("Authorization");
// 2. 验证令牌(实际项目中需调用认证服务/解析JWT获取用户信息)
if (token != null && !token.isEmpty()) {
// 模拟:验证token后获取用户信息(实际需替换为真实逻辑)
UserContextHolder.UserInfo userInfo = new UserContextHolder.UserInfo(1L, "zhangsan", token);
// 3. 将用户信息存入ThreadLocal
UserContextHolder.setUser(userInfo);
}
return true; // 放行请求
}
/**
* 请求处理完成后执行:清除ThreadLocal,防止内存泄漏
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
UserContextHolder.clear(); // 必须清除!
}
}
3. 注册拦截器(让 Spring 生效)
通过配置类注册拦截器,指定拦截的路径:
java
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* Web配置类:注册用户上下文拦截器
*/
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册拦截器,拦截所有请求(可根据业务调整拦截路径)
registry.addInterceptor(new UserContextInterceptor())
.addPathPatterns("/**") // 拦截所有路径
.excludePathPatterns("/login", "/register"); // 排除登录/注册接口
}
}
4. 业务层使用用户上下文
在 Controller/Service 中直接通过工具类获取用户信息,无需手动传递:
java
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 测试控制器:演示如何获取用户上下文信息
*/
@RestController
public class TestController {
@GetMapping("/user/info")
public String getUserInfo() {
// 从上下文获取当前登录用户信息
UserContextHolder.UserInfo user = UserContextHolder.getUser();
if (user != null) {
return "当前登录用户:" + user.getUsername() + ",用户ID:" + user.getUserId();
}
return "未登录";
}
@GetMapping("/order/create")
public String createOrder() {
// 业务层获取用户信息,无需参数传递
Long userId = UserContextHolder.getUser().getUserId();
return "用户" + userId + "创建订单成功";
}
}
进阶场景:异步线程的上下文传递
如果你的业务中有异步操作(如 @Async 注解),ThreadLocal 中的信息会丢失,需额外处理:
自定义异步任务执行器,传递上下文:
java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean("asyncExecutor")
public Executor asyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(20);
executor.setThreadNamePrefix("async-");
// 自定义线程装饰器:传递ThreadLocal上下文
executor.setTaskDecorator(runnable -> {
// 获取当前线程的用户上下文
UserContextHolder.UserInfo user = UserContextHolder.getUser();
return () -> {
try {
// 将上下文设置到异步线程中
UserContextHolder.setUser(user);
runnable.run();
} finally {
// 清除异步线程的上下文
UserContextHolder.clear();
}
};
});
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
}
异步方法中使用上下文:
java
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
@Service
public class AsyncService {
@Async("asyncExecutor")
public void asyncTask() {
// 异步线程中仍可获取用户上下文
UserContextHolder.UserInfo user = UserContextHolder.getUser();
System.out.println("异步任务获取用户:" + user.getUsername());
}
}
三、注意事项
- 必须清除 ThreadLocal :在
afterCompletion中调用clear(),否则线程池复用会导致信息串用、内存泄漏; - 异步场景处理 :默认 ThreadLocal 不支持异步,需通过
TaskDecorator手动传递上下文; - 安全性:用户信息仅在当前请求线程有效,不同请求之间隔离,无需担心线程安全问题;
- 替代方案:如果是 Spring Cloud 微服务场景,可结合 Sleuth/Spring Context 实现跨服务的上下文传递(需序列化用户信息到请求头)。
四、打造一个通用、可扩展、线程安全的全局上下文框架
设计思路
一个健壮的全局上下文需要满足:
- 多维度存储:支持用户信息、请求信息、租户信息等多类数据;
- 线程安全:基于 ThreadLocal 实现,异步场景可传递;
- 可扩展:支持自定义上下文字段,不侵入业务代码;
- 自动清理:防止内存泄漏,支持手动 / 自动清除;
- 兼容性:适配同步 / 异步、Web / 非 Web 场景。
架构图:
否
是
用户请求
GlobalContextInterceptor
(拦截器)
GlobalContextHolder.init()
初始化上下文
填充基础数据
(用户/请求/租户)
业务代码调用
GlobalContextHolder获取数据
是否异步操作?
Interceptor.afterCompletion
清除上下文
ContextDecorator
传递上下文到异步线程
异步线程执行业务
获取上下文数据
异步线程执行完
清除上下文
返回响应
实现方案
1. 定义上下文核心模型(可扩展)
先定义上下文的基础结构,支持多维度数据存储:
java
import lombok.Data;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 全局上下文核心模型:存储所有需全局共享的信息
*/
@Data
public class GlobalContext {
// 基础字段:用户信息(可替换为项目真实用户类)
private UserInfo userInfo;
// 基础字段:请求信息
private RequestInfo requestInfo;
// 扩展字段:自定义业务数据(键值对,兼容任意类型)
private Map<String, Object> extData = new ConcurrentHashMap<>();
// ==================== 内置子模型 ====================
/**
* 用户信息子模型
*/
@Data
public static class UserInfo {
private Long userId; // 用户ID
private String username; // 用户名
private String token; // 登录令牌
private Long tenantId; // 租户ID(多租户场景)
}
/**
* 请求信息子模型
*/
@Data
public static class RequestInfo {
private String requestId; // 全局请求ID(链路追踪)
private String ip; // 请求IP
private String url; // 请求URL
private Long timestamp; // 请求时间戳
}
// ==================== 扩展方法 ====================
/**
* 设置自定义扩展字段
*/
public void setExtData(String key, Object value) {
this.extData.put(key, value);
}
/**
* 获取自定义扩展字段
*/
@SuppressWarnings("unchecked")
public <T> T getExtData(String key) {
return (T) this.extData.get(key);
}
/**
* 移除自定义扩展字段
*/
public void removeExtData(String key) {
this.extData.remove(key);
}
}
2. 实现上下文持有器(核心:ThreadLocal + 异步传递)
封装 ThreadLocal 操作,提供全局访问入口,并支持异步上下文传递:
java
import org.springframework.core.task.TaskDecorator;
import org.springframework.stereotype.Component;
/**
* 全局上下文持有器:核心工具类,封装ThreadLocal操作
*/
@Component
public class GlobalContextHolder {
// 核心:ThreadLocal存储全局上下文(每个线程独立副本)
private static final ThreadLocal<GlobalContext> CONTEXT_HOLDER = new ThreadLocal<>();
// ==================== 基础操作 ====================
/**
* 初始化上下文(请求开始时调用)
*/
public static void init() {
CONTEXT_HOLDER.set(new GlobalContext());
}
/**
* 获取当前线程的全局上下文
*/
public static GlobalContext getContext() {
GlobalContext context = CONTEXT_HOLDER.get();
if (context == null) {
context = new GlobalContext();
CONTEXT_HOLDER.set(context);
}
return context;
}
/**
* 清除当前线程的上下文(必须调用,防止内存泄漏)
*/
public static void clear() {
CONTEXT_HOLDER.remove();
}
// ==================== 快捷操作(简化业务调用) ====================
/**
* 快捷设置用户信息
*/
public static void setUserInfo(GlobalContext.UserInfo userInfo) {
getContext().setUserInfo(userInfo);
}
/**
* 快捷获取用户信息
*/
public static GlobalContext.UserInfo getUserInfo() {
return getContext().getUserInfo();
}
/**
* 快捷设置请求信息
*/
public static void setRequestInfo(GlobalContext.RequestInfo requestInfo) {
getContext().setRequestInfo(requestInfo);
}
/**
* 快捷获取请求信息
*/
public static GlobalContext.RequestInfo getRequestInfo() {
return getContext().getRequestInfo();
}
/**
* 快捷设置扩展字段
*/
public static void setExtData(String key, Object value) {
getContext().setExtData(key, value);
}
/**
* 快捷获取扩展字段
*/
public static <T> T getExtData(String key) {
return getContext().getExtData(key);
}
// ==================== 异步上下文传递(关键) ====================
/**
* 异步任务装饰器:实现主线程上下文向异步线程传递
* 配合@Async使用,需在线程池配置中指定
*/
public static TaskDecorator getContextDecorator() {
return runnable -> {
// 捕获主线程上下文
GlobalContext parentContext = GlobalContextHolder.getContext();
return () -> {
try {
// 将主线程上下文设置到异步线程
CONTEXT_HOLDER.set(parentContext);
runnable.run();
} finally {
// 异步线程执行完后清除上下文
GlobalContextHolder.clear();
}
};
};
}
}
3. 自动注入上下文(Web 场景)
通过拦截器自动初始化 / 填充 / 清理上下文,无需业务代码手动调用:
java
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.UUID;
/**
* 全局上下文拦截器:Web场景自动注入上下文数据
*/
public class GlobalContextInterceptor implements HandlerInterceptor {
/**
* 请求处理前:初始化上下文 + 填充基础数据
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 1. 初始化上下文
GlobalContextHolder.init();
// 2. 填充请求信息
GlobalContext.RequestInfo requestInfo = new GlobalContext.RequestInfo();
requestInfo.setRequestId(UUID.randomUUID().toString()); // 生成全局请求ID
requestInfo.setIp(getClientIp(request)); // 获取客户端IP
requestInfo.setUrl(request.getRequestURI()); // 获取请求URL
requestInfo.setTimestamp(System.currentTimeMillis()); // 请求时间戳
GlobalContextHolder.setRequestInfo(requestInfo);
// 3. 填充用户信息(从Token解析,替换为真实逻辑)
String token = request.getHeader("Authorization");
if (token != null && !token.isEmpty()) {
GlobalContext.UserInfo userInfo = new GlobalContext.UserInfo();
userInfo.setUserId(1L); // 模拟从Token解析的用户ID
userInfo.setUsername("admin"); // 模拟用户名
userInfo.setToken(token); // 登录令牌
userInfo.setTenantId(1001L); // 模拟租户ID
GlobalContextHolder.setUserInfo(userInfo);
}
// 4. 填充自定义扩展字段(示例:业务标识)
GlobalContextHolder.setExtData("biz_type", "order_create");
return true;
}
/**
* 请求完成后:清除上下文,防止内存泄漏
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
GlobalContextHolder.clear();
}
// 工具方法:获取真实客户端IP
private String getClientIp(HttpServletRequest request) {
String ip = request.getHeader("X-Forwarded-For");
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
}
4. 配置拦截器 + 异步线程池
让上下文在 Web 场景和异步场景都生效:
java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
/**
* 全局上下文配置类:注册拦截器 + 配置异步线程池
*/
@Configuration
@EnableAsync
public class GlobalContextConfig implements WebMvcConfigurer {
// 1. 注册Web拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new GlobalContextInterceptor())
.addPathPatterns("/**") // 拦截所有请求
.excludePathPatterns("/login"); // 排除登录接口
}
// 2. 配置异步线程池(支持上下文传递)
@Bean("asyncExecutor")
public Executor asyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(20);
executor.setThreadNamePrefix("global-context-async-");
// 设置上下文装饰器,实现异步线程上下文传递
executor.setTaskDecorator(GlobalContextHolder.getContextDecorator());
// 拒绝策略:由调用方线程执行,避免任务丢失
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
}
5. 业务中使用全局上下文(极简调用)
在 Controller/Service/ 异步方法中,一行代码即可获取任意上下文数据:
java
import org.springframework.scheduling.annotation.Async;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class BusinessController {
// 同步场景使用
@GetMapping("/order/create")
public String createOrder() {
// 1. 获取用户信息
Long userId = GlobalContextHolder.getUserInfo().getUserId();
// 2. 获取请求ID(链路追踪)
String requestId = GlobalContextHolder.getRequestInfo().getRequestId();
// 3. 获取自定义扩展字段
String bizType = GlobalContextHolder.getExtData("biz_type");
return String.format("用户[%s]创建订单,请求ID[%s],业务类型[%s]", userId, requestId, bizType);
}
// 异步场景使用
@Async("asyncExecutor")
public void asyncHandleOrder() {
// 异步线程中仍可获取上下文
Long tenantId = GlobalContextHolder.getUserInfo().getTenantId();
String ip = GlobalContextHolder.getRequestInfo().getIp();
System.out.println("异步处理:租户ID=" + tenantId + ",请求IP=" + ip);
}
}
全局上下文扩展能力
多租户场景 :已内置 tenantId 字段,可直接使用;
链路追踪 :requestId 可对接 SkyWalking/Zipkin 等链路追踪工具;
自定义业务字段 :通过 setExtData/getExtData 存储任意业务数据(如订单 ID、商品 ID);
非 Web 场景:
如定时任务,可手动调用 GlobalContextHolder.init() 初始化上下文,执行完后调用 clear();
java
// 定时任务中手动使用上下文
public void scheduledTask() {
try {
GlobalContextHolder.init();
// 设置自定义上下文
GlobalContextHolder.setExtData("task_name", "order_stat");
// 执行业务逻辑
System.out.println(GlobalContextHolder.getExtData("task_name"));
} finally {
GlobalContextHolder.clear(); // 必须清除
}
}
注意事项
内存泄漏防护:
Web 场景:拦截器 afterCompletion 自动清除;
异步场景:装饰器 finally 块自动清除;
非 Web 场景:必须在 finally 块中手动调用 clear()。
线程安全:
ThreadLocal 保证线程隔离,多线程不会串用上下文;
扩展字段使用 ConcurrentHashMap,支持并发读写。
性能优化:
上下文仅初始化一次(init()),避免重复创建对象;
异步装饰器仅复制上下文引用,无深拷贝,性能损耗可忽略。
总结:
核心架构:基于 ThreadLocal 实现线程隔离,通过拦截器自动注入、装饰器支持异步传递,形成完整的上下文生命周期管理;
使用体验:业务代码仅需调用 GlobalContextHolder 静态方法,零侵入、极简调用;
扩展性:内置基础字段 + 自定义扩展字段,适配用户、请求、租户、自定义业务等全场景。
五、分布式实现上下文传递
在分布式 / 微服务环境下,如何让全局上下文(用户信息、请求 ID、租户 ID 等)跨服务、跨进程传递,这是分布式系统中链路追踪和上下文统一的核心需求,也是单机上下文方案的重要扩展。
思路
分布式环境下的上下文传递,核心是将上下文数据序列化后随网络请求传递,再在接收端反序列化恢复上下文,整体流程:
plaintext
服务A(主线程)→ 序列化上下文到请求头 → 网络请求 → 服务B(接收请求)→ 反序列化上下文 → 恢复到ThreadLocal
主要解决 3 个问题:
- 数据序列化:上下文需能序列化为字符串(如 JSON),适配 HTTP 请求头 / 参数;
- 跨服务传递:通过 HTTP 请求头、RPC 元数据等载体传递;
- 全链路一致:保证请求 ID、租户 ID 等核心字段在所有服务中统一。
流程图
渲染错误: Mermaid 渲染失败: Parse error on line 31: ...troke-width:2px %% 用户请求→网关 加粗 linkS -----------------------^ Expecting 'SEMI', 'NEWLINE', 'SPACE', 'EOF', 'COLON', 'STYLE', 'NUM', 'COMMA', 'NODE_STRING', 'UNIT', 'BRKT', 'PCT', got 'UNICODE_TEXT'
方案
以下方案基于 Spring Cloud + OpenFeign(HTTP 调用)为例,适配主流微服务架构:
1. 改造全局上下文:支持序列化 / 反序列化
首先让 GlobalContext 支持 JSON 序列化,方便跨服务传递:
java
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Data;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Data
public class GlobalContext {
// 基础字段
private UserInfo userInfo;
private RequestInfo requestInfo;
private Map<String, Object> extData = new ConcurrentHashMap<>();
// 静态ObjectMapper(全局复用)
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
// ==================== 序列化/反序列化方法 ====================
/**
* 上下文序列化为JSON字符串(用于传递)
*/
public String toJson() {
try {
return OBJECT_MAPPER.writeValueAsString(this);
} catch (JsonProcessingException e) {
throw new RuntimeException("上下文序列化失败", e);
}
}
/**
* 从JSON字符串反序列化恢复上下文
*/
public static GlobalContext fromJson(String json) {
if (json == null || json.isEmpty()) {
return new GlobalContext();
}
try {
return OBJECT_MAPPER.readValue(json, GlobalContext.class);
} catch (JsonProcessingException e) {
throw new RuntimeException("上下文反序列化失败", e);
}
}
// 子模型保持不变(UserInfo/RequestInfo)
@Data
public static class UserInfo {
private Long userId;
private String username;
private Long tenantId;
private String token;
}
@Data
public static class RequestInfo {
private String requestId; // 全链路唯一请求ID
private String ip;
private Long timestamp;
}
}
2. 实现 Feign 拦截器:请求发送端传递上下文
通过 Feign 拦截器,在服务 A 调用服务 B 时,自动将上下文序列化后放入 HTTP 请求头:
java
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springframework.stereotype.Component;
/**
* Feign请求拦截器:发送请求时传递分布式上下文
*/
@Component
public class FeignContextInterceptor implements RequestInterceptor {
// 自定义请求头:存储序列化后的上下文(可自定义名称)
private static final String CONTEXT_HEADER = "X-Global-Context";
// 核心:请求发送前,将上下文写入请求头
@Override
public void apply(RequestTemplate template) {
// 1. 获取当前线程的全局上下文
GlobalContext context = GlobalContextHolder.getContext();
if (context == null) {
return;
}
// 2. 序列化为JSON字符串,放入请求头
String contextJson = context.toJson();
template.header(CONTEXT_HEADER, contextJson);
}
}
3. 实现 Web 拦截器:请求接收端恢复上下文
服务 B 接收到请求后,通过 Web 拦截器从请求头中解析上下文,恢复到 ThreadLocal:
java
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.UUID;
/**
* 分布式上下文拦截器:接收请求时恢复上下文
*/
public class DistributedContextInterceptor implements HandlerInterceptor {
private static final String CONTEXT_HEADER = "X-Global-Context";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 1. 初始化上下文
GlobalContextHolder.init();
// 2. 从请求头获取序列化的上下文
String contextJson = request.getHeader(CONTEXT_HEADER);
if (contextJson != null && !contextJson.isEmpty()) {
// 3. 反序列化恢复上下文
GlobalContext context = GlobalContext.fromJson(contextJson);
// 4. 设置到当前线程的ThreadLocal
GlobalContextHolder.getContext().setUserInfo(context.getUserInfo());
GlobalContextHolder.getContext().setRequestInfo(context.getRequestInfo());
GlobalContextHolder.getContext().setExtData(context.getExtData());
} else {
// 兜底:如果是链路入口服务(无上游),生成请求ID
GlobalContext.RequestInfo requestInfo = new GlobalContext.RequestInfo();
requestInfo.setRequestId(UUID.randomUUID().toString());
requestInfo.setIp(getClientIp(request));
requestInfo.setTimestamp(System.currentTimeMillis());
GlobalContextHolder.setRequestInfo(requestInfo);
}
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 清除上下文,防止内存泄漏
GlobalContextHolder.clear();
}
// 工具方法:获取客户端IP
private String getClientIp(HttpServletRequest request) {
String ip = request.getHeader("X-Forwarded-For");
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
}
4. 注册拦截器:让分布式上下文生效
java
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* 分布式上下文配置类
*/
@Configuration
public class DistributedContextConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册分布式上下文拦截器,优先级高于业务拦截器
registry.addInterceptor(new DistributedContextInterceptor())
.addPathPatterns("/**")
.excludePathPatterns("/login", "/health");
}
}
5. RPC 框架适配(以 Dubbo 为例)
如果使用 Dubbo 等 RPC 框架,需通过 Dubbo 的过滤器传递上下文(原理同 Feign):
java
import org.apache.dubbo.common.constants.CommonConstants;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.rpc.*;
/**
* Dubbo上下文传递过滤器:RPC调用时传递上下文
*/
@Activate(group = {CommonConstants.PROVIDER, CommonConstants.CONSUMER})
public class DubboContextFilter implements Filter {
private static final String CONTEXT_ATTACHMENT = "X-Global-Context";
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
// 消费端:将上下文放入RPC附件
if (RpcContext.getServiceContext().isConsumerSide()) {
GlobalContext context = GlobalContextHolder.getContext();
invocation.getAttachments().put(CONTEXT_ATTACHMENT, context.toJson());
}
// 服务端:从RPC附件恢复上下文
if (RpcContext.getServiceContext().isProviderSide()) {
String contextJson = invocation.getAttachment(CONTEXT_ATTACHMENT);
if (contextJson != null) {
GlobalContext context = GlobalContext.fromJson(contextJson);
GlobalContextHolder.getContext().setUserInfo(context.getUserInfo());
GlobalContextHolder.getContext().setRequestInfo(context.getRequestInfo());
}
}
try {
return invoker.invoke(invocation);
} finally {
// 服务端执行完清除上下文
if (RpcContext.getServiceContext().isProviderSide()) {
GlobalContextHolder.clear();
}
}
}
}
关键点
精简传递数据:
不要传递完整上下文,仅传递核心字段(requestId、userId、tenantId),减少网络开销;
示例:只传递 requestId 和 tenantId,而非整个 GlobalContext。
全链路请求 ID 统一:
链路入口服务(如网关)生成唯一 requestId,后续所有服务复用该 ID,便于日志追踪;
结合 SLF4J MDC 打印请求 ID:
java
// 在拦截器中设置MDC
MDC.put("requestId", GlobalContextHolder.getRequestInfo().getRequestId());
// 日志配置文件中添加 %X{requestId},即可打印请求ID
安全性处理:
敏感信息(如 token)避免明文传递,可通过网关统一解析 token,仅传递 userId/tenantId;
对请求头中的上下文做合法性校验,防止伪造。
异步线程传递:
分布式场景下的异步线程,仍需使用之前的 TaskDecorator 装饰器,保证线程内上下文不丢失。
总结
- 核心工具 :基于
ThreadLocal的UserContextHolder是实现上下文传递的核心,能为每个请求线程存储独立的用户信息; - 自动注入:通过拦截器在请求处理前注入用户信息,请求结束后清除,保证线程安全;
- 异步兼容:异步场景需自定义任务执行器,手动传递 ThreadLocal 上下文,避免信息丢失。
这种方式能让你在任意业务层代码中通过 UserContextHolder.getUser() 直接获取当前登录用户信息,无需在方法参数中层层传递,是 Spring 中实现用户上下文传递的标准最佳实践。