ThreadLocal优缺点,封装及使用(在拦截中拦截请求放到threadlocal)

ThreadLocal的优点:

  • 线程隔离:每个线程都有自己独立的变量副本,不会受到其他线程的影响,可以避免线程安全问题。
  • 高效性:由于每个线程都有自己的变量副本,不需要进行额外的同步操作,可以提高程序的执行效率。
  • 简单易用:使用 ThreadLocal 可以方便地在多线程环境下管理登录信息,不需要手动进行线程间的变量传递。

缺点

  1. 内存泄漏:如果没有及时清理 ThreadLocal 中的变量副本,可能会导致内存泄漏问题。因为 ThreadLocal 中的变量副本是与线程绑定的,如果线程一直存在,那么对应的变量副本也会一直存在,可能会占用大量的内存空间。
  2. 上下文切换问题:由于每个线程都有自己的变量副本,当需要在多个线程之间共享数据时,可能需要进行额外的上下文切换操作,增加了程序的复杂性和开销。

为什么要封装ThreadLocal?
原因有两点:

1、对于Thread,如果希望在Interceptor中存入User并在Service层通过ThreadLocal把User取出来,必须保证Interceptor和Service此时用的是同一个ThreadLocal。

2.一个对象如何同时出现在Interceptor和Service呢?各自new一个ThreadLocal可不行,因为此时是两个对象了。

所以我们定义一个类

简单版本(小白使用)

 这里定义了一个threadlocal的工具类,这是最简单的版本

/**
 * @author ljc
 */
public class MyThreadLocal {
 
    private MyThreadLocal() {
    }
 
    private static final ThreadLocal<Object> THREAD_CONTEXT = new ThreadLocal<>();
 
    public static void set(Object obj) {
        THREAD_CONTEXT.set(obj);
    }
 
    public static Object get() {
        return THREAD_CONTEXT.get();
    }
 
    public static void remove() {
        THREAD_CONTEXT.remove();
    }
}

但是简单版本存在一个问题就是只能存单个对象,如果存多个对象会覆盖掉前面的对象

高级版本(推荐):

package com.heima.utils.thread;

import java.util.HashMap;
import java.util.Map;

/**
 * @author ljc
 * 参考 mx
 */
public class ThreadLocalUtil {
 
    private ThreadLocalUtil() {
    }
 
    /**
     * 将ThreadLocal泛型指定为String,那么造了一个ThreadLocalMap后,这个map只能存 threadLocal:"这是字符串" 这样的键值对
     * 将ThreadLocal泛型指定为Integer,那么造了一个ThreadLocalMap后,这个map只能存 threadLocal:1111111111 这样的键值对
     * 由于单纯的value会发生值覆盖,所以我们使用Map<String, Object>作为value
     */
    private static final ThreadLocal<Map<String, Object>> THREAD_CONTEXT = new ThreadLocal<>();

    /**
     * 存入线程变量
     * @param key
     * @param object
     */
    public static void put(String key, Object object) {
        /**
         * ThreadLocalMap的构造类似于这样
         * {
         * ...THREAD_CONTEXT: {
         * ........."USER_INFO":"{'name':'bravo', 'age':18}",
         * ........."SCORE":"{'Math':99, 'English': 97}"
         * ......}
         * }
         *
         * 2.ThreadLocalMap.Entry e = map.getEntry(this); 把自己(THREAD_CONTEXT)作为key,取出属于自己的value,此时value是一个Map<String, Object>。
         * 3.所以最终THREAD_CONTEXT.get()返回的Map<String, Object> map
         *
         */
        Map<String, Object> map = THREAD_CONTEXT.get();
        // 第一次从ThreadLocalMap中根据threadLocal取出的value可能是null
        if (map == null) {
            map = new HashMap<>();
            // 把map作为value放进去
            THREAD_CONTEXT.set(map);
        }
        /**
         * 假设本次存的是 USER_INFO:{"name":"bravo", "age":18}
         * 此时ThreadLocalMap中的结构是
         * {
         * ...THREAD_CONTEXT: {
         * ........."USER_INFO":"{'name':'bravo', 'age':18}",
         * ......}
         * }
         *
         */
        map.put(key, object);
    }
 
