一种新的方法来存储用户信息——ThreadLocal

简介: 一种新的方法来存储用户信息——ThreadLocal

一,问题提出:

通常在项目设计开发中,用户登录后,我们会将用户的信息存到session,如果想在其它地方获取session中的用户信息,我们需要先获取HttpServletRequest,再通过request.getSession得到HttpSession。例如下代码:

public static User getSessionUser(HttpServletRequest request)
    {
        if(request.getSession().getAttribute( "sessionuser" ) != null)
        {
            return (User)request.getSession().getAttribute( "sessionuser" );
        }
        return null;
    }

但是这样操作会很麻烦并且存在并发问题

1.1 每次获取session中的用户信息,先手动获取到HttpServletRequest对象。

每次要获取session都要传递Request请求参数,尤其是service层或者dao层也要使用到user的信息,而通常在一个大型项目中,service层和dao层都是和web层分离开来,

都是单独的工程,不依赖servlet api,大家也不会为了在service层或者dao层获取登录用户信息而这么做,这样显得会很奇怪,所以我们只能在action中调用service的时候,将用户信息以参数形式传过去。

对于session中的用户信息,我们不仅想要在action中随用随取,还想在其它普通类中取,即使不依赖servlet api, 我们也要在方法里随用随取,我们在处理请求的时候,很多操作都要获取当前用户的ID等信息,由上可见,我们凡是在action的方法中任何一处想要获取session中的用户信息,则必须要先手动获取到HttpServletRequest,是不是比较麻烦

1.2 如果遇到高并发,多人同时登录系统时,会出现session混乱。

考虑到并发,如果两个人或者多人同时登录系统,A置成自己的session了,B又置成他的session了,两人开始打架了。

二,解决方案

客户端发送的每次http请求,对应的服务端都会分配一个新的线程来处理,在处理过程中涉及到下面类中的方法都属于相同的一个线程。

我们引入ThreadLocal,首先它不是用来解决多线程并发共享一类问题的,它解决的是在一个线程中参数的传递。

2.1 ThreadLocal

顾名思义,就是本地线程,可是这个名字实在容易让人误解,因为其实它是本地线程局部变量的意思,首先我们要知道,我们每个请求都会对应一个线程,

这个ThreadLocal就是这个线程使用过程中的一个变量,该变量为其所属线程所有,各个线程互不影响。

这里我们要了解一下ThreadLocal的三个方法:

ThreadLocal.set(T value); //设置值
ThreadLocal.get(); //获取值
ThreadLocal.remove(); //移除值

具体实例:

package com.tigerhhzz.wuaimai.common;
/**
 * 基于ThreadLocal封装工具类,用户保存和获取当前登录用户id
 */
public class BaseContext {
    private static ThreadLocal<Long> threadLocal = new ThreadLocal<>();
    /**
     * 设置值
     * @param id
     */
    public static void setCurrentId(Long id){
        threadLocal.set(id);
    }
    /**
     * 获取值
     * @return
     */
    public static Long getCurrentId(){
        return threadLocal.get();
    }
}

所以我们可以借助这个ThreadLocal来存储登录用户的信息,在一个请求中,所有调用的方法都在同一个线程中去处理,这样就实现了在任何地方都可以获取到用户信息了,从而摆脱了HttpServletRequest的束缚。

2.2 避免了跨层之间的参数传递,实现了层与层之间的松耦合。

ThreadLocal并不是一个Thread,而是Thread的局部变量。当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每个线程都可以独立的改变自己的副本,而不会影响其他线程所对应的副本。

ThreadLocal为每个线程提供了单独一份存储空间,具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问。

2.3 项目实际应用

项目结构如下:

BaseContext .class

package com.tigerhhzz.wuaimai.common;
/**
 * 基于ThreadLocal封装工具类,用户保存和获取当前登录用户id
 */
public class BaseContext {
    private static ThreadLocal<Long> threadLocal = new ThreadLocal<>();
    /**
     * 设置值
     * @param id
     */
    public static void setCurrentId(Long id){
        threadLocal.set(id);
    }
    /**
     * 获取值
     * @return
     */
    public static Long getCurrentId(){
        return threadLocal.get();
    }
}

logincheckfilter部分代码:

