1.介绍
在并发请求情况下,因为每次请求都有不同的用户信息,我们必须保证每次请求保存的用户信息互不干扰,线程独立。注意:这里不是解决多线程资源共享问题,而是要保证每个线程都有自己的用户资源,互不干扰。
ThreadLocal的作用主要是做数据隔离,填充的数据只属于当前线程,变量的数据对别的线程而言是相对隔离的,在多线程环境下,如何防止自己的变量被其它线程篡改。
而JDK中提供的ThreadLocal
恰好满足这个需求,那么ThreadLocal是如何实现这一需求的呢?
关键点:
- 每个线程(
Thread
)内部都持有一个ThreadLocalMap
对象。 ThreadLocalMap
的Key是某个ThreadLocal
对象,值是任意Object。- 不同线程,内部有自己的
ThreadLocalMap
,因此Map中的资源互相不会干扰。
数据在堆栈中的存储示意图:
2.基本使用
public class UserHolder {
private static final ThreadLocal<Long> TL = new ThreadLocal<>();
public static void setUserId(Long userId) {
TL.set(userId);
}
public static Long getUserId() {
return TL.get();
}
public static void removeUserId() {
TL.remove();
}
}
3. 工具类
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
/**
* @author 暗余
* @date 2021/7/17 15:44
*/
@SuppressWarnings("unused")
public final class ThreadLocalUtils {
private static final ThreadLocal<Map<String, Object>> THREAD_LOCAL =
ThreadLocal.withInitial(() -> new ConcurrentHashMap<>(16));
/**
* 获取到ThreadLocal中值
*
* @return ThreadLocal存储的是Map
*/
public static Map<String, Object> getThreadLocal() {
return THREAD_LOCAL.get();
}
/**
* 从ThreadLocal中的Map获取值
*
* @param key Map中的key
* @param <T> Map中的value的类型
* @return Map中的value值 可能为空
*/
public static <T> T get(String key) {
return get(key, null);
}
/**
* 从ThreadLocal中的Map获取值
*
* @param key Map中的key
* @param defaultValue Map中的value的为null 是 的默认值
* @param <T> Map中的value的类型
* @return Map中的value值 可能为空
*/
@SuppressWarnings("unchecked")
public static <T> T get(String key, T defaultValue) {
Map<String, Object> map = THREAD_LOCAL.get();
if (MapUtils.isEmpty(map)) {
return null;
}
return (T) Optional.ofNullable(map.get(key)).orElse(defaultValue);
}
/**
* ThreadLocal中的Map设置值
*
* @param key Map中的key
* @param value Map中的value
*/
public static void set(String key, Object value) {
Map<String, Object> map = THREAD_LOCAL.get();
map.put(key, value);
}
/**
* ThreadLocal中的Map 添加Map
*
* @param keyValueMap 参数map
*/
public static void set(Map<String, Object> keyValueMap) {
Map<String, Object> map = THREAD_LOCAL.get();
map.putAll(keyValueMap);
}
/**
* 删除ThreadLocal中的Map 中的value
*
* @param key Map中的key
*/
public static void delete(String key) {
Map<String, Object> map = THREAD_LOCAL.get();
if (MapUtils.isEmpty(map)) {
return;
}
map.remove(key);
}
/**
* 删除ThreadLocal中的Map
*/
public static void remove() {
THREAD_LOCAL.remove();
}
/**
* 从ThreadLocal中的Map获取值 根据可key的前缀
*
* @param prefix key 的前缀
* @param <T> Map中的value的类型
* @return 符合条件的Map
*/
@SuppressWarnings("unchecked")
public static <T> Map<String, T> fetchVarsByPrefix(String prefix) {
Map<String, T> vars = new HashMap<>(16);
if (StringUtils.isBlank(prefix)) {
return vars;
}
Map<String, Object> map = THREAD_LOCAL.get();
if (MapUtils.isEmpty(map)) {
return vars;
}
return map.entrySet().stream().filter(test -> test.getKey().startsWith(prefix))
.collect(Collectors.toMap(Map.Entry::getKey, time -> (T) time.getValue()));
}
/**
* 删除ThreadLocal中的Map 中的Value 按 Map中的Key的前缀
*
* @param prefix Map中的Key的前缀
*/
public static void deleteVarsByPrefix(String prefix) {
if (StringUtils.isBlank(prefix)) {
return;
}
Map<String, Object> map = THREAD_LOCAL.get();
if (MapUtils.isEmpty(map)) {
return;
}
map.keySet().stream().filter(o -> o.startsWith(prefix)).collect(Collectors.toSet()).forEach(map::remove);
}
}
4. 例子
package net.facelib.eam.plancenter.webController.config;
import com.alibaba.fastjson.JSONObject;
import net.facelib.eam.*;
import net.facelib.eam.plancenter.common.MyPlanConstant;
import net.facelib.eam.plancenter.common.PlanException;
import net.facelib.eam.plancenter.webController.utils.ServletUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
/**
* @author lc
* @version 1.0
* @date 2022/7/5 9:23
*/
@Aspect
@Component
@Order(1)
public class PermissionAspect {
private static Logger logger = LoggerFactory.getLogger(PermissionAspect.class);
private static ThreadLocal<Map<String, Object>> userInfo = new ThreadLocal<>();
@Autowired
protected ITokenAccessor tokenAccessor;
@Pointcut("execution(* net.facelib.eam.plancenter.webController.service..*.*(..))")
public void pointCut(){
}
@Before("pointCut()")
public void before(JoinPoint joinPoint){
try {
String tokenid = ServletUtils.getRequest().getHeader("tokenid");
Token token = tokenAccessor.getToken(tokenid);
Map<String, Object> map = new HashMap<>();
map.put("userId", token.getId());
map.put("userName", token.getName());
map.put("props", token.getProps());
map.put("tokenid", tokenid);
userInfo.set(map);
} catch (Exception e){
logger.error("token解析失败"+ e);
throw new PlanException(EamErrorType.EAM_INVALID_TOKEN.getValue(), MyPlanConstant.PC_USER_TOKEN_ERROR, e);
}
}
public static Long getUserId() {
Map<String, Object> map = userInfo.get();
Long userId = Long.valueOf(map.get("userId").toString());
return userId;
}
public static String getUserName() {
Map<String, Object> map = userInfo.get();
return map.get("nickName").toString();
}
public static JSONObject getProps() {
Map<String, Object> map = userInfo.get();
JSONObject jsonObject = JSONObject.parseObject(map.get("props").toString());
return jsonObject;
}
public static String getTokeid() {
Map<String, Object> map = userInfo.get();
String tokenid = map.get("tokenid").toString();
return tokenid;
}
}