    /**
     * 取出线程变量
     *
     * @param key
     * @return
     */
    public static Object get(String key) {
        // 先获取Map
        Map<String, Object> map = THREAD_CONTEXT.get();
        // 从Map中得到USER_INFO
        return map != null ? map.get(key) : null;
    }
 
    /**
     * 移除当前线程的指定变量
     * 比如把
     * {
     * ...THREAD_CONTEXT: {
     * ........."USER_INFO":"{'name':'bravo', 'age':18}",
     * ........."SCORE":"{'Math':99, 'English': 97}"
     * ......}
     * }
     * 变成
     * {
     * ...THREAD_CONTEXT: {
     * ........."SCORE":"{'Math':99, 'English': 97}"
     * ......}
     * }
     * 并不是移除所有,而是只移除USER_INFO
     * @param key
     */
    public static void remove(String key) {
        Map<String, Object> map = THREAD_CONTEXT.get();
        map.remove(key);
    }
 
    /**
     * 移除当前线程的所有变量
     * 比如把
     * {
     * ...THREAD_CONTEXT: {
     * ........."USER_INFO":"{'name':'bravo', 'age':18}",
     * ........."SCORE":"{'Math':99, 'English': 97}"
     * ......}
     * }
     * 变成
     * {
     * }
     */
    public static void clear() {
        THREAD_CONTEXT.remove();
    }
}

拦截器中拦截数据放到threadlocal(拦截器在相应的微服务中设置)


// 自定义拦截器  实现HandlerInterceptor 并复写里面的方法
public class AppTokenInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 从请求头中取出数据放到
        String userId = request.getHeader("userId");
        if (userId != null){
            ApUser user = new ApUser();
            user.setId(Integer.valueOf(userId));
            ThreadLocalUtil.put("user",user);
        }
        return true;
    }
    // controller 出错 该方法就不执行了
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        ThreadLocalUtil.clear();
    }
    // controller方法出错,依然执行
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        ThreadLocalUtil.clear();
    }
}

然后添加拦截器的配置类让其生效,并设置拦截范围

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new AppTokenInterceptor()).addPathPatterns("/**");
    }
}

Shiro 使用过滤器来获取用户登录信息并存储到 ThreadLocal 。具体来说,Shiro 的过滤器链是通过 Servlet 容器(如 Tomcat)的 Filter 进行拦截和处理的。 在 Shiro ,有一个名为 `ShiroFilter` 的过滤器,它是整个 Shiro 过滤器链的入口点。当请求到达时,Servlet 容器会将请求交给 `ShiroFilter` 进行处理。 `ShiroFilter` 会根据配置的 URL 规则和拦截器链进行判断和处理。其拦截器链的一些特定的过滤器负责获取用户登录信息,并将其存储到 ThreadLocal 。 具体来说,`UserFilter` 是 Shiro 提供的一个过滤器,它负责获取用户的身份信息并存储到 ThreadLocal 。当请求经过 `UserFilter` 时,它会从请求解析出用户的身份信息(如 Cookie、Token 等),然后将其封装成一个 `Subject` 对象,并将该对象存储到 ThreadLocal 。 `UserFilter` 是 Shiro 过滤器链的一个关键组成部分,用于实现用户身份认证的功能。当其他组件需要获取当前用户对象时,可以通过 `SecurityUtils.getSubject()` 方法从 ThreadLocal 获取。 总结起来,Shiro 使用过滤器来拦截请求并进行身份认证和授权操作。其的特定过滤器(如 `UserFilter`)负责获取用户登录信息,并将其存储到 ThreadLocal ,以便后续的请求处理过程可以方便地获取当前用户对象。这样可以保证在整个请求处理过程,可以便捷地访问和操作用户信息。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值