起因
在开发过程中,因为业务逻辑比较复杂,需要异步返回,所以需要开启子线程去操作
这些操作中是需要保存数据的,而恰好用的是jpa,并且有@CreatedBy注解,保存数据时,会去找到实现 AuditorAware接口的类,去获取当前操作的用户名。
但是因为开启了多线程,原本使用的方法是通过下面的方法获取,现在获取的request是null了
((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest()
分析
在求知欲(铁打的工作)的不断探索(百度)下,发现是因为开启子线程后,不享有主线程的request导致的。因此现在就需要想办法让子线程也 可以拿到request里面的当前用户信息。
解决方法
第一种方法:RequestAttributes设置为子线程共享
这是搜索下答案最多一个,就是下面这个方法,放在开启子线程前就好了(但是看有人说主线程销毁了,子线程就获取不到了,后续有时间再细细研究,也欢迎研究过的大佬指点指点)
ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
RequestContextHolder.setRequestAttributes(sra, true);
第二种方法:使用ThreadLocal
在寻找的过程中,我发现了ThreadLocal,然后还发现ThreadLocal的set方法如下:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
可以看出,set方法会和当前线程绑定。
这样就可以利用ThreadLocal的set的值会和线程关联起来的性质,先把需要使用的用户信息从主线程获取,并用一个变量去接收
接着在开启子线程后,把这个变量的值set到ThreadLocal对象里。在需要用的地方直接调用就可以拿到。
jpa使用@CreatedBy注解时,需要实现的类
@Component
public class AuditorAwareImpl implements AuditorAware<String> {
@Override
public Optional<String> getCurrentAuditor() {
String loginUser = ContextHolder.getCurrentUser();
return Optional.of(loginUser);
}
}
获取当前操作用户的类
public class ContextHolder {
//在此定义一个ThreadLocal对象去接收子线程的用户信息
private static ThreadLocal<String> tl = new ThreadLocal<>();
private ContextHolder() {
}
//获取request
public static HttpServletRequest getRequest() {
return RequestContextHolder.getRequestAttributes() == null ? null: ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
}
//调用该方法设置
public static void setLoginUser(String loginUser){
tl.set(loginUser);
}
//结束任务后将该子线程的信息移除
public static void removeLoginUser(){
tl.remove();
}
public static String getCurrentUser(){
HttpServletRequest request = getRequest();
//判断如果request为空,则去找子线程的操作用户信息
return request == null ? tl.get() : request.getHeader("loginUser");
}
}
示例代码:
public class testThreadLocal() {
//先从主线程获取当前用户信息
String loginUser = ContextHolder.getCurrentUser();
new Thread(() -> {
ContextHolder.setLoginUser(loginUser);
//do something
ContextHolder.removeLoginUser();
});
}