我有一些解决方案,大致分为Callable(用于@Async),AsyncExecutionInterceptor(用于@Async)和CallableProcessingInterceptor(用于控制器)。
1. Callable解决方案可将上下文信息放入@Async线程中:
关键是使用ContextAwarePoolExecutor替换@Async的默认执行器:
@Configuration
公共类 DemoExecutorConfig {
@Bean("demoExecutor")
public Executor contextAwarePoolExecutor() {
return new ContextAwarePoolExecutor();
}
}
而 ContextAwarePoolExecutor 则通过在 submit 和 submitListenable 方法中使用 ContextAwareCallable 来进行重写:
public class ContextAwarePoolExecutor extends ThreadPoolTaskExecutor {
private static final long serialVersionUID = 667815067287186086L;
@Override
public <T> Future<T> submit(Callable<T> task) {
return super.submit(new ContextAwareCallable<T>(task, newThreadContextContainer()));
}
@Override
public <T> ListenableFuture<T> submitListenable(Callable<T> task) {
return super.submitListenable(new ContextAwareCallable<T>(task, newThreadContextContainer()));
}
private ThreadContextContainer newThreadContextContainer() {
ThreadContextContainer container = new ThreadContextContainer();
container.setRequestAttributes(RequestContextHolder.currentRequestAttributes());
container.setContextMapOfMDC(MDC.getCopyOfContextMap());
return container;
}
}
ThreadContextContainer只是一个POJO,用于方便地存储信息:
public class ThreadContextContainer implements Serializable {
private static final long serialVersionUID = -6809291915300091330L;
private RequestAttributes requestAttributes;
private Map<String, String> contextMapOfMDC;
public RequestAttributes getRequestAttributes() {
return requestAttributes;
}
public Map<String, String> getContextMapOfMDC() {
return contextMapOfMDC;
}
public void setRequestAttributes(RequestAttributes requestAttributes) {
this.requestAttributes = requestAttributes;
}
public void setContextMapOfMDC(Map<String, String> contextMapOfMDC) {
this.contextMapOfMDC = contextMapOfMDC;
}
}
ContextAwareCallable(原始任务的Callable代理)重写call方法,在原始任务执行其call方法之前存储MDC或其他上下文信息:
public class ContextAwareCallable<T> implements Callable<T> {
private Callable<T> task;
private ThreadContextContainer threadContextContainer;
public ContextAwareCallable(Callable<T> task, ThreadContextContainer threadContextContainer) {
this.task = task;
this.threadContextContainer = threadContextContainer;
}
@Override
public T call() throws Exception {
if (threadContextContainer != null) {
RequestAttributes requestAttributes = threadContextContainer.getRequestAttributes();
if (requestAttributes != null) {
RequestContextHolder.setRequestAttributes(requestAttributes);
}
Map<String, String> contextMapOfMDC = threadContextContainer.getContextMapOfMDC();
if (contextMapOfMDC != null) {
MDC.setContextMap(contextMapOfMDC);
}
}
try {
return task.call();
} finally {
RequestContextHolder.resetRequestAttributes();
try {
MDC.clear();
} finally {
}
}
}
}
最后,使用配置的bean“demoExecutor”和@Async一起使用,像这样:@Async("demoExecutor")
void yourTaskMethod();
2.关于您处理响应的问题:
很遗憾,我没有一个经过验证的解决方案。也许org.springframework.aop.interceptor.AsyncExecutionInterceptor#invoke可以解决这个问题。
而且我认为使用ServletLoggingFilter无法处理响应。因为异步方法会立即返回。afterRequest方法立即执行并在异步方法执行之前返回。除非您同步等待异步方法完成执行,否则您将无法获得想要的结果。
但是,如果您只想记录一些内容,您可以在我的示例ContextAwareCallable中添加这些代码,原始任务执行其调用方法后:
try {
return task.call();
} finally {
String something = MDC.get("doSomething");
RequestContextHolder.resetRequestAttributes();
try {
MDC.clear();
} finally {
}
}
decorate(Runnable runnable)
中,我为contextMap添加了一个空值检查,因为在我的应用程序中它有些为空:@Override
public Runnable decorate(Runnable runnable) { ` Map<String, String> contextMap = MDC.getCopyOfContextMap(); if (contextMap != null) { return () -> { try { MDC.setContextMap(contextMap); runnable.run(); } finally { MDC.clear(); } } } else { return runnable}; } - Hung Nguyen Duy