μΈμ¦ μ΄λ©μΌ μ μ‘ μ, κ°νΉ λ€νΈμν¬ μ€λ₯λ‘ μΈν΄ λ©μΌμ΄ μ μμ μΌλ‘ μ μ‘λμ§ μλ κ²½μ°κ° λ°μν©λλ€.
μ΄λ¬ν μν©μμ μ¬μ©μμκ² μ¦μ μ€λ₯λ₯Ό μ리λ λμ , μ¬μλ λ‘μ§μ ν΅ν΄ μ΄λ©μΌ μ μ‘ μ±κ³΅ κ°λ₯μ±μ λμ΄λ κ²μ΄ μ¬μ©μ κ²½νμ ν₯μνλ μ€μν μμλΌκ³ νλ¨νμ΅λλ€.
κ·Έλμ, μ¬μλ μΆκ° μ κ³ λ €ν κ²λ€μ λ€μκ³Ό κ°μ λͺ©μ°¨λ‘ μκ°ν©λλ€.
- μ¬μλ(@Retryable) 쑰건 μ€μ
- μ¬μλ μμΈμ μ°λ λ ν μμΈλ₯Ό ꡬλΆν΄μ μ²λ¦¬νκΈ°
- μ¬μλ λ° λ³΅κ΅¬κ° μ λμλμ§ ν μ€νΈνκΈ°
* λΉλκΈ°μμ μ¬μλ λ‘μ§ κ΅¬νμ μν΄ Baeldung(async-retry)λ₯Ό μ°Έκ³ νμ΅λλ€.
μ¬μλ(@Retryable) 쑰건 μ€μ
λ¨Όμ , μ΄λ©μΌ μ μ‘μ μ¬μλ 쑰건μ μ€μ νκΈ° μν΄ Spring Mail κ΄λ ¨ μμΈλ₯Ό μκ°ν©λλ€.
- MailAuthenticationException : μΈμ¦ μ€ν¨ μ λ°μνλ μμΈ
- MailException : λ©μΌ μ μ‘κ³Ό κ΄λ ¨λ λͺ¨λ μμΈμ κΈ°λ³Έ ν΄λμ€λ‘ ꡬ체μ μΈ μ€λ₯μ λ°λΌ μΈλΆ μμΈλ€μ΄ μ΄ ν΄λμ€λ₯Ό μμν©λλ€.
- MailParseException : μλͺ»λ λ©μμ§ μμ±μ΄ λ°κ²¬λ κ²½μ° λ°μνλ μμΈ
- MailPreparationException : μ¬μ©μ μ½λμμ λ©μΌ μ€λΉ μ€ μ€λ₯κ° λ°μνμ λ λμ Έμ§λ μμΈ
- MailSendException : λ©μΌ μ μ‘ μ€λ₯ λ°μ μ λμ Έμ§λ μμΈ
λ§μ½ μ΅μμ μμΈμΈ MailExceptionμ μ¬μλ 쑰건μΌλ‘ μ€μ νλ€λ©΄, μΈμ¦ μ€ν¨λ λ©μμ§ κ΅¬λ¬Έ μ€λ₯κ° λ°μν΄λ μ΄λ₯Ό μΈμνμ§ λͺ»νκ³ κ³μν΄μ μ¬μλλ₯Ό λ°λ³΅νκ² λ©λλ€. κ·Έλ¦¬κ³ , MailSendExceptionμ μ μΈν λ€λ₯Έ μμΈλ€μ μ€μ μ€λ₯λ λ©μΌ λ΄μ©κ³Ό κ΄λ ¨λ λ¬Έμ λ‘, μ¬μλκ° μλ μ€μ μμ μ΄λ λ©μΌ λ΄μ©μ κ³ μΉλ κ²μ΄ νμν©λλ€.
λ°λΌμ, λ€νΈμν¬ μ€λ₯λ‘ μΈν MailSendException λ°μ μμλ§ μ¬μλκ° μ΄λ£¨μ΄μ§λλ‘ μ νμ λμκ³ , μ΄ μμΈκ° λ°μν κ²½μ° 1μ΄μ λλ μ΄ ν μ΅λ 3ν μ¬μλνλλ‘ μ€μ νμ΅λλ€. (κΈ°λ³Έ μ¬μλ νμλ 3νμ λλ€)
μ¬μλ μμΈμ μ°λ λ ν μμΈλ₯Ό ꡬλΆν΄μ μ²λ¦¬νκΈ°
μ΄μ νκ²½μμλ λ‘κ·Έλ₯Ό ν΅ν΄ λ¬Έμ λ₯Ό λΉ λ₯΄κ² νμ ν μ μμ΄μΌ νλ€λ κ²μ΄ μ€μνλ€κ³ μκ°ν©λλ€.
κ·Έλμ, @Asyncμ @Retryableμ΄ μ μ©λ sendAsyncAuthenticationMail λ©μλμμ λ κ°μ§ μ νμ μμΈλ₯Ό ꡬλΆνμ¬ μ²λ¦¬νμ΅λλ€.
- μ¬μλκ° λͺ¨λ μ€ν¨ν΄μ λ°μν μμΈ
- λΉλκΈ° μ°λ λμμ λ°μν μμΈ
1. μ¬μλκ° λͺ¨λ μ€ν¨ν΄μ λ°μν μμΈ μ²λ¦¬νκΈ°
javaMailSender.send() λ©μλ νΈμΆ μ λ°μν μ μλ MailSendException μΈμλ, μ μ μΈμ¦ μ€ν¨ μ MailAuthenticationExceptionμ΄ λ°μν μ μμ΅λλ€.
μ΄λ, μ¬μλ μ€ν¨ μ 볡ꡬνλ @Recover λ©μλκ° MailSendExceptionλ§ μ²λ¦¬νλλ‘ μ€μ λμ΄ μλ€λ©΄, MailAuthenticationExceptionμ΄ λ°μν κ²½μ°, org.springframework.retry.ExhaustedRetryException: Cannot locate recovery method μλ¬κ° λ°μνλ©° μ€μ μμΈμΈ MailAuthenticationExceptionμ΄ λ‘κ·Έμ λλ¬λμ§ μμ κ΄λ ¨ μλ¬λ₯Ό ν΄κ²°νλλ° μκ°μ΄ λ μ€λ 걸리λ κ²½νμ νμ΅λλ€.
λ°λΌμ μ΅μμ μμΈμΈ MailExceptionμ @Recoverμ νλΌλ―Έν°λ‘ μ£Όμ νμ¬, λ€νμ±μ μ΄μ©ν΄ λͺ¨λ λ©μΌ κ΄λ ¨ μμΈλ€μ μ²λ¦¬νμ¬ μ¬μλκ° μ€ν¨ν μμΈμ λ‘κ·Έ λΉ λ₯΄κ² λ¨κΈ°μ΄μ λΉ λ₯΄κ² λμν μ μκ² νμ΅λλ€. κ·Έλ μ§λ§, μ¬μλ(@Retryable)λ MailSendExceptionμΈ λ€νΈμν¬ μ€λ₯κ° λ°μνμ λλ§, μ¬μλλ₯Ό νλλ‘ νμ΅λλ€.
2. λΉλκΈ° μ°λ λμμ λ°μν μμΈ
μ @Asyncλ©μλ(sendAsyncAuthenticationMail)λ λ°ν νμ μ΄ voidμ΄λ―λ‘, λΉλκΈ° μ€λ λμμ λ°μν μμΈκ° μμ μ€λ λλ‘ μ νλμ§ μμ΅λλ€.
λΉλκΈ° λ©μλμ λ°ν νμ μ CompletableFutureλ‘ μ€μ νλ©΄ μμ μ€λ λλ‘ μμΈλ₯Ό μ λ¬ν μ μμ§λ§, λ©μΌ μ μ‘ μ€ν¨ μμΈκ° μ€μνμ§ μμ voidλ‘ μ€μ νμ΅λλ€.
κ·Έλ μ§λ§, void λ°ννμ μΈ @Asyncμμ λ°μν μμΈλ₯Ό μ²λ¦¬νμ§ μμΌλ©΄ μμΈκ° μ νλμ§ μκΈ° λλ¬Έμ, μμΈ λ°μ μ μΉλͺ μ μ λλ€.
μ΄λ₯Ό λ°©μ§νκΈ° μν΄, λΉλκΈ° μ²λ¦¬λ₯Ό μν μ€λ λ ν μ€μ μμ AsyncUncaughtExceptionHandlerλ₯Ό μ€λ²λΌμ΄λνμ¬ λΉλκΈ° μ€λ λμμ λ°μν μμΈλ₯Ό λ‘κ·Έλ‘ μΆλ ₯νλλ‘ μ€μ νμ΅λλ€.(ν΄λΉ Config μ½λ)

