a

TreadLocal保存当前用户信息

1.1 ThreadLocal概述

  • ThreadLocal是Java中用于实现线程局部变量的工具类,它为每个线程提供独立的变量副本,解决多线程环境下的数据隔离问题。其核心原理是通过ThreadLocalMap存储线程专属数据,避免同步开销,适用于数据库连接管理、用户会话存储等场景,但需注意内存泄露风险
  • ThreadLocal和当前线程绑定到一起,当前线程结束了,ThreadLocal就没有了
  • ThreadLoca没有了具体指的是 当前对象 没有了,值还存在
  • map.set(this, value); this 没有了,value还在内存中
  • 手动清除value值,remove方法,不然造成内存泄露

1.2 使用场景

接口:获取登录用户个人信息

  • 登录成功后,跳转到主页面中,在主页面里面调用接口,获取当前登录成功的用户信息(用户名或者头像等)

  • 分析过程

    • 浏览器发送请求,首先到拦截器,拦截器把请求拦下
    • 在拦截器校验是否登录,如果是登录,把请求放行
    • 放行之后,执行后面接口,接口返回数据
    • 这样完成一次请求过程
    • 简单理解为一次请求开启线程
    • 使用ThreadLocal和当前线程绑定到一起
  • 具体实现

      1. 创建ThreadLocal工具类,封装向ThreaLocal放数据,取数据和清除数据的方法
      1
      2
      3
      4
      5
      6
      7
      8
      @Data
      @AllArgsConstructor
      @NoArgsConstructor
      public class LoginUser {

      private Long userId;
      private String username;
      }
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      public class LoginUserContext {

      private static final ThreadLocal<LoginUser> userThreadLocal = new ThreadLocal<>();
      // 存放
      public static void setLoginUser(LoginUser loginUser) {
      userThreadLocal.set(loginUser);
      }
      // 获取
      public static LoginUser getLoginUser() {
      return userThreadLocal.get();
      }
      // 清除
      public static void clear() {
      userThreadLocal.remove();
      }
      }
    • 在拦截器方法里面使用ThreadLocal,向ThreadLocal中放用户数据

    • 在接口中,直接从ThreadLocal获取拦截器时放数据

    • Token 只在拦截器解析一次,然后通过 ThreadLocal 保存当前登录用户,Controller 和 Service 直接获取,无需重复解析 JWT。

      1. 在拦截器方法中使用、threadLocal,向ThreadLocal里存放用户数据
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      @Component
      public class AuthenticationInterceptor implements HandlerInterceptor {


      @Override
      public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
      String token = request.getHeader("access-token");
      //String token = "aaaaabc";

      //String token = "eyJhbGciOiJIUzI1NiIsInppcCI6IkdaSVAifQ.H4sIAAAAAAAA_6tWKi5NUrJSCg12DYr39HPzV9JRSq0oULIyNLcwMTUxNjY10lEqLU4t8kxRsrI0hLDzEnNTgXpySpMrlWoBQVhMh0IAAAA.ZEWIGVl1GrcNMOxwIgq_2A4jPC6ktM8VgGFFXVtwaEI";

      // 判断token是否为空
      if (token == null) {
      throw new LeaseException(ResultCodeEnum.ADMIN_LOGIN_AUTH);
      } else {
      // 如果不为空,解析token
      Claims claims = JwtUtil.parseToken(token);
      Long userId = claims.get("userId", Long.class);
      String username = claims.get("username", String.class);
      System.out.println("拦截器解析token获取userId = " + userId);

      // 解析token成功之后,根据userId查询数据库,判断用户是否存在


      // 把userId放到ThreadLocal中
      LoginUser loginUser = new LoginUser();
      loginUser.setUserId(userId);
      loginUser.setUsername(username);
      LoginUserContext.setLoginUser(loginUser);

      }
      return true;


      }
      // 清除ThreadLocal值,防止内存泄露
      @Override
      public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
      LoginUserContext.clear();
      }
      }
        1. 在接口里面,直接从ThreadLocal获取拦截器时存放数据
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        @GetMapping("info")
        @Operation(summary = "获取登录用户信息")
        public Result<UserInfoVo> info(HttpServletRequest request) {
        // 1. 从请求头获取当前登录的用户id
        // 用户id在请求头token中
        //String token = request.getHeader("access-token");

        // 解析token 使用jwt
        //Claims claims = JwtUtil.parseToken(token);
        //Long userId = claims.get("userId", Long.class);

        // 直接从ThreadLocal获取userId
        LoginUser loginUser = LoginUserContext.getLoginUser();
        Long userId = loginUser.getUserId();


        // 2. 根据用户id查询数据库表,获取用户信息
        UserInfoVo userInfoVo = loginService.getUserInfoById(userId);

        return Result.ok();
        }
      • 测试:

        房间租期管理