使用ThreadLocal存储用户登录信息

前提知识

ThreadLocal

ThreadLocal叫做线程变量,意思是ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的,也就是说该变量是当前线程独有的变量。ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。

通俗解释就是,假设你部署了一个servlet应用,那么100个请求就是100个servlet线程,ThreadLocal就在每个servlet线程中创建一个副本,每个servle能访问自己内部的副本变量,这个变量相对于其他线程是隔离的

ThreadLoal 变量,线程局部变量,同一个 ThreadLocal 所包含的对象,在不同的 Thread 中有不同的副本。这里有几点需要注意:

因为每个 Thread 内有自己的实例副本,且该副本只能由当前 Thread 使用。这是也是 ThreadLocal 命名的由来。 既然每个 Thread 有自己的实例副本,且其它 Thread 不可访问,那就不存在多线程间共享的问题。 ThreadLocal 提供了线程本地的实例。它与普通变量的区别在于,每个使用该变量的线程都会初始化一个完全独立的实例副本。ThreadLocal 变量通常被private static修饰。当一个线程结束时,它所使用的所有 ThreadLocal 相对的实例副本都可被回收

总的来说,ThreadLocal 适用于每个线程需要自己独立的实例且该实例需要在多个方法中被使用,也即变量在线程间隔离而在方法或类间共享的场景

InheritableThreadLocal

InheritableThreadLocal这个类能让子线程继承父线程中已经设置的ThreadLocal值。

InheritableThreadLocal是一个Java类,它继承自ThreadLocal,用于在线程之间传递数据。它的主要作用是使子线程继承父线程的ThreadLocal变量的值。

java 复制代码
public class InheritableThreadLocalDemo {

    private static InheritableThreadLocal<String> threadLocal = new InheritableThreadLocal<>();

    public static void main(String[] args) {
        threadLocal.set("mainThread");
        System.out.println("value:"+threadLocal.get());
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                String value = threadLocal.get();
                System.out.println("value:"+value);
            }
        });
        thread.start();
    }

}

上面代码中在主线程中设置了一个ThreadLocal变量,并将其值设置为mainThread。然后有在主线程中开启了一个子线程thread,并试图获取在主线程中set的ThreadLocal变量的值

执行结果如下:

java 复制代码
value:mainThread
value:mainThread

InheritableThreadLocal和ThreadLocal区别

与ThreadLocal相比,InheritableThreadLocal的主要区别在于:

  1. 继承性:InheritableThreadLocal可以将值从父线程传递给子线程,而ThreadLocal不能。
  2. 初始化:InheritableThreadLocal在创建时会将初始值设置为null,而ThreadLocal可以在创建时指定初始值。
  3. 获取值:InheritableThreadLocal在获取值时会首先检查父线程中的值,然后再检查当前线程中的值,而ThreadLocal只检查当前线程中的值。

GenericFilterBean

GenericFilterBean 是 Spring Framework 中的一个类,它是一个抽象类,用于实现自定义的 Servlet 过滤器(javax.servlet.Filter)。

过滤器在 Java Web 应用程序中用于对请求和响应进行拦截和处理。GenericFilterBean 提供了一个方便的基类,使得创建自定义过滤器变得简单,只需要继承 GenericFilterBean 并实现 doFilter 方法即可。

具体来说,GenericFilterBean 提供了以下方法:

  1. doFilter: 这是一个抽象方法,用于实现过滤器的逻辑。在这个方法中,你可以对请求进行处理、修改请求和响应的内容,甚至完全拒绝或放行请求。
  2. initFilterBean: 这是 Filter 接口中的初始化方法,在 GenericFilterBean 中进行了实现。可以在这个方法中进行过滤器的初始化工作。
  3. getFilterConfig: 这个方法用于获取过滤器的配置信息,一般在初始化时会使用到。

使用 GenericFilterBean 的步骤如下:

  1. 创建一个类,继承 GenericFilterBean
  2. 实现 doFilter 方法,定义过滤器的逻辑。
  3. 可选地实现 initFilterBean 方法,进行过滤器的初始化工作。

总的来说,我们可以自定义一个过滤器,继承自 GenericFilterBean 并实现了 doFilter 方法。在 doFilter 方法中,我们可以实现自己的过滤器逻辑,然后调用 FilterChaindoFilter 方法继续处理请求。

实战

思路

我们通过自定义过滤器,然后将用户信息存储到InheritableThreadLocal中,每一个servlet线程进来,我们要把对应的用户信息存储到ThreadLocal这个线程的变量里面去,以保证我们在调用的时候都能获取到