μ¬μλ λ° λ³΅κ΅¬κ° μ μ μ© λ¬λμ§ ν μ€νΈνκΈ°
μ¬μλ λ° λ³΅κ΅¬κ° μλλλ‘ μλνλμ§ ν μ€νΈνμ¬ νμΈνμ΅λλ€. μ£Όμ ν μ€νΈ ν¬μΈνΈλ λ€μκ³Ό κ°μ΅λλ€.
- μ¬μλκ° μ΄ 3ν μλλλμ§: μ½λμ verify()μ times()λ‘ κ²μ¦
- μ¬μλ μ€ν¨ ν @Recoverλ‘ μ μ νλ¦μΌλ‘ μ νλλμ§: μ½λμ assertThatCode()λ‘ νμΈ
μΆκ°λ‘, μ²μμλ μ¬μλ νμ(3λ²) νμΈ ν μ€νΈμ @Recoverλ‘ μμΈκ° 볡ꡬλλμ§ νμΈνλ ν μ€νΈλ₯Ό κ°κ° μμ±νλλ°, μ€λ³΅λ λ‘μ§μ΄ λ§μ μ½λ κ°λ μ±μ΄ λ¨μ΄μ‘κ³ , λ ν μ€νΈκ° νλμ λ©μλλ₯Ό κ²μ¦νλμ§ λ€λ₯Έ μ¬λμκ²λ λͺ ννμ§ μμ μ μμμ΅λλ€. μ΄μ λ°λΌ DynamicTestλ₯Ό νμ©νμ¬ μλλ¦¬μ€ ν μ€νΈλ‘ μμ±νκ³ μ€λ³΅ λ‘μ§μ κ°μ νμ¬ κ°λ μ±μ λμμ΅λλ€.
μ 체 ν μ€νΈ μ½λμ λλ€.
@DisplayName("μ΄λ©μΌ μ μ‘ μ€ν¨ μ μ¬μλ λ° λ³΅κ΅¬ μλ리μ€")
@TestFactory
Collection<DynamicTest> sendAsyncAuthenticationMail() {
// given
String email = "test@naver.com";
String authenticationCode = "authCode";
# MockBeanμ μ¬μ©ν΄ λ©μΌ μμ²μ 보λλ€κ³ κ°μ
willThrow(MailSendException.class)
.given(javaMailSender)
.send(any(SimpleMailMessage.class));
// when then
return List.of(
dynamicTest(
"μ¬μλκ° λͺ¨λ μ€ν¨ν΄λ @Recoverμ μν΄ μμΈλ λ°μνμ§ μλλ€", () ->
assertThatCode(() ->
mailClient.sendAsyncAuthenticationMail(email, authenticationCode))
.doesNotThrowAnyException())
),
dynamicTest("μΈμ¦ μ΄λ©μΌ 보λ΄κΈ°μ μ€ν¨νλ©΄ μ΄ 3λ²μ μλλ₯Ό νλ€", () -> {
Thread.sleep(5000); # λ©μΈ μ€λ λκ° μ¬μλ νλ λΉλκΈ° μ°λ λλ³΄λ€ λ¨Όμ μ’
λ£λμ§ μκΈ° μν¨
verify(javaMailSender, times(3)).send(any(SimpleMailMessage.class));
})
);
}
λ§λ¬΄λ¦¬νλ©°
μ΅κ·Όμ μ΄μμμλ 무μμ΄ μ€μν κΉ? μ λν κ³ λ―Όμ΄ λ§μμ§λ©΄μ μμΈ μ²λ¦¬μ λ‘κ·Έ κ΄λ¦¬μ μ€μμ±μ μ€κ°νκ³ μμ΅λλ€. μ΄μ κ΄λ ¨λ μμκΉμ§ κ³ λ €νλ©° κ°λ°νλ€ λ³΄λ, λΉλκΈ° μ²λ¦¬λ λ©μΌ κ΄λ ¨ λ¬Έμλ₯Ό λ κΉμ΄ μ½κ³ μ΄ν΄ν μ μκ² λμ΄ κ°λ° μ΄ν΄λλ λμμ§λ μ₯μ μ λλΌκ³ μμ΅λλ€. μμΌλ‘ μ₯μ λ°μ μ μ μνκ² λμν μ μλ μ΄μ λ¨κ³μ λ°©λ²λ€μ λν΄ λ 곡λΆνλ € ν©λλ€. μ΄μ κ΄λ ¨ν΄ μΆμ²ν λ§ν λ νΌλ°μ€κ° μλ€λ©΄ λκΈλ‘ μλ €μ£Όμλ©΄ κ°μ¬νκ² μ΅λλ€. μ½μ΄ μ£Όμ μ κ°μ¬ν©λλ€!