使用@Async和TaskDecorator记录MDC

10
使用Spring MVC,我有以下设置:
  1. 一个继承自AbstractRequestLoggingFilter的过滤器,用于记录请求。
  2. 一个任务装饰器,将MDC上下文映射从Web请求线程转换到@Async线程。

我正在尝试使用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 method

@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();
}

}

3个回答

7
创建一个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();
      }
    };
  }
}

一切都好了。愉快编程。


这对我非常有效!但是,在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

5

我希望您已经解决了问题,但如果没有解决,这里有一个解决方案。
你所需要做的可以简单总结为以下两个步骤:

  1. 保留你的类 MdcTaskDecorator
  2. 为你的主类继承 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);
        }
    }

-3

我有一些解决方案,大致分为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()));
}

/**
 * set infos what we need
 */
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> {

/**
 * 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 {
        }
    }
}

}

最后,使用配置的bean“demoExecutor”和@Async一起使用,像这样:@Async("demoExecutor") void yourTaskMethod();

2.关于您处理响应的问题:

很遗憾,我没有一个经过验证的解决方案。也许org.springframework.aop.interceptor.AsyncExecutionInterceptor#invoke可以解决这个问题。

而且我认为使用ServletLoggingFilter无法处理响应。因为异步方法会立即返回。afterRequest方法立即执行并在异步方法执行之前返回。除非您同步等待异步方法完成执行,否则您将无法获得想要的结果。

但是,如果您只想记录一些内容,您可以在我的示例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 {
        }
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接