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();
  }

你可能感兴趣的:(线程,java,开发语言,后端)