ThreadLocal存储用户登录信息

为什么要用ThreadLocal存储用户信息?

一般来说如果我们需要获取当前用户信息的话,大家想到的可能都是在用户的登录的时候将用户的信息存到缓存中,然后登录不是会生成token吗,到时候如果哪个controller中需要用到用户信息的话,那我就将token传过去,然后再校验一下token,ckeckToken的逻辑中会给我返回我存在缓存中的用户信息,就比如用户id吧(直接存密码就有点不安全了),再通过id去查询数据库获得用户的用户名密码之类的。但是这种实现逻辑就有点复杂且冗余了,这个时候我们就可以采用ThreadLocal来解决这个问题,ThreadLocal可以将用户信息保存在线程中,当请求结束后我们再把保存的信息清除掉,这样我们在开发的时候就可以直接从全局的ThreadLocal中很方便地获取用户信息。

实现步骤

  • 首先我们需要创建ThreadLocal类,就叫UserTheadLocal吧。其中有put()方法、get()方法和remove()方法,代码很简单,应该都能看懂。
public class UserThreadLocal {
  private UserThreadLocal(){}
  //线程变量隔离
  private static final ThreadLocal<SysUser> LOCAL = new ThreadLocal<>();

  public static void put(SysUser sysUser){
    LOCAL.set(sysUser);
  }

  public static SysUser get(){
    return LOCAL.get();
  }

  public static void remove(){
    LOCAL.remove();
  }
}
  • 然后我们需要在登录的拦截器中,也就是在preHandle()方法中就将用户信息存到ThreadLocal中,也就是在执行所有controller之前,这个方法一般来说是用来通过校验token判断用户是否登录的。
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    //在执行controller方法(handler之前进行执行

    /**
     * 需要判断请求的接口路径是否为handlermethod(controller方法
     * 判断token是否为空 如果为空 未登录
     * 如果toke不为空  登录验证 loginservice checktoken进行认证
     * 如果认证成功 进行放行
     */
    if (!(handler instanceof HandlerMethod)) {
     //handler可能是requestresourcehandler springboot程序 访问静态资源 默认classpath下的static目录

      return true;
    }
    String token= request.getHeader("Authorization");


    log.info("=================request start===========================");
    String requestURI = request.getRequestURI();
    log.info("request uri:{}",requestURI);
    log.info("request method:{}",request.getMethod());
    log.info("token:{}", token);
    log.info("=================request end===========================");

    if(StringUtils.isBlank(token)){
      Result result=Result.fail(ErrorCode.NO_LOGIN.getCode(), "未登录");
      response.setContentType("application/json;charset=utf-8");
      response.getWriter().print(JSON.toJSONString(result));
      return false;
    }
    SysUser sysUser = loginService.checkToken(token);
    if(sysUser==null){
      Result result=Result.fail(ErrorCode.NO_LOGIN.getCode(), "未登录");
      response.setContentType("application/json;charset=utf-8");
      response.getWriter().print(JSON.toJSONString(result));
      return false;
    }

    //登录验证成功,放行
    //我希望在controller中 直接获取用户的信息 怎么获取?
    UserThreadLocal.put(sysUser);
    return true;
  }
  • 最后一点我们需要在WebMvc配置一下需要拦截的接口,因为如果你不配置的话,那么就说明不需要登录也能访问你的接口了,这样自然也就获取不到用户信息了,因为我们是在登录拦截的preHandle()方法中将用户信息存入到了ThreadLocal中。
@Configuration
public class WebMVCConfig implements WebMvcConfigurer {

 @Autowired
 private LoginInterceptor loginInterceptor;

  @Override
  public void addInterceptors(InterceptorRegistry registry) {
    //拦截test接口,后续实际遇到需要拦截的接口时,在配置为真正的拦截接口
    registry.addInterceptor(loginInterceptor)
      .addPathPatterns("/test")
      .addPathPatterns("/comments/create/change").addPathPatterns("/message/add").addPathPatterns("/upload/Avatar")
      .addPathPatterns("/articles/publish").addPathPatterns("/tree/add");
  }
  • 接着我们就能在配置的接口中通过ThreadLocal获取用户信息了。
@Override
  public Result publish(ArticleParam articleParam) {

    //此接口要加入到登录当中
    SysUser sysUser = UserThreadLocal.get();
 }

防止内存泄露

最后说一个很重要的点,ThreadLocal线程实现是一个Map(每一个Thread维护一个ThreadLocalMap),Map中有一个Key、一个value。其中的Key指向的就是我们new出来的ThreadLocal线程,value就是我们保存的数据。其中key指向ThreadLocal是弱引用,而value指向我们保存的数据是强引用。线程回收的时候会将弱引用的东西回收,保留强引用。
ThreadLocal所在线程进行一次垃圾回收,那么Key就会被GC回收,这样就会导致ThreadLocalMap中key为null,而value还存在强引用。这样我们的Value就会永远存在我们的内存中,无法被删除(如果有大量的类似情况就会造成内存泄露)。
所以我们需要在登录拦截中添加一下方法,在controller执行完成后使用remove方法删除信息。这也是UserTheadLocal类中为什么会有remove()方法的原因。

 @Override
  public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    //如果不删除 ThreadLocal中用完的信息 会有内存泄漏的风险
    UserThreadLocal.remove();
  }
ThreadLocal是一种线程局部变量,可以在每个线程中存储和访问数据,而不会被其他线程访问到。在多线程环境下,可以使用ThreadLocal存储用户信息,以便在后续的代码中直接获取用户信息。 具体实现原理如下: 1. 在UserContext类中定义了一个静态的ThreadLocal变量,用于存储用户信息\[2\]。 2. 当用户登录成功后,将用户信息存储ThreadLocal中,通过调用setBaseUser方法\[2\]。 3. 在后续的代码中,可以通过调用getUser方法来获取当前线程中存储用户信息\[2\]。 4. 当用户请求处理完成后,可以通过调用remove方法来清除当前线程中存储用户信息,以防止内存泄漏\[2\]。 使用ThreadLocal存储用户信息的好处是可以避免在代码中一层一层传递HttpServletRequest或其他对象来获取用户信息\[3\]。每个线程都有自己独立的ThreadLocal变量,可以在当前线程中直接获取用户信息,提高了代码的简洁性和可读性。 需要注意的是,由于ThreadLocal的特性,需要在适当的时候调用remove方法来清除ThreadLocal中的数据,以防止内存泄漏\[1\]。在登录拦截中,可以在controller执行完成后的afterCompletion方法中调用remove方法来清除用户信息\[1\]。 总结起来,ThreadLocal可以用于存储用户信息,通过在每个线程中存储和获取数据,避免了在代码中一层一层传递对象的繁琐操作,提高了代码的简洁性和可读性。同时,需要注意在适当的时候调用remove方法来清除ThreadLocal中的数据,以防止内存泄漏。 #### 引用[.reference_title] - *1* [ThreadLocal存储用户登录信息](https://blog.csdn.net/weixin_46000937/article/details/126828778)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down28v1,239^v4^insert_chatgpt"}} ] [.reference_item] - *2* *3* [spring boot项目使用threadlocal存储用户信息实现数据权限控制](https://blog.csdn.net/bird_tp/article/details/117297856)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down28v1,239^v4^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值