APP下载

执行绪池中使用ThreadLocal方案

消息来源:baojiabao.com 作者: 发布时间:2024-05-21

报价宝综合消息执行绪池中使用ThreadLocal方案

在这篇文章里,我们将会演示如何从web执行绪里复制MDC资料到@Async注解的执行绪里,我们将会使用一个全新的 Spring Framework 4.3的特性: ThreadPoolTaskExecutor#setTaskDecorator() [set-task-decorator]. 下面是最终结果:

注意到倒数第二行和第三行:在这个log级别上输出了[userId:Duke],倒数第三行是在一个web执行绪里(一个使用@RestController注解的类)发出的,倒数第二行是在一个用了@Async注解的异步执行绪里发出的。本质上,MDC资料从web执行绪中复制到了使用@Async注解的异步执行绪里中了(这就是最酷的部分,:smirk:)

这篇文章的所有程式码都可以在GitGub上的示例中找到。如果有需要的话,可以去看看细节。

关于示例专案

这个示例专案基于Spring Boot 2。日志API这里用的是SLF4J和Logback(用了Logger, LoggerFactory和MDC) 如果你去看了那个示例专案,你将会发现这个@RestController注解的Controler

@RestController

public class MessageRestController {

private final Logger logger = LoggerFactory.getLogger(getClass());

private final MessageRepository messageRepository;

MessageRestController(MessageRepository messageRepository) {

this.messageRepository = messageRepository;

}

@GetMapping

List list() throws Exception {

logger.info("RestController in action");

return messageRepository.findAll().get();

}

}

注意到它输出了日志:RestController in action,同时注意到它有一个古怪的呼叫:messageRepository.findAll().get(),这是因为它执行了一个异步的方法,接收了一个Future物件,并且呼叫了get()方法来等待结果返回,所以这是一个在web执行绪里呼叫使用@Async注解的异步方法。这是一个很显然的人为的为了演示而写的示例(我猜你在工作中的一些场景中会明智的呼叫此类异步方法)

下面是那个repository类:

@Repository

class MessageRepository {

private final Logger logger = LoggerFactory.getLogger(getClass());

@Async

Future> findAll() {

logger.info("Repository in action");

return new AsyncResult(Arrays.asList("Hello World", "Spring Boot is awesome"));

}

}

注意到findAll方法里打印了日志:Repository in action。

为了完整起见,让我向你展示如何在web执行绪里设定MDC资料的:

@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资料不会简单的自动复制过来,好讯息是这个超级简单解决

解决方案第一步: 配置@Async执行绪池

首先,定制化你的异步功能,我是这样做的:

@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())。就是这行程式码使我们可以自定义TaskDecorator

解决方案第二步: 实现TaskDecorator

现在到了说明自定义的TaskDecorator:

class MdcTaskDecorator implements TaskDecorator {

@Override

public Runnable decorate(Runnable runnable) {

// Right now: Web thread context !

// (Grab the current thread MDC data)

Map 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等等,只要你喜欢。

感谢您的观看,喜欢的小伙伴可以点个赞!!!专注Java、大资料知识干货及相关领域动态分享,请多多关注哦!

2020-01-13 12:02:00

相关文章