关于ThreadLocal

背景:学习点评项目——实现登录拦截器/判断用户是否存在


前言

先观看如下代码:

package com.hmdp.utils;

import com.hmdp.dto.UserDTO;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 1. 获取 session
        HttpSession session = request.getSession();
        // 2. 获取 session 中的用户
        Object user = session.getAttribute("user");
        // 3. 判断用户是否存在
        if (user == null) {
            // 4. 不存在,拦截,设置 401 状态码
            response.setStatus(401);
            return false;
        }
        // 5. 存在,保存用户信息到 ThreadLocal(便于后续 Service 层获取)
        UserHolder.saveUser((UserDTO) user);
        // 6. 放行
        return true;
    }
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 请求结束后移除用户信息,防止内存泄漏(因为 ThreadLocal 与线程绑定,而 Tomcat 使用线程池)
        UserHolder.removeUser();
    }

这是一个登录拦截器,用于在处理每次请求前设置ThreadLocal,以及请求完成后移除threadLocal中的用户信息


以下是本篇文章正文内容

一、为什么要将用户信息存入ThreadLocal?

存入ThreadLocal不是多此一举,而是解决了数据传递问题:

  • 没有ThreadLocal的情况时,需要在每个方法间传递用户信息
Controller( userDTO ) → Service( userDTO ) → Mapper( userDTO )
  • 有ThreadLocal时,任何地方都能直接获取
UserHolder.getUser();  // 任意层、任意方法直接获取

二、为什么在请求处理完成后需要移除ThreadLocal中的用户信息?

核心原因:Tomcat 使用线程池复用线程

用户A请求 → 线程1 → 请求结束,线程1放回池中
用户B请求 → 线程1(被复用)→ 如果没清理,会拿到用户A的信息!

如果不移除,会发生严重的数据串扰问题:

  • 用户A登录后,线程1的ThreadLocal中存了用户A的信息
  • 用户B未登录,但恰好也被分配到线程1
  • 系统错误地认为用户B已经登录了!

三、触发移除的时机

@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, 
                           Object handler, Exception ex) throws Exception {
    UserHolder.removeUser();  // 这里触发!
}

执行时机: 请求处理完成之后(Controller执行完毕、视图渲染完成,完整的请求生命周期如下:

请求进入 → preHandle(存入) → Controller → afterCompletion(移除) → 响应返回

四、为什么不在finally中移除?

可能会问:既然这么重要,为什么不在Controller的finally中移除?

答:拦截器的afterCompletion更可靠 - 即使Controller抛出异常,afterCompletion也一定会执行,避免内存泄漏。


Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