使用Spring MVC,我有以下设置:
我尝试使用MDC(或ThreadLocal对象)为处理请求所涉及的所有组件收集上下文信息。
我可以从@Async线程正确检索MDC上下文信息。但是,如果@Async线程要向MDC添加上下文信息,我现在如何将MDC上下文信息封送到处理响应的线程?
TaskDecorator
public class MdcTaskDecorator implements TaskDecorator {
@Override
public Runnable decorate(Runnable runnable) {
// Web thread context
// Get the logging MDC context
Map<String, String> contextMap = MDC.getCopyOfContextMap();
return () -> {
try {
// @Async thread context
// Restore the web thread MDC context
if(contextMap != null) {
MDC.setContextMap(contextMap);
}
else {
MDC.clear();
}
// Run the new thread
runnable.run();
}
finally {
MDC.clear();
}
};
}
}
异步方法
@Async
public CompletableFuture<String> doSomething_Async() {
MDC.put("doSomething", "started");
return doit();
}
记录过滤器
public class ServletLoggingFilter extends AbstractRequestLoggingFilter {
@Override
protected void beforeRequest(HttpServletRequest request, String message) {
MDC.put("webthread", Thread.currentThread().getName()); // Will be webthread-1
}
@Override
protected void afterRequest(HttpServletRequest request, String message) {
MDC.put("responsethread", Thread.currentThread().getName()); // Will be webthread-2
String s = MDC.get("doSomething"); // Will be null
// logthis();
}
}
答案 0 :(得分:2)
我希望您已经解决了这个问题,但是如果您没有解决,那么这里有一个解决方案。
您要做的所有事情都可以概括为以下两个简单步骤:
MdcTaskDecorator
。AsyncConfigurerSupport
,并覆盖getAsyncExecutor()
以使用您自定义的装饰器设置装饰器。 public class AsyncTaskdecoratorApplication extends AsyncConfigurerSupport {
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setTaskDecorator(new MdcTaskDecorator());
executor.initialize();
return executor;
}
public static void main(String[] args) {
SpringApplication.run(AsyncTaskdecoratorApplication.class, args);
}
}
答案 1 :(得分:0)
创建一个 bean,它将 MDC 属性从父线程传递到后继线程。
@Configuration
@Slf4j
public class AsyncMDCConfiguration {
@Bean
public Executor asyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setTaskDecorator(new MDCTaskDecorator());//MDCTaskDecorator i s a custom created class thet implements TaskDecorator that is reponsible for passing on the MDC properties
executor.initialize();
return executor;
}
}
@Slf4j
public class MDCTaskDecorator implements TaskDecorator {
@Override
public Runnable decorate(Runnable runnable) {
Map<String, String> contextMap = MDC.getCopyOfContextMap();
return () -> {
try {
MDC.setContextMap(contextMap);
runnable.run();
} finally {
MDC.clear();
}
};
}
}
现在一切都好。快乐编码
答案 2 :(得分:-2)
我有一些解决方案,大致分为Callable(对于@Async),AsyncExecutionInterceptor(对于@Async),CallableProcessingInterceptor(对于控制器)。
1。用于将上下文信息放入@Async线程的Callable解决方案:
关键是使用ContextAwarePoolExecutor替换@Async的默认执行器:
@Configuration
公共类DemoExecutorConfig {
@Bean("demoExecutor")
public Executor contextAwarePoolExecutor() {
return new ContextAwarePoolExecutor();
}
}
并且ContextAwarePoolExecutor覆盖了内部具有ContextAwareCallable的commit和submitListenable方法:
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()));
}
/**
* set infos what we need
*/
private ThreadContextContainer newThreadContextContainer() {
ThreadContextContainer container = new ThreadContextContainer();
container.setRequestAttributes(RequestContextHolder.currentRequestAttributes());
container.setContextMapOfMDC(MDC.getCopyOfContextMap());
return container;
}
}
ThreadContextContainer只是为了方便存储信息的一种方式:
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(原始任务的可调用代理)在原始任务执行其调用方法之前,将调用方法覆盖存储MDC或其他上下文信息:
public class ContextAwareCallable<T> implements Callable<T> {
/**
* the original task
*/
private Callable<T> task;
/**
* for storing infos what we need
*/
private ThreadContextContainer threadContextContainer;
public ContextAwareCallable(Callable<T> task, ThreadContextContainer threadContextContainer) {
this.task = task;
this.threadContextContainer = threadContextContainer;
}
@Override
public T call() throws Exception {
// set infos
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 {
// execute the original task
return task.call();
} finally {
// clear infos after task completed
RequestContextHolder.resetRequestAttributes();
try {
MDC.clear();
} finally {
}
}
}
}
最后,将@Async与配置的bean“ demoExecutor”一起使用,像这样:@Async("demoExecutor")
void yourTaskMethod();
2。关于您处理回复的问题:
遗憾地告诉我我确实没有经过验证的解决方案。也许org.springframework.aop.interceptor.AsyncExecutionInterceptor#invoke可以解决这个问题。
我认为它没有解决方案可以通过ServletLoggingFilter处理响应。因为异步方法将立即返回。 afterRequest方法立即执行并在Async方法执行之前返回。除非您同步等待Async方法完成执行,否则您将无法获得所需的结果。
但是,如果您只想记录某些内容,则可以在原始任务执行其调用方法之后,将这些代码添加到我的示例ContextAwareCallable中:
try {
// execute the original task
return task.call();
} finally {
String something = MDC.get("doSomething"); // will not be null
// logthis(something);
// clear infos after task completed
RequestContextHolder.resetRequestAttributes();
try {
MDC.clear();
} finally {
}
}