一,问题提出:
通常在项目设计开发中,用户登录后,我们会将用户的信息存到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为每个线程提供了单独一份存储空间,具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问。
后续直接在你的登录方法中调用这个类然后把用户信息通过setCurrentId或者getCurrentId进行添加或者调用就行,当然你也可以修改这个方法。