使用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 适用于每个线程需要自己独立的实例且该实例需要在多个方法中被使用,也即变量在线程间隔离而在方法或类间共享的场景

image.png

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 方法中,我们可以实现自己的过滤器逻辑,然后调用 FilterChain 的 doFilter 方法继续处理请求。

实战

思路

我们通过自定义过滤器,然后将用户信息存储到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. 由于 InheritableThreadLocal 是 ThreadLocal 的子类,它提供了在每个线程中存储和检索值的功能。
  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; }

  • 30
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ThreadLocal是一种线程局部变量,可以在每个线程中存储和访问数据,而不会被其他线程访问到。在多线程环境下,可以使用ThreadLocal存储用户的信息,以便在后续的代码中直接获取用户信息。 具体实现原理如下: 1. 在UserContext类中定义了一个静态的ThreadLocal变量,用于存储用户信息\[2\]。 2. 当用户登录成功后,将用户信息存储ThreadLocal中,通过调用setBaseUser方法\[2\]。 3. 在后续的代码中,可以通过调用getUser方法来获取当前线程中存储用户信息\[2\]。 4. 当用户请求处理完成后,可以通过调用remove方法来清除当前线程中存储用户信息,以防止内存泄漏\[2\]。 使用ThreadLocal存储用户信息的好处是可以避免在代码中一层一层传递HttpServletRequest或其他对象来获取用户信息\[3\]。每个线程都有自己独立的ThreadLocal变量,可以在当前线程中直接获取用户信息,提高了代码的简洁性和可读性。 需要注意的是,由于ThreadLocal的特性,需要在适当的时候调用remove方法来清除ThreadLocal中的数据,以防止内存泄漏\[1\]。在登录拦截中,可以在controller执行完成后的afterCompletion方法中调用remove方法来清除用户信息\[1\]。 总结起来,ThreadLocal可以用于存储用户信息,通过在每个线程中存储和获取数据,避免了在代码中一层一层传递对象的繁琐操作,提高了代码的简洁性和可读性。同时,需要注意在适当的时候调用remove方法来清除ThreadLocal中的数据,以防止内存泄漏。 #### 引用[.reference_title] - *1* [ThreadLocal存储用户登录信息](https://blog.csdn.net/weixin_46000937/article/details/126828778)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down28v1,239^v4^insert_chatgpt"}} ] [.reference_item] - *2* *3* [spring boot项目使用threadlocal存储用户信息实现数据权限控制](https://blog.csdn.net/bird_tp/article/details/117297856)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down28v1,239^v4^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值