Spring Boot中标记@Async的方法在Map对象操作阶段停止执行问题排查
看起来你的异步邮件通知任务还没执行完,就因为Spring容器关闭导致线程池被终止了。从日志里能看到,异步线程AsyncExecutor-1刚执行到创建验证令牌的步骤,extShutdownHook就触发了线程池关闭操作——这说明主线程(测试用例的main线程)已经执行完毕,触发了JVM shutdown,Spring容器开始销毁资源,异步任务被强制中断。
问题根源
你的sendNotification方法返回CompletableFuture<Void>,但测试用例里没有等待这个异步任务执行完成就结束了主线程。Spring的ThreadPoolTaskExecutor默认在容器关闭时会立即终止所有正在运行的任务,不会等待它们完成。
解决方案
我给你两个关键步骤来解决这个问题:
1. 在测试用例中等待异步任务完成
既然你的异步方法返回CompletableFuture,在测试代码里调用它的join()或者get()方法,让主线程等待异步任务执行完毕再退出:
@Test void testSendNotification() { AppUser testUser = new AppUser(); testUser.setEmail("test@example.com"); // 调用异步方法 CompletableFuture<Void> future = emailService.sendNotification(testUser); // 等待任务完成 future.join(); // 或者用 future.get(),需要处理InterruptedException和ExecutionException }
这样主线程会一直等到异步任务执行完sendMessage方法,再触发容器关闭流程。
2. 配置线程池等待任务完成后再关闭
为了避免生产环境中也出现类似的容器关闭时中断异步任务的问题,你需要在ThreadPoolTaskExecutor的配置里添加两个关键参数:
@Bean(name = "getAsyncExecutor") public Executor asyncExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(3); executor.setMaxPoolSize(5); executor.setQueueCapacity(100); executor.setThreadNamePrefix("AsyncExecutor-"); // 容器关闭时等待所有任务完成 executor.setWaitForTasksToCompleteOnShutdown(true); // 最多等待60秒,超时后强制终止 executor.setAwaitTerminationSeconds(60); executor.initialize(); return executor; }
setWaitForTasksToCompleteOnShutdown(true):告诉线程池在关闭时不要立即终止任务,等待所有正在执行的任务和队列里的任务完成。setAwaitTerminationSeconds(60):设置等待的最大时长,防止线程池无限等待。
额外检查点
顺便提一下,你的confirmRegistration方法里调用sendMessage时传了一个空的ConcurrentHashMap,而不是你刚创建的params——这应该是个笔误,记得改成sendMessage(params, Locale.getDefault());,不然邮件参数会丢失。
内容的提问来源于stack exchange,提问作者Oluwatobi Omotosho

