스프링에서 @Async로 비동기처리하기 @Async in Spring

2020. 9. 3. 18:45 Spring Framework/Spring boot

스프링에서 @Async로 비동기처리하기 @Async in Spring

[원문: http://www.baeldung.com/spring-async]


1. 개요 Overview

이 글에서 스프링의 비동기 실행 지원asynchronous execution support과 @Async annotation에 대해 살펴볼 것이다. 간단히 설명하면, @Async 어노테이션을 빈bean에 넣으면 별도의 쓰레드에서 실행되는 것이다. 이를테면 호출자는 호출된 메소드가 완료될 때까지 기다릴 필요가 없다.



2. Async 기능 켜기 Enable Async Support

자바 설정Java configuration으로 비동기 처리enabling asynchronous processing를 쓰려면 간단히 설정 클래스에 @EnableAsync를 추가해주기만 하면 된다:

1
2
3
@Configuration
@EnableAsync
public class SpringAsyncConfig { ... }

위의 어노테이션이면 충분하지만, 당신이 필요로 하는 몇가지 옵션 또한 설정해 줄 수 있다:

  • annotation – 기본값, @EnableAsync은 스프링의 Async 어노테이션을 감지하며 EJB 3.1javax.ejb.Asynchronous; 이 옵션으로 사용자 정의된 다른 어노테이션 또한 감지할 수 있다.
  • mode – 사용해야할 advice의 타입을 가르킨다 - JDK proxy 기반 또는 AspectJ weaving.
  • proxyTargetClass – 사용해야할 proxy의 타입을 가르킨다 - CGLIB 또는 JDK; 이 속성값은 오직 mode가 AdviceMode.PROXY 로 설정되어 있을때만 유효하다.
  • order – sets the order in which AsyncAnnotationBeanPostProcessor 가 적용해야할 순서를 설정한다; 단지 모든 현존의 프록시를 고려하기 위해 기본값으로 마지막부터 실행된다.

비동기 처리는 task 네임스페이스를 사용하여 XML 설정을 통해 사용할 수도 있다:

1
2
<task:executor id="myexecutor" pool-size="5"  />
<task:annotation-driven executor="myexecutor"/>

3. @Async 어노테이션 The @Async Annotation

먼저 규칙을 살펴보자 – @Async 는 두가지 제약사항이 있다:

  • 1. public 메소드에만 적용해야한다
  • 2. 셀프 호출self invocation – 같은 클래스안에서 async 메소드를 호출 – 은 작동하지않음

이 이유는 간단한데 메소드가 public이어야 프록시가 될수 있기 때문이고 셀프호출은 프록시를 우회하고 해당 메소드를 직접 호출하기때문에 작동하지않는 것이다.


3.1. 리턴타입이 없는 메소드 Methods with void Return Type

다음과 같이 간단한 설정으로 리턴타입이 void인 메소드가 비동기로 작동한다:

1
2
3
4
5
@Async
public void asyncMethodWithVoidReturnType() {
    System.out.println("Execute method asynchronously. " + Thread.currentThread().getName());
}

3.2. 리턴타입이 있는 메소드 Methods with Return Type

Future 객체에 실제 리턴값을 넣음으로서 @Async 는 리턴타입이 있는 메소드에 적용할 수 있다.can also be applied to a method with return type:

1
2
3
4
5
6
7
8
9
10
11


@Async
public Future<String> asyncMethodWithReturnType() {
    System.out.println("Execute method asynchronously - " + Thread.currentThread().getName());
    try {
        Thread.sleep(5000);
        return new AsyncResult<String>("hello world !!!!");
    } catch (InterruptedException e) {
        //
    }
    return null;
}

스프링은 또한 Future를 구현한 AsyncResult 클래스를 제공하며 이는 비동기 메소드 실행의 결과를 가져오는데 사용한다.

이제 위의 메소드를 호출하여 Future 객체를 사용해 비동기 처리의 결과값을 가져와보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void testAsyncAnnotationForMethodsWithReturnType()
    throws InterruptedException, ExecutionException {
    System.out.println("Invoking an asynchronous method. "
      + Thread.currentThread().getName());
    Future<String> future = asyncAnnotationExample.asyncMethodWithReturnType();
 
    while (true) {
        if (future.isDone()) {
            System.out.println("Result from asynchronous process - " + future.get());
            break;
        }
        System.out.println("Continue doing something else. ");
        Thread.sleep(1000);
    }
}

4. 실행자 The Executor

스프링은 기본값으로 SimpleAsyncTaskExecutor 를 사용하여 실제 메소드들을 비동기 실행한다. 기본 설정은 두가지 레벨로 오버라이드할 수 있다 - 어플리케이션 레벨 또는 개인 메소드 레벨.


4.1. 메소드 레벨로 실행자 오버라이드하기 Override the Executor at the Method Level

설정 클래스에서 필요한 실행자를 선언해주어야한다:

1
2
3
4
5
6
7
8
9
@Configuration
@EnableAsync
public class SpringAsyncConfig {
     
    @Bean(name = "threadPoolTaskExecutor")
    public Executor threadPoolTaskExecutor() {
        return new ThreadPoolTaskExecutor();
    }
}

그 후 실행자 이름을 @Async에서 속성값으로 제공해주어야 한다:

1
2
3
4
5
@Async("threadPoolTaskExecutor")
public void asyncMethodWithConfiguredExecutor() {
    System.out.println("Execute method with configured executor - "  + Thread.currentThread().getName());
}

4.2. 어플리케이션 레벨로 실행자 오버라이드하기 Override the Executor at the Application Level

이 경우 설정 클래스는AsyncConfigurer 인터페이스를 구현해주어야한다 - 이는 getAsyncExecutor() 메소드를 구현해야한다는 의미이다. 여기에 우리는 전체 어플리케이션을 위한 실행자를 리턴할 것이다 - 이는 이제 @Async로 어노테이션된 메소드를 실행하는 기본 실행자가 된다.

1
2
3
4
5
6
7
8
9
10
@Configuration
@EnableAsync
public class SpringAsyncConfig implements AsyncConfigurer {
     
    @Override
    public Executor getAsyncExecutor() {
        return new ThreadPoolTaskExecutor();
    }
     
}

5. 예외 처리하기 Exception Handling

메소드의 리턴타입이 Future일 경우 예외처리는 쉽다 - Future.get() 메소드가 예외를 발생한다.

하지만 리턴타입이 void일 때, 예외는 호출 스레드에 전달되지 않을 것이다. 따라서 우리는 예외 처리를 위한 추가 설정이 필요하다.

우리는 AsyncUncaughtExceptionHandler 인터페이스를 구현함으로서 커스텀 비동기 예외처리자를 만들것이다. handleUncaughtException() 메소드는 잡히지않은uncaught 비동기 예외가 발생할때 호출된다:

1
2
3
4
5
6
7
8
9
10
11
12
public class CustomAsyncExceptionHandler  implements AsyncUncaughtExceptionHandler {
 
    @Override
    public void handleUncaughtException(Throwable throwable, Method method, Object... obj) {
        System.out.println("Exception message - " + throwable.getMessage());
        System.out.println("Method name - " + method.getName());
        for (Object param : obj) {
            System.out.println("Parameter value - " + param);
        }
    }
     
}

전 섹션에서 우리는 설정 클래스에 의해 구현된 AsyncConfigurer 인터페이스를 보았다. 그 일부로서 우리의 커스텀 비동기 예외처리자를 리턴하는 getAsyncUncaughtExceptionHandler() 메소드 또한 오버라이드해주어야한다:

1
2
3
4
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
    return new CustomAsyncExceptionHandler();
}

6. 결 론 Conclusion

이 튜토리얼에서 우리는 스프링에서 비동기 코드를 동작하는 법을 둘러보았다. 매우 기본적인 설정과 어노테이션으로 작동해 보았을 뿐 아니라, 우리가 설정한 실행자, 또는 예외처리 전략과 같은 더 고급진 설정법 또한 둘러보았다.



출처: https://springboot.tistory.com/38?category=620230 [스프링부트는 사랑입니다]