You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot使用AWS SES发邮件:线程异步失效导致请求阻塞求助

Spring Boot 结合 AWS SES 异步发邮件阻塞问题解决指南

看起来你手动用线程实现异步发邮件没生效,导致其他请求被阻塞——这大概率是因为手动线程的实现没贴合Spring的请求上下文机制,或者没有正确把邮件发送的阻塞逻辑隔离到独立线程池里。我给你梳理下最规范的解决思路和代码示例:

一、别自己手动造线程了,用Spring官方的@Async注解(最靠谱)

Spring的@Async是专门用来处理异步任务的,它基于线程池管理,能确保异步任务不占用请求线程,让你的列表查询请求能并行处理。

步骤1:开启异步支持

在你的Spring Boot启动类上加上@EnableAsync注解,告诉Spring要启用异步任务功能:

@SpringBootApplication
@EnableAsync
public class YourAppApplication {
    public static void main(String[] args) {
        SpringApplication.run(YourAppApplication.class, args);
    }
}

步骤2:把邮件发送逻辑抽成异步Service

单独创建一个邮件发送的Service类,把发送邮件的方法标记为@Async——注意这个方法必须是public,而且不能和调用它的Controller在同一个类里(因为Spring的AOP代理只能拦截外部类的调用):

@Service
public class SesEmailService {

    // 如果用JavaMail结合AWS SES SMTP,注入JavaMailSender
    private final JavaMailSender mailSender;
    // 如果用AWS SDK直接调用SES,注入AmazonSesAsyncClient(异步客户端更优)
    private final AmazonSesAsyncClient sesAsyncClient;

    // 构造注入(推荐用构造注入,避免循环依赖)
    public SesEmailService(JavaMailSender mailSender, AmazonSesAsyncClient sesAsyncClient) {
        this.mailSender = mailSender;
        this.sesAsyncClient = sesAsyncClient;
    }

    // 用JavaMail发送的异步方法
    @Async("emailThreadPool") // 指定自定义线程池,避免用默认线程池
    public CompletableFuture<Void> sendEmailWithJavaMail() {
        try {
            // 构建MimeMessage,填充收件人、主题、内容
            MimeMessage message = mailSender.createMimeMessage();
            MimeMessageHelper helper = new MimeMessageHelper(message, true);
            helper.setTo("recipient@example.com");
            helper.setSubject("测试邮件");
            helper.setText("这是一封异步发送的邮件", true);

            // 发送邮件(这里的阻塞逻辑会在异步线程里执行)
            mailSender.send(message);
            return CompletableFuture.completedFuture(null);
        } catch (Exception e) {
            // 处理发送异常,比如日志记录
            log.error("邮件发送失败", e);
            return CompletableFuture.failedFuture(e);
        }
    }

    // 用AWS SES异步客户端发送的方法(更推荐,因为SDK本身支持异步)
    @Async("emailThreadPool")
    public CompletableFuture<Void> sendEmailWithSesAsync() {
        SendEmailRequest request = new SendEmailRequest()
                .withDestination(new Destination().withToAddresses("recipient@example.com"))
                .withMessage(new Message()
                        .withSubject(new Content().withData("测试邮件"))
                        .withBody(new Body().withHtml(new Content().withData("<h1>异步邮件内容</h1>"))))
                .withSource("sender@example.com");

        // SES异步客户端会返回Future,我们把它包装成CompletableFuture
        return sesAsyncClient.sendEmailAsync(request)
                .thenApply(response -> null)
                .exceptionally(e -> {
                    log.error("SES邮件发送失败", e);
                    throw new RuntimeException("邮件发送失败", e);
                });
    }
}

步骤3:配置自定义线程池(可选但强烈推荐)

默认的Spring异步线程池配置比较保守,我们可以自定义一个线程池,专门处理邮件发送任务,避免和其他异步任务抢占资源:

@Configuration
public class AsyncThreadPoolConfig {

    @Bean(name = "emailThreadPool")
    public ThreadPoolTaskExecutor emailThreadPool() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5); // 核心线程数
        executor.setMaxPoolSize(10); // 最大线程数
        executor.setQueueCapacity(20); // 任务队列容量
        executor.setThreadNamePrefix("SES-Email-Sender-"); // 线程名称前缀,方便日志排查
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); // 任务拒绝策略,避免丢任务
        executor.initialize();
        return executor;
    }
}

步骤4:在Controller里调用异步方法

Controller里调用邮件Service的异步方法后,会立即返回响应,不会等待邮件发送完成,这样列表查询请求就能并行处理了:

@RestController
public class DemoController {

    private final SesEmailService sesEmailService;
    private final YourDataService yourDataService;

    public DemoController(SesEmailService sesEmailService, YourDataService yourDataService) {
        this.sesEmailService = sesEmailService;
        this.yourDataService = yourDataService;
    }

    @GetMapping("/send-email")
    public ResponseEntity<String> triggerEmail() {
        // 调用异步邮件方法,立即返回
        sesEmailService.sendEmailWithJavaMail();
        return ResponseEntity.ok("邮件发送请求已提交,正在处理中");
    }

    @GetMapping("/list")
    public ResponseEntity<List<YourEntity>> getList() {
        // 列表查询逻辑,完全不会被邮件发送阻塞
        List<YourEntity> dataList = yourDataService.queryDataList();
        return ResponseEntity.ok(dataList);
    }
}

二、为什么你手动开线程没生效?

大概率是这几个原因:

  1. 手动线程没有脱离请求上下文:Spring的请求线程会绑定一些资源(比如数据库连接、事务上下文),如果你的手动线程没有正确隔离,可能还是会阻塞请求线程。
  2. 线程没有正确启动:比如你创建了Thread对象但没调用start()方法,还是在当前线程执行。
  3. Tomcat线程池被占满:如果你的手动线程太多,或者邮件发送耗时太长,导致Tomcat的请求线程池被耗尽,新的列表查询请求只能排队等待。
  4. 邮件发送逻辑本身是同步阻塞的:比如你用的是AWS SES的同步客户端,手动线程里调用同步方法,但如果线程没正确执行,还是会阻塞。

三、额外注意事项

  • 如果你必须在同一个类里调用异步方法(不推荐),不要直接调用,要通过Spring的ApplicationContext获取代理对象,或者自己注入自己(比如@Autowired private SesEmailService self;)。
  • 用AWS SES的话,优先用AmazonSesAsyncClient而不是同步客户端,这样能利用AWS SDK本身的异步能力,进一步提升性能。
  • 一定要给异步方法加异常处理,避免异步线程抛出异常导致线程池故障。

内容的提问来源于stack exchange,提问作者Davi Resio

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 08:09:16