以下是实现将登录信息存储在 ThreadLocal
中的完整方案,以用户身份信息为例:
1. 创建用户上下文工具类
设计一个静态工具类 UserContext
,用于操作 ThreadLocal
中的用户信息。
public class UserContext {// 定义 ThreadLocal,存储用户对象private static final ThreadLocal<User> USER_HOLDER = new ThreadLocal<>();/*** 设置当前线程的用户信息*/public static void setUser(User user) {USER_HOLDER.set(user);}/*** 获取当前线程的用户信息*/public static User getUser() {return USER_HOLDER.get();}/*** 清除当前线程的用户信息(防止内存泄漏)*/public static void clear() {USER_HOLDER.remove();}
}
2. 用户登录时存储信息
在用户登录成功后,将用户信息存入 ThreadLocal
。通常在 拦截器、过滤器或认证服务 中实现。
示例:登录认证后存储用户信息
public class LoginService {public void login(String username, String password) {// 1. 校验用户名密码User user = checkUser(username, password);// 2. 将用户信息存入 ThreadLocalUserContext.setUser(user);}
}
3. 在业务层获取用户信息
后续业务代码(如 Controller、Service、DAO)可直接从 UserContext
中获取用户信息,无需传参。
示例:Controller 中获取当前用户
@RestController
public class OrderController {@GetMapping("/order")public String createOrder() {// 直接从 ThreadLocal 获取用户信息User currentUser = UserContext.getUser();return "用户 " + currentUser.getUsername() + " 创建订单成功";}
}
4. 请求结束时清理数据
为了避免内存泄漏(尤其是使用线程池时),必须在请求处理完成后清理 ThreadLocal
。
可通过 拦截器、过滤器或 AOP 实现自动清理。
示例:Spring 拦截器中自动清理
public class ClearThreadLocalInterceptor implements HandlerInterceptor {@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {// 请求结束时清理 ThreadLocalUserContext.clear();}
}
注册拦截器到 Spring:
@Configuration
public class WebConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new ClearThreadLocalInterceptor());}
}
5. 用户信息存储的详细流程
以一次 HTTP 请求为例:
- 用户登录:校验成功后,调用
UserContext.setUser(user)
。 - 后续请求处理:任何业务代码通过
UserContext.getUser()
直接获取用户。 - 请求结束:拦截器自动调用
UserContext.clear()
。
6. 关键注意事项
6.1 内存泄漏问题
- 必须清理:如果使用线程池(如 Tomcat 容器的线程池),线程会被复用,残留的
ThreadLocal
数据可能导致信息错乱或内存泄漏。 - 防御性代码:在
finally
块中确保清理:try {// 业务逻辑... } finally {UserContext.clear(); }
6.2 异步任务中的问题
如果业务代码中使用异步线程(如 @Async
、CompletableFuture
),子线程无法直接继承父线程的 ThreadLocal
数据。
解决方案:手动传递用户信息或使用 InheritableThreadLocal
(但需谨慎,线程池中可能不适用)。
7. 完整代码示例
User 类(实体)
public class User {private Long id;private String username;// 其他字段、Getter/Setter...
}
拦截器配置(Spring Boot)
public class UserInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {// 从请求头或 Cookie 中获取 TokenString token = request.getHeader("Authorization");if (token != null) {User user = parseUserFromToken(token); // 解析 Token 获取用户UserContext.setUser(user);}return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {UserContext.clear();}
}
总结
- 优点:避免在方法间传递用户参数,代码更简洁。
- 关键点:登录时存储 → 业务中获取 → 请求结束清理。
- 陷阱:异步场景需特殊处理,务必清理
ThreadLocal
。
通过这种方式,你可以安全地将用户登录信息存储在 ThreadLocal
中,并在整个请求链路中共享数据。