ThreadLocal
原理
- ThreadLocal只有当前线程可以访问,每个线程都有自己的变量副本。线程消亡他也消亡,他是变量对象,不是线程。
- 其中存储的内容只有当前线程能访问的
API
void set(T value)
设置当前线程的threadlocal的值T get()
该方法返回当前线程threadlocal的值void removed()
将当前线程threadlocal的值删除。
目的是为了减少内存的占用。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度T initialValue()
返回该线程threadlocal的初始值,该方法是一个 protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用geto或 set(Object))时才执行,并且仅执行1次。 ThreadLocal中的默认实现直接返回一个null
场景使用ThreadLocal
在开发项目中,登录场景是不可避免的,但是再没登录的场景下也可以访问系统的相关功能,这就需要TheadLocal来进行对临时用户的存储及其鉴权。
思路
参考各个商城系统,在点击购物车时,会为临时用户生成一个name为user-key的cookie临时标识,过期时间为一个月,如果手动清除user-key,那么临时购物车的购物项也被清除,所以user-key是用来标识和存储临时购物车数据的
- 首先明确每次拦截器都会重新设置threadlocal
- 没有的话创建一个临时用户,回去的时候告诉用户的临时cookie。threadlocal中只封装临时用户信息
- 有的话把临时用户和登录用户封装到一起,设置到threadlocal中
代码
package com.tomalen.gulimall.product.interceptor;
import com.alibaba.cloud.commons.lang.StringUtils;
import com.tomalen.gulimall.product.constant.ProductConstant;
import com.tomalen.gulimall.product.entity.MemberRespVo;
import com.tomalen.gulimall.product.entity.UserInfoTo;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.UUID;
public class ProductInterceptor implements HandlerInterceptor {
public static ThreadLocal<UserInfoTo> threadLocal = new ThreadLocal<>();
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 准备好要设置到threadlocal里的user对象
UserInfoTo userInfoTo = new UserInfoTo();
HttpSession session = request.getSession();
// 获取loginUser对应的用户value,没有也不去登录了。登录逻辑放到别的代码里,需要登录时再重定向
MemberRespVo user = (MemberRespVo) session.getAttribute(ProductConstant.LOGIN_USER);
if (user != null){ // 用户登陆了,设置userId
userInfoTo.setUsername(user.getUsername());
userInfoTo.setUserId(user.getId());
}
// 不登录也没关系,可以访问临时用户购物车
// 去查看请求带过来的cookies里的临时购物车cookie
Cookie[] cookies = request.getCookies();
if(cookies != null && cookies.length > 0){
for (Cookie cookie : cookies) {
String name = cookie.getName();
if(name.equals(ProductConstant.TEMP_USER_COOKIE_NAME)){
userInfoTo.setUserKey(cookie.getValue());
userInfoTo.setTempUser(true);
}
}
}
// 如果没有临时用户 则分配一个临时用户 // 分配的临时用户在postHandle的时候放到cookie里即可
if (StringUtils.isEmpty(userInfoTo.getUserKey())){
String uuid = UUID.randomUUID().toString().replace("-","");
userInfoTo.setUserKey("GULI-" + uuid);//临时用户
}
threadLocal.set(userInfoTo);
return true;
// 还有一个登录后应该删除临时购物车的逻辑没有实现
}
/**
* 执行完毕之后分配临时用户让浏览器保存
*/
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
UserInfoTo userInfoTo = threadLocal.get();
// 如果是临时用户,返回临时购物车的cookie
if(!userInfoTo.isTempUser()){
Cookie cookie = new Cookie(ProductConstant.TEMP_USER_COOKIE_NAME, userInfoTo.getUserKey());
// 设置这个cookie作用域 过期时间
cookie.setDomain("gulimall.com");
cookie.setMaxAge(ProductConstant.TEMP_USER_COOKIE_TIME_OUT);
response.addCookie(cookie);
}
}
}