해당 글은 https://www.baeldung.com/spring-async 를 번역하여 제작하였습니다.
1. Overview
이 튜토리얼에서는 스프링이 비동기 실행을 어떻게 지원하는지를 @Async 어노테이션과 함께 알아볼 것이다. 간단히 말해, 빈에 @Async 어노테이션을 달기만 하면, 해당 어노테이션이 다른 스레드에서 실행될 수 있도록 해줄 것이다. 즉, 함수를 호출하는 상황에서 호출하는 쪽이 호출되는 쪽을 기다릴 필요가 없어진다.
한가지 스프링의 흥미로운 점은 프레임워크 단에서의 이벤트 지원이 필요시 비동기 처리 또한 지원한다는 것이다.
이후 읽어볼 만한 글:
Spring Events |
Spring Security Context
|
Servlet 3 Async Support with Spring MVC and Spring Security |
스프링 이벤트의 기본
|
@Async 어노테이션 사용 시
|
Spring MVC에서 비동기 응답 시
|
2. 비동기 지원 허용하기
비동기 처리를 가능하게 하도록 자바 설정부터 시작하자.
우리는 설정 클래스에 @EnableSync를 붙여서 비동기 지원을 허용시킬 것이다.
@Configuration
@EnableAsync
public class SpringAsyncConfig { ... }
이 어노테이션만으로도 충분하지만, 추가로 고려해볼만한 몇가지 간단한 설정 옵션이 있다.
- annotation — 디폴트로는 @EnableAsync 어노테이션이 스프링의 @Asnyc 어노테이션과 EJB 3.1 javax.ejb.Asynchronous 를 탐지한다. 그 외의 사용자가 정의한 어노테이션 타입과 같은 것들을 탐지하고자 한다면, 해당 옵션을 사용한다.
- mode 는 사용될 advice의 타입을 가리킨다. — AspectJ weaving을 바탕으로 한 JDK 프록시
- proxyTargetClass 는CGLIB 나 JDK로 사용될 proxy를 가리킨다.
해당 속성은 mode가 AdviceMode.PROXY로 세팅될 경우에만 유효하다. - order 는AsyncAnnotationBeanPostProcessor 가 적용될 순서를 설정한다.
디폴트로는 존재하는 모든 프록시를 감안하여 가장 마지막으로 돈다.
우리는 XML 설정에서 task namespace를 사용하여 비동기 처리를 가능하게 할 수 있다.
<task:executor id="myexecutor" pool-size="5" />
<task:annotation-driven executor="myexecutor"/>
3. @Async 어노테이션
먼저, 규칙부터 살펴보자. @Async에는 두가지 한계점이 있는데,
- public 메소드에만 적용되어야 한다.
: proxy로 만들어져야 하기 때문에 - 같은 클래스의 비동기 메소드를 호출하는 경우 (self-invocation) 하면 동작하지 않는다.
: proxy를 거쳐 근거가 되는 메소드를 직접 호출하므로
3.1 Void 반환값을 가지는 메소드
@Async
public void asyncMethodWithVoidReturnType() {
System.out.println("Execute method asynchronously. "
+ Thread.currentThread().getName());
}
3.2 반환값을 가지는 메소드
우리는 메소드의 실제 반환값을 Future로 감싸서 @Async 를 해당 메소드에 적용시킬 수 있다.
@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;
}
Spring은 Future를 구현한 AsyncResult 클래스를 제공하고 있다. 우리는 이걸 사용해 비동기 메소드 실행 결과를 추적할 수 있다.
그럼 이젠 위 메소드를 호출해보고 Future 객체를 사용해 비동기 처리 결과를 받아보자.
public void testAsyncAnnotationForMethodsWithReturnType()
throws InterruptedException, ExecutionException {
System.out.println("Invoking an asynchronous method. "
+ Thread.currentThread().getName());
Future<String> future = asyncAnnotationExample.asyncMethodWithReturnType();
// Future는 AsyncResult의 부모클래스로 다형성이 적용된 코드로 이해할 수 있다.
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. Executor
스프링은 디폴트로 SimpleAsyncTaskExecutor를 사용해 이 메소드들을 실제로 비동기로 돌린다.
하지만 우리는 이 디폴트를 두가지 레벨에서 재정의(override)할 수 있다.
: 어플리케이션 레벨에서 정의하거나 개별 메소드 레벨에서 정의하는 방법이 있다.
4.1 메소드 레벨에서 Executor를 재정의(Override)
우리는 설정(configuration) 클래스에서 필요한 executor를 정의해야한다.
@Configuration
@EnableAsync
public class SpringAsyncConfig {
@Bean(name = "threadPoolTaskExecutor")
public Executor threadPoolTaskExecutor() {
return new ThreadPoolTaskExecutor();
}
}
그리고나서, 우리는 @Async의 인자로 executor의 이름을 넣어주어야 한다.
@Async("threadPoolTaskExecutor")
public void asyncMethodWithConfiguredExecutor() {
System.out.println("Execute method with configured executor - "
+ Thread.currentThread().getName());
}
4.2 어플리케이션 레벨에서 Executor를 재정의 (Override)
설정 클래스는 AsyncConfigurer 인터페이스를 구현해야 하는데, 따라서 getAsyncExecutor() 메소드를 구현해야 한다. 여기서 우리는 어플리케이션 전체를 위한 executor를 반환할 것이다. 이제 이 executor가 @Async 어노테이션이 달린 모든 메소드에 대한 디폴트 executor로 작동할 것이다.
@Configuration
@EnableAsync
public class SpringAsyncConfig implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
return new ThreadPoolTaskExecutor();
}
}
5. 예외 처리
Future 가 메소드의 반환타입일 경우 예외처리는 쉽다. Future.get() 메소드가 예외를 던져줄 것이다.
하지만 반환 타입이 void 라면, 예외가 호출하는 스레드 쪽으로 전파되지 않을 것이다.
따라서, 우리는 예외 처리를 위해 추가적인 설정을 할 필요가 있다.
custom 하게 작동하는 비동기 예외 처리기를 만들기 위해서는 AsyncUncaughtExceptionHandler 인터페이스를 구현해야 한다. handleUncaughtException() 메소드는 잡히지 않은(catch 되지 않은) 비동기 예외가 있으면 호출된다.
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 인터페이스를 살펴보았다. AsyncConfigurer 의 구현체 코드에, 우리만의 custom한 비동기 예외 처리기를 반환하는 getAsyncUncaughtExceptionHandler() 메소드를 override 하는 부분을 추가해야 한다.
추가할 부분 :
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new CustomAsyncExceptionHandler();
}
최종 SpringAsyncConfig code:
package com.baeldung.async.config;
import java.util.concurrent.Executor;
import com.baeldung.async.CustomAsyncExceptionHandler;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
@Configuration
@EnableAsync()
@ComponentScan("com.baeldung.async")
public class SpringAsyncConfig implements AsyncConfigurer {
@Bean(name = "threadPoolTaskExecutor")
public Executor threadPoolTaskExecutor() {
return new ThreadPoolTaskExecutor();
}
@Override
public Executor getAsyncExecutor() {
return new SimpleAsyncTaskExecutor();
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new CustomAsyncExceptionHandler();
}
}
6. 결론
이 글에서 우리는 Spring에서 비동기 코드를 돌려보았다.
우리는 매우 기본적인 설정부터 시작해서 어노테이션으로 동작할 수 있게 하였다.
하지만 거기서 그치지 않고 좀 더 나아가 custom한 executor와 예외처리 전략과 같은 심화 설정 또한 알아보았다.
매번 그래왔듯, 모든 코드는 GitHub에 있다.
'번역자료 (구글번역은 못 참지) > Baeldung' 카테고리의 다른 글
java.util.concurrent.Future 가이드 (0) | 2022.05.23 |
---|---|
스프링 이벤트 (0) | 2022.05.22 |
스프링 Task Scheduler 가이드 (0) | 2022.05.20 |