ThreadLocal在线程池环境下使用(SpringBoot)
ThreadLocal的问题
当今项目,大多数都会使用线程池的,而ThreadLocal是基于线程的,这就有一个问题,如果ThreadLocal没有及时清理的话,碰到线程被复用的时候,就可以获取到上一次请求的数据。 关于这个问题,网上其实有很多解决方案,但是都是要手动清理ThreadLocal,这个清理的时机,就是一个问题,在什么时候清,如果方法里用完就清,你能保证其他方法就不用了吗?而且手动清理总有不小心漏掉的情况。
解决方案
其实可以通过对ThreadLocal进行一个小小的封装,就可以解决这个问题。
- TheadLocal不要作为业务类的成员变量,新建一个Util,对其进行统一管理。
public class ThreadLocalUtils {
private static final ThreadLocal<Map<String,Object>> threadCache = new ThreadLocal<>();
private static void init(){
Map<String,Object> map = new HashMap<>();
threadCache.set(map);
}
public static void setThreadCache(String key, Object value){
if(threadCache.get() == null){
init();
}
threadCache.get().put(key, value);
}
public static Object getThreadCache(String key){
if(threadCache.get() == null){
init();
}
return threadCache.get().get(key);
}
public static void removeThreadCache(){
threadCache.remove();
}
}
- 通过切面来统一关闭TheadLocal
@Component
@Aspect
public class TheadLocalAspect {
@After(
"@annotation(org.springframework.web.bind.annotation.RequestMapping) || " +
"@annotation(org.springframework.web.bind.annotation.GetMapping) || " +
"@annotation(org.springframework.web.bind.annotation.PostMapping)"
)
public void after(){
ThreadLocalUtils.removeThreadCache();
}
}
至于为什么选择after来处理,看下图: After通知是在目标方法结束后,无论如何都会触发的,用其来清理ThreadLocal最为合适。
使用
使用方式如下(举了一些简单例子):
ThreadLocalUtils.setThreadCache("USER", user);
User user = (User)ThreadLocalUtils.getThreadCache("USER");
因为ThreadLocalUtils的threadCache会由切面统一清理,所以使用时不必清理。
结尾
切面只拦截了Http请求的,所以这个工具类也只适用于Http请求的ThreadLocal管理,但是目前来说SpingBoot架构的互联网业务场景绝大多数是基于Http的,REST请求是Http、SpringCloud的微服务架构也是基于Http,物联网设备请求SpringBoot项目的后台,也是配置的Http。如果真的有非Http请求的情况,修改TheadLocalAspect 类切点即可。
|