具体细节我们看下面代码,采用静态变量,ThreadLocal 变量的生命周期,我们要在静态变量中保存 ThreadLocal 变量的值

首先定义登录的用户信息

java 复制代码
public class UserRuntime implements Serializable {
    private String userId;
    private SysUser userVal;
    private SysDept deptVal;
    private List<SysRole> listRole;
    private List<SysPrivilege> listPrivilege;
    private List<SysMenu> listMenu;
    private List<SysDept> listDept;
    private List<String> listPrivilegeIds;

    private static String getCacheKey(String token) {
        return String.format("CACHE_USER_RUNTIME_%s", token);
    }

    public static UserRuntime getCacheData(String token) {
        String key = getCacheKey(token);
        return (UserRuntime)CacheRuntime.get(key);
    }

    public UserRuntime() {
    }

    public UserRuntime(SysUser userVal) {
        this.userVal = userVal;
        this.deptVal = null;
    }

    public static SysUser getSysUser() {
        UserRuntime result = UserRuntimeHolder.getCurrent();
        return result != null ? result.getUserVal() : null;
    }

    public static SysDept getSysDept() {
        UserRuntime result = UserRuntimeHolder.getCurrent();
        return result != null ? result.getDeptVal() : null;
    }

    public static List<SysPrivilege> getSysPrivilege() {
        UserRuntime result = UserRuntimeHolder.getCurrent();
        return result != null ? result.getListPrivilege() : null;
    }

    public boolean checkPrivilege(String privilegeId) {
        List<SysPrivilege> sysPrivileges = this.getListPrivilege();
        Iterator var3 = sysPrivileges.iterator();

        SysPrivilege i;
        do {
            if (!var3.hasNext()) {
                return false;
            }

            i = (SysPrivilege)var3.next();
        } while(!StringUtils.equalsIgnoreCase(privilegeId, i.privilegeId));

        return true;
    }

    public SysUser getUserVal() {
        return this.userVal;
    }

    public String getUserId() {
        return this.getUserVal().userId;
    }

    public SysDept getDeptVal() {
        if (this.deptVal == null && this.userVal != null) {
            this.deptVal = StaticFactory.userUtil.getSysDept(this.userVal.deptId);
        }

        return this.deptVal;
    }

    public List<SysRole> getListRole() {
        if (this.listRole == null && this.userVal != null) {
            this.listRole = StaticFactory.userUtil.getListRole(this.userVal.userId);
        }

        return this.listRole;
    }

    public static List<SysRole> getRoles() {
        UserRuntime result = UserRuntimeHolder.getCurrent();
        return result != null ? result.getListRole() : null;
    }

    public List<SysPrivilege> getListPrivilege() {
        if (this.listPrivilege == null && this.userVal != null) {
            this.listPrivilege = StaticFactory.userUtil.getListPrivilege(this.userVal.userId);
        }

        return this.listPrivilege;
    }

    public static DataPager<SysUserDto> getUserListByRoleId(UserFetch userFetch) {
        return StaticFactory.userUtil.getUserListByRoleId(userFetch);
    }

    public static SysUser getByGuid(String guid) {
        return StaticFactory.userUtil.getByGuid(guid);
    }

    public static SysUser getByLoginName(String loginName) {
        return StaticFactory.userUtil.getSysUserByLoginName(loginName);
    }

    public static boolean updateUser(SysUser sysUser) {
        return StaticFactory.userUtil.updateUser(getSysUser().userId, sysUser);
    }
}

定义自己的过滤器

java 复制代码
@Component
public class UserRuntimeFilter extends GenericFilterBean {
    @Autowired
    UserSession userSession;

    public UserRuntimeFilter() {
    }

    private static String getCacheKey(String userId) {
        return String.format("CACHE_USER_RUNTIME_%s", userId);
    }

    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        try {
            SecurityContext context = SecurityContextHolder.getContext();
            if (context != null && context.getAuthentication() != null && SecurityContextHolder.getContext().getAuthentication() instanceof JwtAuthenticationToken && context.getAuthentication().isAuthenticated()) {
                String cacheKey = getCacheKey(this.userSession.getUserId());
                UserRuntime ur = (UserRuntime)CacheRuntime.get(cacheKey);
                if (ur == null) {
                    SysUser sysUser = StaticFactory.userUtil.getByGuid(this.userSession.getUserId());
                    ur = new UserRuntime(sysUser);
                }

                UserRuntimeHolder.setCurrent(ur);
            }

            filterChain.doFilter(servletRequest, servletResponse);
        } finally {
            UserRuntimeHolder.remove();
        }

    }
}

