人工手打,翻译自:https://moelholm.com/2017/07/24/spring-4-3-using-a-taskdecorator-to-copy-mdc-data-to-async-threads 原本想本身写一篇关于线程池threadlocal的,偶然看到这篇文章以为挺好的,便直接翻译了html
尊重外国人写文章的习惯,若是你初次看到此类翻译可能会形成不愉悦,但若是你曾经看到过,那你必定明白我在说什么,有的地方加上我本身的理解和注释java
在这篇文章里,咱们将会演示如何从web线程里复制MDC数据到@Async注解的线程里,咱们将会使用一个全新的 Spring Framework 4.3的特性: ThreadPoolTaskExecutor#setTaskDecorator() [set-task-decorator]. 下面是最终结果:
git
注意到倒数第二行和第三行:在这个log级别上输出了[userId:Duke],倒数第三行是在一个web线程里(一个使用@RestController注解的类)发出的,倒数第二行是在一个用了@Async注解的异步线程里发出的。本质上,MDC数据从web线程中复制到了使用@Async注解的异步线程里中了(这就是最酷的部分,:smirk:)
继续阅读吧,少年,去看看这是怎么实现的。这篇文章的全部代码均可以在GitGub上的示例中找到。若是有须要的话,能够去看看细节。github
这个示例项目基于Spring Boot 2。日志API这里用的是SLF4J和Logback(用了Logger, LoggerFactory和MDC) 若是你去看了那个示例项目,你将会发现这个@RestController注解的Controlerweb
@RestController public class MessageRestController { private final Logger logger = LoggerFactory.getLogger(getClass()); private final MessageRepository messageRepository; MessageRestController(MessageRepository messageRepository) { this.messageRepository = messageRepository; } @GetMapping List<String> list() throws Exception { logger.info("RestController in action"); return messageRepository.findAll().get(); } }
注意到它输出了日志:RestController in action,同时注意到它有一个古怪的调用:messageRepository.findAll().get(),这是由于它执行了一个异步的方法,接收了一个Future对象,而且调用了get()方法来等待结果返回,因此这是一个在web线程里调用使用@Async注解的异步方法。这是一个很显然的人为的为了演示而写的示例(我猜你在工做中的一些场景中会明智的调用此类异步方法)
下面是那个repository类:spring
@Repository class MessageRepository { private final Logger logger = LoggerFactory.getLogger(getClass()); @Async Future<List<String>> findAll() { logger.info("Repository in action"); return new AsyncResult<>(Arrays.asList("Hello World", "Spring Boot is awesome")); } }
注意到findAll方法里打印了日志:Repository in action。
为了完整起见,让我向你展现如何在web线程里设置MDC数据的:api
@Component public class MdcFilter extends GenericFilterBean { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { try { MDC.put("mdcData", "[userId:Duke]"); chain.doFilter(request, response); } finally { MDC.clear(); } } }
若是咱们什么也不作,咱们能够在web线程里很轻松的拿到正确配置的MDC数据,可是当一个web请求进入了@Async注解的异步方法调用里,咱们却不能跟踪它:MDC数据里的ThreadLocal数据不会简单的自动复制过来,好消息是这个超级简单解决app
首先,定制化你的异步功能,我是这样作的:异步
@EnableAsync(proxyTargetClass = true) @SpringBootApplication public class Application 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(Application.class, args); } }
有意思的地方是咱们扩展了AsyncConfigurerSupport,好让咱们能够自定义线程池
更精确的说:秘密在于executor.setTaskDecorator(new MdcTaskDecorator())。就是这行代码使咱们能够自定义TaskDecoratorasync
如今到了说明自定义的TaskDecorator:
class MdcTaskDecorator implements TaskDecorator { @Override public Runnable decorate(Runnable runnable) { // Right now: Web thread context ! // (Grab the current thread MDC data) Map<String, String> contextMap = MDC.getCopyOfContextMap(); return () -> { try { // Right now: @Async thread context ! // (Restore the Web thread context's MDC data) MDC.setContextMap(contextMap); runnable.run(); } finally { MDC.clear(); } }; } }
decorate()方法的参数是一个Runnable对象,返回结果也是另外一个Runnable对象
这里,我只是把原始的Runnable对象包装了一下,首先取得MDC数据,而后把它放到了委托的run方法里(Here, I basically wrap the original Runnable and maintain the MDC data around a delegation to its run() method.英文原文是这样,太难翻译了,囧)
从web线程里复制MDC数据到异步线程是如此的容易,这里展现的技巧不局限于复制MDC数据,你也可使用它来复制其余ThreadLocal数据(MDC内部就是使用ThreadLocal),或者你可使用TaskDecorator作一些其余彻底不一样的事情:记录日志,度量方法执行的时间,吞掉异常,退出JVM等等,只要你喜欢
墙裂感谢Joris Kuipers (@jkuipers)提醒我这个牛逼的Spring Framework 4.3新功能, An awesome tip :hugging:(这一句怎么翻译?)
[set-task-decorator] ThreadPoolTaskExecutor#setTaskDecorator() (Spring’s JavaDoc) https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/scheduling/concurrent/ThreadPoolTaskExecutor.html#setTaskDecorator-org.springframework.core.task.TaskDecorator-
如下本身的总结:
关于threadlocal的代码细节,见个人另一篇文章:再看ThreadLocal