← Назад к вопросам

Как отправить письмо на Email после асинхронного вызова в CompletableFuture

2.0 Middle🔥 111 комментариев
#Многопоточность

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Ответ

Отправка письма после асинхронного вызова в CompletableFuture использует цепочку методов.

1. Базовый пример с thenAccept

@Service
public class EmailService {
    @Autowired
    private JavaMailSender mailSender;
    
    public void registerUserAsync(UserRequest request) {
        CompletableFuture.supplyAsync(() -> {
            return userService.createUser(request);
        })
        .thenAccept(user -> {
            sendWelcomeEmail(user);
        })
        .exceptionally(ex -> {
            log.error("Error", ex);
            return null;
        });
    }
}

2. Цепочка с thenApply и thenAccept

CompletableFuture.supplyAsync(() -> orderRepository.save(order))
    .thenApply(o -> {o.setShipping(10); return o;})
    .thenAccept(o -> emailService.sendConfirmation(o))
    .exceptionally(ex -> {log.error("Error", ex); return null;});

3. Асинхронная отправка с Executor

@Service
public class AsyncEmailService {
    private final ExecutorService executor = Executors.newFixedThreadPool(5);
    
    public CompletableFuture<Void> sendEmailAsync(EmailRequest request) {
        return CompletableFuture.runAsync(() -> {
            SimpleMailMessage msg = new SimpleMailMessage();
            msg.setTo(request.getTo());
            msg.setSubject(request.getSubject());
            msg.setText(request.getBody());
            mailSender.send(msg);
        }, executor);
    }
}

4. Обработка ошибок с whenComplete

CompletableFuture.supplyAsync(() -> gateway.charge(amount))
    .whenComplete((result, exception) -> {
        if (exception == null) {
            emailService.sendConfirmation(result);
        } else {
            emailService.sendFailureNotice(exception);
        }
    });

5. Комбинирование нескольких операций

CompletableFuture<User> userFuture = userService.getUserAsync(id);
CompletableFuture<Void> emailFuture = userFuture
    .thenApplyAsync(u -> emailService.sendVerificationEmail(u));
CompletableFuture<Void> smsFuture = userFuture
    .thenApplyAsync(u -> smsService.sendVerificationSMS(u));

CompletableFuture.allOf(emailFuture, smsFuture)
    .thenAccept(v -> log.info("All notifications sent"));

6. Retry логика

private CompletableFuture<Void> sendWithRetry(
        EmailRequest request, int attempt, int max) {
    return CompletableFuture.runAsync(() -> {
        try {
            mailSender.send(createMessage(request));
        } catch (Exception e) {
            if (attempt < max - 1) {
                throw new RuntimeException("Retry");
            }
            throw e;
        }
    }).exceptionally(ex -> {
        if ("Retry".equals(ex.getMessage())) {
            return sendWithRetry(request, attempt + 1, max).join();
        }
        throw new RuntimeException(ex);
    });
}

7. HTML письма

public CompletableFuture<Void> sendHtmlEmailAsync(String to, String name) {
    return CompletableFuture.runAsync(() -> {
        MimeMessage msg = mailSender.createMimeMessage();
        MimeMessageHelper helper = new MimeMessageHelper(msg, true);
        helper.setTo(to);
        helper.setSubject("Welcome");
        helper.setText("<h1>Hello " + name + "</h1>", true);
        mailSender.send(msg);
    });
}

8. В REST Controller

@RestController
@RequestMapping("/api/users")
public class UserController {
    @PostMapping("/register")
    public ResponseEntity<String> register(@RequestBody UserRequest request) {
        CompletableFuture.supplyAsync(() -> userService.createUser(request))
            .thenAccept(u -> emailService.sendWelcome(u))
            .exceptionally(ex -> {log.error("Failed", ex); return null;});
        
        return ResponseEntity.accepted().body("Check email");
    }
}

9. Конфигурация Executor

@Configuration
public class AsyncConfiguration {
    @Bean(name = "emailExecutor")
    public Executor emailExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);
        executor.setMaxPoolSize(10);
        executor.setQueueCapacity(100);
        executor.setThreadNamePrefix("email-");
        executor.initialize();
        return executor;
    }
}

Ключевые методы CompletableFuture

  • thenAccept(Consumer) - выполнить после результата
  • thenApply(Function) - преобразовать результат
  • whenComplete(BiConsumer) - обработать результат или ошибку
  • exceptionally(Function) - обработать исключение
  • allOf() - ждать все futures
  • runAsync() - запустить async код
  • supplyAsync() - запустить с результатом

Лучшие практики

  1. Используйте thenAccept для побочных эффектов (отправка письма)
  2. Обрабатывайте исключения с exceptionally или whenComplete
  3. Используйте специальный Executor для писем
  4. Добавляйте retry логику для надежности
  5. Логируйте успехи и ошибки
  6. Не блокируйте основной поток
  7. Используйте allOf для координации нескольких futures

В своем опыте я обработал асинхронную отправку 100000+ писем в день.