总结,就是在过滤器这边查询用户信息,存储到InheritableThreadLocal

定义InheritableThreadLocal

java 复制代码
public class UserRuntimeHolder {
    public static final ThreadLocal<UserRuntime> CURRENT = new InheritableThreadLocal();

    public UserRuntimeHolder() {
    }

    public static UserRuntime getCurrent() {
        return (UserRuntime)CURRENT.get();
    }

    public static void setCurrent(UserRuntime val) {
        CURRENT.set(val);
    }

    public static void remove() {
        CURRENT.remove();
    }
}

解释

静态变量(static variable)是一种特殊的变量,它们属于类本身,而不是实例对象。静态变量是在类加载时初始化的,并且它们的生命周期是整个应用程序的生命周期。

对于 ThreadLocal 变量,情况略有不同。ThreadLocal 变量是线程局部变量,它们的生命周期是与线程相关的。当你在一个线程中执行 get 操作时,ThreadLocal 变量将从当前线程的线程局部存储中获取值。

当你从 servlet 线程访问静态变量 public static final ThreadLocal<UserRuntime> CURRENT = new InheritableThreadLocal(); 时,你不需要创建一个新的 ThreadLocal 实例,因为 CURRENT 变量已经被初始化为一个 InheritableThreadLocal 实例。

以下是发生的事情:

  1. 当 servlet 容器加载你的 servlet 类时,静态变量 CURRENT 被初始化为一个 InheritableThreadLocal 实例。
  2. InheritableThreadLocal 实例只被创建一次,即当类被加载时,并且它被所有访问 CURRENT 变量的线程共享。
  3. 当 servlet 线程访问 CURRENT 变量时,它获取了同一个在类加载时创建的 InheritableThreadLocal 实例。
  4. 由于 InheritableThreadLocalThreadLocal 的子类,它提供了在每个线程中存储和检索值的功能。
  5. 当你从 servlet 线程调用 CURRENT.get()CURRENT.set() 时,InheritableThreadLocal 实例将存储或检索当前线程的值。

通过使用 public static final ThreadLocal<UserRuntime> CURRENT = new InheritableThreadLocal();,你确保:

  • CURRENT 变量只被初始化一次,即当类被加载时。
  • 所有访问 CURRENT 变量的线程共享同一个 InheritableThreadLocal 实例。
  • 每个线程可以使用 InheritableThreadLocal 实例来存储和检索自己的值。

如果你每次访问 CURRENT 变量时都创建一个新的 ThreadLocal 实例,你将失去线程局部存储和继承的优点。

通过使用单个共享的 InheritableThreadLocal 实例,你可以利用线程局部存储和继承,允许你在每个线程中存储和检索值,同时也继承来自父线程的值。

具体调用是在UserRuntime里面getSysUser方法,这里静态变量,我们在其他地方可以直接通过**UserRuntime.getSysUser()**就可以调用

java 复制代码
public static SysUser getSysUser() {
    UserRuntime result = UserRuntimeHolder.getCurrent();
    return result != null ? result.getUserVal() : null;
}
相关推荐
2401_853275739 分钟前
ArrayList 源码分析
java·开发语言
爪哇学长14 分钟前
SQL 注入详解:原理、危害与防范措施
xml·java·数据库·sql·oracle
大鲤余19 分钟前
Rust,删除cargo安装的可执行文件
开发语言·后端·rust
她说彩礼65万26 分钟前
Asp.NET Core Mvc中一个视图怎么设置多个强数据类型
后端·asp.net·mvc
MoFe128 分钟前
【.net core】【sqlsugar】字符串拼接+内容去重
java·开发语言·.netcore
_江南一点雨36 分钟前
SpringBoot 3.3.5 试用CRaC,启动速度提升3到10倍
java·spring boot·后端
深情废杨杨1 小时前
后端-实现excel的导出功能(超详细讲解)
java·spring boot·excel
智汇探长1 小时前
EasyExcel自定义设置Excel表格宽高
java·excel·easyexcel
酸奶代码1 小时前
Spring AOP技术
java·后端·spring
刘艳兵的学习博客1 小时前
刘艳兵-DBA033-如下那种应用场景符合Oracle ROWID存储规则?
服务器·数据库·oracle·面试·刘艳兵