网站Logo Ilren 小记

SpringBoot使用ThreadLocal线程上下文传递数据

jack
6
2020-08-11

最近在开发项目时需要在拦截器中请求认证服务判断token的有效性并获取用户信息,但是拿到用户信息后需要传递给Controller层或者Service层去使用。一开始想到了使用header和session传递用户信息,但是感觉不是很优雅,有点low,最后想到了使用ThreadLocal多线程上下文传递参数。

ThreadLocal是什么?

    ThreadLocal是一个本地线程副本变量工具类。主要用于将私有线程和该线程存放的副本对象做一个映射,各个线程之间的变量互不干扰,在高并发场景下,可以实现无状态的调用,特别适用于各个线程依赖不同的变量值完成操作的场景。

ThreadLocal类提供如下几个核心方法:

public T get()

public void set(T value)

public void remove()
  • get()方法用于获取当前线程的副本变量值。

  • set()方法用于保存当前线程的副本变量值。

  • remove()方法移除当前线程的副本变量值。

在SpringBoot项目中使用ThreadLocla拦截器传递数据

  1. 创建用户向下文实体类

package cn.ilren.resource.common;

import cn.ilren.resource.pojo.LoginUser;

/**
 * 使用线程上下文在线程内共享用户信息
 */
public class UserContext {

    //把构造函数私有化,外部不能new
    private UserContext() {
    }

    private static final ThreadLocal<LoginUser> context = new ThreadLocal<LoginUser>();

    /**
     * 存放用户信息
     *
     * @param user
     */
    public static void set(LoginUser user) {
        context.set(user);
    }

    /**
     * 获取用户信息
     *
     * @return
     */
    public static LoginUser get() {
        return context.get();
    }

    /**
     * 清除当前线程内引用,防止内存泄漏
     */
    public static void remove() {
        context.remove();
    }
}

2.在拦截器中获取用户信息并将用户信息放置在UserContext线程上下文中

package cn.ilren.resource.interceptor;

import cn.ilren.resource.common.UserContext;
import cn.ilren.resource.pojo.LoginUser;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 拦截器
 */
@Slf4j
public class ResourceInterceptor implements HandlerInterceptor {


    /**
     * 在请求处理之前进行调用(Controller方法调用之前)
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object object) throws Exception {
        try {

            ······

            LoginUser user = ······ ;//从请求认证服务获取用户信息
            UserContext.set(user);
            return Boolean.TRUE;
        } catch (Exception e) {
            log.error("拦截器出错", e);
            return Boolean.FALSE;
        }
    }

    /**
     * 请求处理之后进行调用(Controller方法调用之后)
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object object, ModelAndView mv)
            throws Exception {

    }

    /**
     * 在整个请求结束之后被调用(主要是用于进行资源清理工作)
     * 一定要在请求结束后调用remove清除当前线程的副本变量值,否则会造成内存泄漏
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object object, Exception ex)
            throws Exception {
            UserContext.remove();
    }

}

3.在Controller或Service层使用当前线程上下文获取用户信息

    LoginUser user = UserContext.get();

动物装饰