Spring framework очень большая штука и пришлось потратить полдня, чтобы изучить сторону с @Async выполнением и виртуальными потоками.
Чтобы у нас не терялась трассировка и контекст при выполнении кода в отдельных потоках необходимо задать отдельную, простую конфигурацию.
Вот целиком конфигурация
@Configuration
public class AsyncConfig {
@Bean
public ContextSnapshotFactory contextSnapshotFactory() {
return ContextSnapshotFactory.builder().build();
}
@Bean(name = TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME)
public AsyncTaskExecutor applicationTaskExecutor(ContextSnapshotFactory contextSnapshotFactory) {
ThreadFactory virtualThreadFactory = Thread.ofVirtual()
.name("async-vt-", 0)
.factory();
ExecutorService virtualExecutor = Executors.newThreadPerTaskExecutor(virtualThreadFactory);
Executor decoratedExecutor = ContextExecutorService.wrap(
virtualExecutor,
contextSnapshotFactory::captureAll
);
return new TaskExecutorAdapter(decoratedExecutor);
}
@Bean(name="virtualThreadsParallelExecutor", destroyMethod = "close")
public ExecutorService parallelExecutor(ContextSnapshotFactory contextSnapshotFactory) {
ThreadFactory virtualThreadFactory = Thread.ofVirtual()
.name("parallel-custom-vt-", 0)
.factory();
ExecutorService virtualExecutor = Executors.newThreadPerTaskExecutor(virtualThreadFactory);
return ContextExecutorService.wrap(
virtualExecutor,
contextSnapshotFactory::captureAll
);
}
}
ContextSnapshotFactory тут то, что будет заполнять контекст трассировки и прочего в новом потоке.
virtualThreadsParallelExecutor — кастомный экзекьютор, который можно инжектить в код. Удобно, не нужно обвесы делать.
AsyncTaskExecutor — экзекьютор, который будет выполнять код методов под аннотацией @Async