ThreadLocal解决一个线程里面跨层传递问题

Problems&Solutions

登录用户的id在拦截器里面被解析但是Services拿不到,所以要ThreadLocal来解释:

拦截器在线程里面放下empId=" "

Service里面打开这个ThreadLocal来获取到这个empId

项目里面有一个BaseContext类简单包装了ThreadLocal

java 复制代码
package com.sky.context;

public class BaseContext {

    // 创建一个 ThreadLocal,用来存当前登录用户的ID
    private static final ThreadLocal<Long> threadLocal = new ThreadLocal<>();

    // 放数据:当前线程存一个值
    public static void setCurrentId(Long id) {
        threadLocal.set(id);
    }

    // 取数据:当前线程取出之前存的值
    public static Long getCurrentId() {
        return threadLocal.get();
    }

    // 清除数据:当前线程移除值(防止内存泄漏,后面讲)
    public static void removeCurrentId() {
        threadLocal.remove();
    }
}

完整的传递流程:

第 1 步:拦截器里 set(放进去)

复制代码
@Component
public class JwtTokenAdminInterceptor implements HandlerInterceptor {

    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response,
                             Object handler) {

        // 1. 从请求头取 token
        String token = request.getHeader("token");

        // 2. 解析 token,拿到 empId
        Claims claims = JwtUtil.parseJWT(jwtProperties.getAdminSecretKey(), token);
        Long empId = Long.valueOf(claims.get(JwtClaimsConstant.EMP_ID).toString());
        log.info("当前员工id:{}", empId);

        // 3. ★ 关键:把 empId 放进当前线程的 ThreadLocal
        BaseContext.setCurrentId(empId);

        return true;  // 放行
    }
}

执行完这一步,当前线程的储物柜里就有了 empId=1。

第 2 步:Service 里 get(取出来)

复制代码
@Service
public class EmployeeServiceImpl implements EmployeeService {

    public void save(EmployeeDTO employeeDTO) {
        Employee employee = new Employee();
        BeanUtils.copyProperties(employeeDTO, employee);

        employee.setStatus(StatusConstant.ENABLE);
        employee.setPassword(DigestUtils.md5DigestAsHex(...));
        employee.setCreateTime(LocalDateTime.now());
        employee.setUpdateTime(LocalDateTime.now());

        // ★ 关键:从当前线程的 ThreadLocal 里取出 empId
        employee.setCreateUser(BaseContext.getCurrentId());  // 取出 1
        employee.setUpdateUser(BaseContext.getCurrentId());  // 取出 1

        employeeMapper.insert(employee);
    }
}

因为还是同一个线程,所以 getCurrentId() 就能拿到拦截器里放的 1。

第 3 步:请求结束后 remove(清理)

复制代码
// 拦截器的 afterCompletion 方法,请求结束后执行
public void afterCompletion(HttpServletRequest request,
                            HttpServletResponse response,
                            Object handler,
                            Exception ex) {
    // ★ 清理当前线程的 ThreadLocal,防止内存泄漏
    BaseContext.removeCurrentId();
}

五、为什么必须 remove?

Tomcat 使用线程池,线程不会销毁,会被重复使用:

复制代码
请求1(admin)→ 线程A → set(1) → 处理完 → 线程A回到线程池
请求2(新来的)→ 线程A又被拿出来用 → 如果没remove,getCurrentId() 还能拿到 1!← 脏数据!

所以必须在请求结束后清理,否则下一个复用这个线程的请求可能拿到上一个用户的 ID,造成数据混乱。


六、完整流程图

复制代码
前端请求(带token)
    │
    ▼
【拦截器 preHandle】
    │
    ├── 解析token → empId=1
    ├── BaseContext.setCurrentId(1)   ← 放进线程A的储物柜
    └── 放行
    │
    ▼
【Controller.save()】
    │
    └── employeeService.save(dto)
    │
    ▼
【Service.save()】
    │
    ├── 组装 employee 对象
    ├── BaseContext.getCurrentId()   ← 从线程A的储物柜取出 1
    ├── setCreateUser(1)
    └── employeeMapper.insert(employee)
    │
    ▼
【Mapper.insert()】→ 数据库
    │
    ▼
【拦截器 afterCompletion】
    │
    └── BaseContext.removeCurrentId()  ← 清理线程A的储物柜
    │
    ▼
响应返回前端

七、关键要点总结

表格

问题 答案
ThreadLocal 是什么 每个线程的私人储物柜,线程间数据隔离
为什么能用它传值 一次请求从头到尾在同一个线程执行
在哪放 拦截器 preHandle 里 setCurrentId(empId)
在哪取 Service 里 getCurrentId()
为什么要清理 线程池会复用线程,不清理会有脏数据
在哪清理 拦截器 afterCompletion 里 removeCurrentId()
需不需要改方法参数 不需要!这就是 ThreadLocal 的优势
相关推荐
鸽鸽程序猿1 分钟前
【项目】【在线判题系统】公共功能
java·spring boot·maven
高频因子挖掘机1 分钟前
多股票行情为什么容易对不齐?用 Pandas MultiIndex 管理标的、日期与缺失值
后端·github·api
一只笨菜鸟9 分钟前
IDEA如何让多个项目同时显示
java·ide·intellij-idea
程序猿乐锅12 分钟前
【从0-1学习Python | 第六篇】 Python 函数:定义、参数与返回值
开发语言·python·学习
G018_star sky♬14 分钟前
java中的字符串常见方法
java·开发语言
熊猫钓鱼>_>16 分钟前
Kotlin Multiplatform for OpenHarmony 实战:kotlinx-serialization JSON 适配
开发语言·华为·kotlin·华为云·json·harmonyos·code
高频因子挖掘机20 分钟前
Python 量化数据接口设计实战:用数据契约解决字段混乱与回测不一致
后端·github·api
.Bryce.1 小时前
你了解 Blackboard(黑板系统)吗?听说过基于黑板系统实现的智能体吗?
java·网络·gpt·php·agent·codex·黑板系统
银河技术1 小时前
从脚本到 Operator:用 Go 构建可靠的微服务自动扩缩容控制器
开发语言·微服务·golang
GIS数据转换器3 小时前
地下空间综合管廊运营管理平台
大数据·开发语言·人工智能·经验分享·perl