人工手打,翻譯自: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