본문 바로가기
Spring

Spring boot - @Async를 통한 메서드 비동기 실행 및 주의사항

by khds 2024. 8. 29.

 

 

이전 글(https://khdscor.tistory.com/131)에서 Spring boot에서 gmail smtp 서버를 통해 메일 전송 기능을 구현하였는데, 응답 시간이 오래 걸리는 것을 확인하였다.

대략 4초가 걸렸는데, 사용자가 응답을 받는 데까지 기다리는 시간이 오래 걸리기 때문에 좋지 못한 상황이다. 

그런데, 대부분의 사이트에서 인증 메일을 보낼 때는 대기 없이 바로 인증 메일을 전송했다는 응답을 받는다. 어떻게 이럴 수 있나 찾아보다가 @Async 어노테이션을 통해 특정 메서드를 비동기 방식으로 진행할 수 있다는 것을 알게 되었다. 

 

@Async 어노테이션이 추가된 메서드는  별도의 스레드에서 실행되므로, 사용자는 해당 메서드가 종료되기 전에 응답을 받을 수 있고, 비동기로 동작하는 메서드는 뒤늦게 종료될 수 있는 것이다.)

아래는 반환 타입이 Void 인 메서드에 @Async 어노테이션을 추가하였을 시, 해당 메서드를 주입받은 Controller가 요청을 처리하는 과정을 보여준다. 

 

 

아래는 메일 전송 기능에 @Async 어노테이션을 적용하기 전과 후의 속도 차이이다.

 

 

후 

 

확연한 차이를 확인할 수 있다. 그렇다면 @Async를 어떻게 적용할 수있을까?

우선 메인 클래스에 @EnableAsync를 달아준다.(spring-boot-starter에 포함되어 있기 때문에 추가적인 의존성 추가는 없다.)

@EnableAsync
@SpringBootApplication
public class TestApplication {

    public static void main(String[] args) {
        SpringApplication.run(TestApplication.class, args);
    }
}

 

이후 비동기 방식으로 동작하길 원하는 메서드에  @Async를 붙이면 된다. 아래는 실제로 메일 전송 메서드에 적용한 코드이다. 

@Service
@RequiredArgsConstructor
public class TestMailService {

    private final JavaMailSender javaMailSender;
    private final SpringTemplateEngine templateEngine;

    @Async
    @Transactional
    public void test(){
        Map<String, String> emailContent = new HashMap<>();
        emailContent.put("test", "하하하하");
        emailContent.put("name", "호호호호");
        emailContent.put("code", "히히히히");
        List<String> images = new ArrayList<>();
        images.add("static/images/main-logo.jpg");
        List<String> emailList = new ArrayList<>();
        emailList.add("khdscor@gmail.com");
        sendMultiEmailWithImages(emailList, emailContent, images);
    }

    private void sendMultiEmailWithImages(
        List<String> receivers,
        Map<String, String> emailContent, List<String> imagePaths
    ) {
        try {
            // 이메일 전송을 위한 MimeMessageHelper 객체 생성
            MimeMessage mimeMessage = javaMailSender.createMimeMessage();
            MimeMessageHelper msgHelper = new MimeMessageHelper(mimeMessage, true, "UTF-8");

            // ... 중략
            
            // 이메일 전송
            javaMailSender.send(msgHelper.getMimeMessage());
        } catch (MessagingException e) {
            throw new RuntimeException("에러 발생");
        }
    }
}

 

@GetMapping("/testtest")
public String test(){
    return testMailService.test();
}

 

이렇게 비동기 방식으로 메서드를 호출할 수가 있다. 

만약 비동기 처리가 제대로 동작하지 않는다면,  아래의 주의사항을 확인해 보길 바란다.

@Async를 사용할 시 주의사항들이 있는데, 이를 지키지 않으면 비동기 처리가 제대로 동작하지 않을 것이다.


1. @EnableAsync 어노테이션이 애플리케이션의 메인 클래스에 추가되었는지 확인한다. 이 어노테이션은 Spring Boot 애플리케이션에서 비동기 처리를 활성화한다.  

2. @Async 어노테이션이 정적(static) 메서드에 추가되었다면, 비동기 처리가 동작하지 않는다. @Async 어노테이션은 인스턴스 메서드에만 적용할 수 있다. 

3. @Async 어노테이션이 추가된 메서드는 public이어야 한다.

4. @Async 어노테이션이 추가된 메서드가 같은 클래스 내의 다른 메서드에 의해 호출되었다면, 비동기 처리가 동작하지 않는다. 이는 Spring의 프록시 기반 AOP 동작 방식 때문으로, 비동기 메서드를 별도의 빈으로 분리하거나, 자기 호출(self-invocation)을 피해야 한다.  

5. @Async 어노테이션이 붙은 메서드의 반환 타입은 Future 또는 void이어야 한다.