现象:利用Async注解异步调用 ,在异步调用方法过程中获取请求上下文报空指针。
原因分析:RequestContextHolder内部采用ThreadLocal(不支持继承),切换线程时无法获取到请求信息。
解决方案:
实现上下文复制装饰器 org.springframework.core.task.TaskDecorator
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import java.util.concurrent.Callable;
/**
* @author xietao
* @since 2023/12/13 19:24
*/
public class ContextCopyCallableDecorator<V> implements Callable<V> {
private Callable<V> delegate;
private RequestAttributes context;
public ContextCopyCallableDecorator(Callable<V> delegate) {
this.delegate = delegate;
this.context = RequestContextHolder.getRequestAttributes();
}
@Override
public V call() throws Exception {
try {
RequestContextHolder.setRequestAttributes(context);
return delegate.call();
} finally {
RequestContextHolder.resetRequestAttributes();
}
}
}
实现自定义Executor
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
/**
* @author xietao
* @since 2023/12/13 19:22
*/
public class ContextCopyExecutor extends SimpleAsyncTaskExecutor {
@Override
public <T> Future<T> submit(Callable<T> task) {
return super.submit(new ContextCopyCallableDecorator<>(task));
}
}
继承org.springframework.scheduling.annotation.AsyncConfigurer
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.stereotype.Component;
import java.util.concurrent.Executor;
@Component
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
public AsyncConfig() {
}
@Override
public Executor getAsyncExecutor() {
return new ContextCopyExecutor();
}
}
问题点:
异步线程如果在执行前睡眠一段时间后在获取请求上下文,会由于浅拷贝导致一系列问题。
解决方案:
深拷贝请求
业务上避免这种问题