使用ThreadLocal优雅地存储和传递上下文数据

前言:

最近在整合SpringCloudAlibaba框架的过程中遇到了一个如何去存储和传递用户登录信息的问题,最开始的时候想法是将前端带回来的token解析成用户信息后再将其写到header中,然后从gateway传递到指定的服务中,最后指定的服务从header中取出做相应的业务逻辑操作,由于服务中可能有诸多接口需要用到用户的登录信息,按照上述操作就需要对每个接口进行修改,这样确实可以实现我们的目的,但是如此操作显然不够优雅,后期也不方便进行扩展,思考再三决定使用ThreadLocal多线程上下文传递参数

ThreadLocal简介:

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

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

public T get()

public void set(T value)

public void remove()

注:
    get()方法用于获取当前线程的副本变量值;
    set()方法用于保存当前线程的副本变量值;
    remove()方法移除当前线程的副本变量值

具体代码实现:

1.编写用户信息上下文实体类

/**
 * @author yao
 * @date 2021/6/28 11:01
 */
public class UserContext {

    private ThreadLocal<UserInfoDTO> threadLocal;

    private UserContext() {
        this.threadLocal = new ThreadLocal<>();
    }

    /**
     * 创建实例
     *
     * @return
     */
    public static UserContext getInstance() {
        return SingletonContext.sInstance;
    }

    /**
     * 静态内部类单例模式
     * 单例初使化
     */
    private static class SingletonContext {
        private static final UserContext sInstance = new UserContext();
    }

    /**
     * 用户上下文中放入信息
     *
     * @param userInfoDTO
     */
    public void setContext(UserInfoDTO userInfoDTO) {
        threadLocal.set(userInfoDTO);
    }

    /**
     * 获取上下文中的信息
     *
     * @return
     */
    public UserInfoDTO getContext() {
        return threadLocal.get();
    }

    /**
     * 获取上下文中的用户名
     *
     * @return
     */
    public String getUsername() {
        return getContext().getUsername();
    }

    /**
     * 获取上下文中的用户id
     *
     * @return
     */
    public Long getUserId() {
        return getContext().getUserId();
    }

    /**
     * 清空上下文
     */
    public void clear() {
        threadLocal.remove();
    }

}

2.在拦截器中将用户登录信息存入ThreadLocal中

/**
 * @author yao
 * @date 2021/6/28 11:17
 */
@Component
public class AuthInterceptor implements HandlerInterceptor {

    private static final String AUTH_ERROR_MSG = "请先登录再进行操作";

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //如果不是映射到方法直接通过
        if (!(handler instanceof HandlerMethod)) {
            return true;
        }
        Method method = ((HandlerMethod) handler).getMethod();
        LoginRequired loginRequired = method.getAnnotation(LoginRequired.class);
        if (Objects.nonNull(loginRequired)) {
            return !loginRequired.required();
        } else {
            String token = request.getHeader(CommonConstants.TOKEN);
            if (StringUtils.isEmpty(token)) {
                throw new WebException(AUTH_ERROR_MSG);
            } else {
                //登录校验通过
                String userId = request.getHeader(LoginConstants.USER_ID);
                UserInfoDTO userInfoDTO = UserInfoDTO.builder().userId(Long.parseLong(userId)).build();
                UserContext.getInstance().setContext(userInfoDTO);
                return true;
            }
        }
    }
}

3.在所需业务逻辑中使用当前线程上下文获取用户信息

/**
 * @author yao
 * @date 2021/6/28 9:42
 */
@Component
@Slf4j
@Aspect
public class WebLogAspect {

    @Autowired
    private WebLogService webLogService;

    /**
     * 这里我们使用注解的形式
     * 当然,我们也可以通过切点表达式直接指定需要拦截的package,需要拦截的class 以及 method
     * 切点表达式:  execution(...)
     */
    @Pointcut("@annotation(com.white.common.annotation.WebLog)")
    public void logPointCut() {
    }

    @Around("logPointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        Object result = point.proceed();
        try {
            //保存系统日志
            saveLog(point);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 保存日志
     */
    private void saveLog(ProceedingJoinPoint point) {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        WebLog declaredAnnotation = method.getDeclaredAnnotation(WebLog.class);
        LoginRequired loginRequired = method.getAnnotation(LoginRequired.class);
        if (Objects.nonNull(declaredAnnotation) && Objects.isNull(loginRequired)) {
            //实现保存日志逻辑
            WebLogDTO webLogDTO = new WebLogDTO();
            webLogDTO.setAccessTime(new Date())
                    .setDescription(declaredAnnotation.desc())
                    .setUserId(UserContext.getInstance().getUserId())
                    .setPath(declaredAnnotation.path());
            log.info("保存日志{}", webLogDTO);
            webLogService.saveLog(webLogDTO);
        }
    }


}
  • 3
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ThreadLocal是Java中的一个类,它提供了一种线程局部变量的机制,可以在每个线程中存储和访问自己的数据,而不会被其他线程访问到。ThreadLocal的主要作用是为每个线程提供独立的变量副本,使得每个线程都可以独立地修改自己的副本,而不会影响其他线程的副本。 使用ThreadLocal可以方便地在多线程环境下存储和获取用户数据。具体使用方法如下: 1. 创建ThreadLocal对象:可以通过直接实例化ThreadLocal类来创建一个ThreadLocal对象。 2. 存储数据:通过调用ThreadLocal对象的set方法,将数据存储到当前线程的ThreadLocalMap中。每个线程都有自己的ThreadLocalMap,用于存储该线程的ThreadLocal变量。 3. 获取数据:通过调用ThreadLocal对象的get方法,可以获取当前线程存储ThreadLocalMap中的数据。 4. 清除数据:为了避免内存泄漏,使用ThreadLocal后应该调用remove方法清除当前线程在ThreadLocalMap中的数据使用ThreadLocal存储用户数据的好处是: - 线程隔离:每个线程都有自己独立的数据副本,不会被其他线程访问到,保证了数据的线程安全性。 - 高效性:由于每个线程都有自己的数据副本,不需要进行线程同步操作,提高了程序的执行效率。 然而,需要注意的是,使用ThreadLocal也存在一些潜在的问题: - 内存泄漏:如果没有及时调用remove方法清除数据,可能会导致内存泄漏。 - 上下文传递ThreadLocal只在当前线程内有效,无法在多个线程之间传递数据

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值