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 是什么 每个线程的私人储物柜,线程间数据隔离
为什么能用它传值 一次请求从头到尾在同一个线程执行
在哪放 拦截器 preHandlesetCurrentId(empId)
在哪取 Service 里 getCurrentId()
为什么要清理 线程池会复用线程,不清理会有脏数据
在哪清理 拦截器 afterCompletionremoveCurrentId()
需不需要改方法参数 不需要!这就是 ThreadLocal 的优势
相关推荐
泡海椒1 小时前
低代码规则开发:JQuick-Java声明式配置驱动开发落地教程
java·低代码·rxjava
Lost of 程序猿2 小时前
ASP.NET Core API 版本管理与契约演进:船岸版本碎片化下的兼容性工程
后端·asp.net
变与不变8062 小时前
引用类型、内存原理与对象
开发语言·前端·javascript
wuyk5552 小时前
从零吃透 MQTT 通信|第 5 章 MQTT 心跳保活机制深度优化,断线检测与智能自动重连
c语言·开发语言·stm32·学习
吃饱了得干活2 小时前
Redis 从单机到集群:持久化、主从复制、哨兵与集群完全指南
redis·后端
weixin_440730502 小时前
socket解决多次输入问题-大数据接收问题-模块动态导入
java·开发语言·servlet
用户3126874877202 小时前
ReentrantLock 和 synchronized 到底选哪个?源码级对比与选型
java
老孙讲技术2 小时前
【监控开发】把车间未戴帽和烟火报警接进安监值班群:workwearDetect 对图与 setMessageCallback 订 smokeAlarm
后端·物联网·音视频开发
计算机毕设定制辅导-无忧学长2 小时前
基于Spring Boot的玄幻小说个性化推荐平台设计与实现
java·vue.js·spring boot·后端·mysql·推荐算法