//4、判断登录状态,如果已登录,则直接放行
        if(request.getSession().getAttribute("employee") != null){
            log.info("用户已登录,用户id为:{}",request.getSession().getAttribute("employee"));
            Long empId = (Long) request.getSession().getAttribute("employee");
            BaseContext.setCurrentId(empId);
            filterChain.doFilter(request,response);
            return;
        }

我们可以在logincheckfilter的dofilter方法中获取当前登录用户id,并调用ThreadLocal方法来设置当前线程的线程局部变量的值(用户id),然后在mymetaobjecthandler的update方法中调用threadlocal的get方法来获得当前线程所对应的线程局部变量的值(用户id)。

MyMetaObjecthandler 公共字段填充类

package com.tigerhhzz.wuaimai.common;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
/**
 * 自定义元数据对象处理器
 */
@Component
@Slf4j
public class MyMetaObjecthandler implements MetaObjectHandler {
    /**
     * 插入操作,自动填充
     * @param metaObject
     */
    @Override
    public void insertFill(MetaObject metaObject) {
        log.info("公共字段自动填充[insert]...");
        log.info(metaObject.toString());
        metaObject.setValue("createTime", LocalDateTime.now());
        metaObject.setValue("updateTime",LocalDateTime.now());
        metaObject.setValue("createUser", BaseContext.getCurrentId());
        metaObject.setValue("updateUser",BaseContext.getCurrentId());
    }
    /**
     * 更新操作,自动填充
     * @param metaObject
     */
    @Override
    public void updateFill(MetaObject metaObject) {
        log.info("公共字段自动填充[update]...");
        log.info(metaObject.toString());
        long id = Thread.currentThread().getId();
        log.info("线程id为:{}",id);
        metaObject.setValue("updateTime",LocalDateTime.now());
        metaObject.setValue("updateUser",BaseContext.getCurrentId());
    }
}


目录
相关文章
|
缓存 NoSQL Java
Java实现redis缓存效果变量过期
Java实现redis缓存效果变量过期
98 0
ThreadLocal实现登录(保存用户登录信息)
ThreadLocal可以将用户信息保存在线程中,当请求结束后我们在把保存的信息清除掉。这样我们才开发的时候就可以直接从全局的ThreadLocal中很方便的获取用户信息。 使用ThreadLocal,可以在同一线程中很方便的获取用户信息,不需要频繁的传递session对象。
2706 1
ThreadLocal实现登录(保存用户登录信息)
|
缓存 NoSQL fastjson
Shiro Session集群共享存入Redis中SimpleSession的transient 属性不能序列化
Shiro Session集群共享存入Redis中SimpleSession的transient 属性不能序列化
168 0
|
1月前
|
缓存 NoSQL 算法
解决Redis缓存雪崩问题的有效方法
解决Redis缓存雪崩问题的有效方法
42 1
|
5月前
|
XML 机器学习/深度学习 存储
技术心得:对象的序列化存入数据库,与反序列化
技术心得:对象的序列化存入数据库,与反序列化
50 0
|
Java
ThreadLocal全面解析
ThreadLocal全面解析
115 0
|
NoSQL Java Redis
redis命令行存入了数据,代码获取不到
redis命令行存入了数据,代码获取不到
|
存储 安全 数据安全/隐私保护
cookie,session和token三者的区别和使用场景
# 引言 今天这篇文章我们来聊一聊cookie,session和token三者的区别和使用场景。 # 区别与使用场景 Cookie、Session 和 Token 是用来在客户端和服务器之间传递信息的三种常见技术。它们的主要区别在于它们的生命周期、位置和安全性。 Cookie 是服务器发送到浏览器的小文本文件,浏览器会在下次访问同一个网站时发送此文件。Cookie 通常用于跟踪用户的浏览器历史记录,例如购物车内的商品或登录状态。Cookie 的生命周期通常是有限的,它可以在浏览器关闭后失效,也可以设置过期时间。 Session 是服务器端的数据结构,用于在客户端和服务器之间存储信息。
|
缓存 NoSQL Dubbo
因在缓存对象中增加字段,而导致Redis中取出缓存转化成Java对象时出现反序列化失败的问题...
因在缓存对象中增加字段,而导致Redis中取出缓存转化成Java对象时出现反序列化失败的问题...
284 0
|
缓存 Java
ThreadLocal引用分析
ThreadLocal引用分析