ThreadLocal 使用场景
用户会话信息
public class UserContext {
private static ThreadLocal<String> userHolder = ThreadLocal.withInitial(() -> null);
public static void setUser(String user) {
userHolder.set(user);
}
public static String getUser() {
return userHolder.get();
}
public static void clear() {
userHolder.remove();
}
}
UserContext.setUser("UserA");
String currentUser = UserContext.getUser();
System.out.println("Current User: " + currentUser);
UserContext.clear();
格式化工具
public class DateFormatter {
private static ThreadLocal<SimpleDateFormat> dateFormatHolder = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
public static String format(Date date) {
return dateFormatHolder.get().format(date);
}
}
String formattedDate = DateFormatter.format(new Date());
System.out.println("Formatted Date: " + formattedDate);
上下文日志信息传递
public class LogContext {
private static ThreadLocal<String> requestIdHolder = ThreadLocal.withInitial(() -> null);
public static void setRequestId(String requestId) {
requestIdHolder.set(requestId);
}
public static String getRequestId() {
return requestIdHolder.get();
}
public static void clear() {
requestIdHolder.remove();
}
}
LogContext.setRequestId("123456");
String requestId = LogContext.getRequestId();
System.out.println("Request ID: " + requestId);
LogContext.clear();
TransmittableThreadLocal (TTL)
TTL实现原理
- 上下文拷贝:在任务提交时,TTL 会拷贝当前线程的上下文到任务中。
- 任务执行前设置上下文:在任务执行前,TTL 会将拷贝的上下文设置到当前线程中。
- 任务执行后清理上下文:任务执行完毕后,TTL 会清理线程中的上下文,防止内存泄漏。

TTL使用场景
- 分布式追踪:在分布式系统中传递追踪 ID,方便日志的关联和问题排查。
- 事务管理:在分布式事务中传递事务上下文,确保事务的一致性。
- 上下文信息传递:在多线程环境中传递用户会话、请求上下文等信息。
TTL与ThreadLocal对比
| Feature | ThreadLocal | TTL |
|---|
| 上下文传递 | 仅在当前线程内存储, 无法跨线程传递 | 能够在线程池和多线程框架中传递上下文信息 |
| 线程复用支持 | 线程池复用线程时无法保证变量一致性 | 支持线程池复用, 确保变量在任务间传递和保持一致 |
| 无侵入性 | 手动管理变量设置和清除 | 替换ThreadLocal即可自动管理上下文传递和清除 |
| 集成方便 | 适用于简单线程环境 | 可与各种线程池和多线程框架无缝集成 |
| 使用场景 | 适用于单线程或不用跨线程传递上下文 | 适用于复杂多线程环境, 特别是跨线程传递上下文 |
TTL实战
- 将用户登录后的信息保存在上下文变量中,并进行跨线程之间传递
- 用户登录之后会返回 token,之后的请求将会带上这个 token,当然 token 中会携带有用户的信息,
- 所有请求最先经过网关的过滤器 AuthFilter,在过滤器中用户信息放到请求头,
- 所有请求经过网关后会来到自定义请求头拦截器 HeaderInterceptor,
- 在拦截器中拿出请求头中的用户信息放到 TTL 中,链路上的服务可以直接从 TTL 中取出用户信息

@Component
public class AuthFilter implements GlobalFilter, Ordered {
private static final Logger log = LoggerFactory.getLogger(AuthFilter.class);
private static final String BEGIN_VISIT_TIME = "begin_visit_time";
@Autowired
private IgnoreWhiteProperties ignoreWhite;
@Autowired
private RedisService redisService;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
ServerHttpRequest.Builder mutate = request.mutate();
String url = request.getURI().getPath();
if (StringUtils.matches(url, ignoreWhite.getWhites())) {
return chain.filter(exchange);
}
String token = getToken(request);
if (StringUtils.isEmpty(token)) {
return unauthorizedResponse(exchange, "令牌不能为空");
}
Claims claims = JwtUtils.parseToken(token);
if (claims == null) {
return unauthorizedResponse(exchange, "令牌已过期或验证不正确!");
}
String userkey = JwtUtils.getUserKey(claims);
boolean islogin = redisService.hasKey(getTokenKey(userkey));
if (!islogin) {
return unauthorizedResponse(exchange, "登录状态已过期");
}
String userid = JwtUtils.getUserId(claims);
String username = JwtUtils.getUserName(claims);
if (StringUtils.isEmpty(userid) || StringUtils.isEmpty(username)) {
return unauthorizedResponse(exchange, "令牌验证失败");
}
addHeader(mutate, SecurityConstants.USER_KEY, userkey);
addHeader(mutate, SecurityConstants.DETAILS_USER_ID, userid);
addHeader(mutate, SecurityConstants.DETAILS_USERNAME, username);
removeHeader(mutate, SecurityConstants.FROM_SOURCE);
exchange.getAttributes().put(BEGIN_VISIT_TIME, System.currentTimeMillis());
return chain.filter(exchange.mutate().request(mutate.build()).build());
}
private void addHeader(ServerHttpRequest.Builder mutate, String name, Object value) {
if (value == null) {
return;
}
String valueStr = value.toString();
}
- 在过滤器
**AuthFilter**中将用户信息放到请求头

public class HeaderInterceptor implements AsyncHandlerInterceptor {
private static final Set<String> EXEMPTED_PATHS = new HashSet<>();
static {
EXEMPTED_PATHS.add("/system/user/getInfo");
EXEMPTED_PATHS.add("/project/statistics");
EXEMPTED_PATHS.add("/project/doing");
EXEMPTED_PATHS.add("/project/queryMyTaskList");
EXEMPTED_PATHS.add("/project/select");
EXEMPTED_PATHS.add("/system/menu/getRouters");
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (!(handler instanceof HandlerMethod)) {
return true;
}
SecurityContextHolder.setUserId(ServletUtils.getHeader(request, SecurityConstants.DETAILS_USER_ID));
SecurityContextHolder.setUserName(ServletUtils.getHeader(request, SecurityConstants.DETAILS_USERNAME));
SecurityContextHolder.setUserKey(ServletUtils.getHeader(request, SecurityConstants.USER_KEY));
String token = SecurityUtils.getToken();
if (StringUtils.isNotEmpty(token)) {
LoginUser loginUser = AuthUtil.getLoginUser(token);
if (StringUtils.isNotNull(loginUser)) {
AuthUtil.verifyLoginUserExpire(loginUser);
SecurityContextHolder.set(SecurityConstants.LOGIN_USER, loginUser);
}
} else {
String requestURI = request.getRequestURI();
if (isExemptedPath(requestURI)) {
LoginUser defaultLoginUser = createDefaultLoginUser();
SecurityContextHolder.set(SecurityConstants.LOGIN_USER, defaultLoginUser);
}
}
return true;
}
private boolean isExemptedPath(String requestURI) {
return EXEMPTED_PATHS.stream().anyMatch(requestURI::startsWith);
}
private LoginUser createDefaultLoginUser() {
LoginUser defaultLoginUser = new LoginUser();
defaultLoginUser.setUserId(173L);
defaultLoginUser.setUsername(Constants.DEMO_ACCOUNT);
SysUser demoSysUser = new SysUser();
demoSysUser.setUserId(173L);
demoSysUser.setUserName(Constants.DEMO_ACCOUNT);
demoSysUser.setDeptId(100L);
demoSysUser.setStatus("0");
defaultLoginUser.setUser(demoSysUser);
return defaultLoginUser;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
SecurityContextHolder.remove();
}
}
