티스토리 뷰
사용법
초기설정
@SpringBootApplication
@EnableAsync
public class SpringplaygroundApplication {
public static void main(String[] args) {
SpringApplication.run(SpringplaygroundApplication.class, args);
}
}
첫 번째로 @EnableAsync을 설정해야 한다. 그래야 비동기로 동작하는 데 필요한 Bean 들이 등록이 된다.
돌리고자 하는 클래스 또는 메소드
@Slf4j
@Service
@Async
public class AsyncService {
public void myAsyncCall() throws InterruptedException {
Thread.sleep(1000L);
log.info("async call = {}", LocalDateTime.now());
}
}
필자는 클래스에 설정하긴 했지만 메소드로 설정해도 상관이 없다.
@Slf4j
@Service
@RequiredArgsConstructor
public class MyAsyncService {
private final AsyncService asyncService;
public void call() throws InterruptedException {
log.info("start time = {}", LocalDateTime.now());
asyncCall();
log.info("end time = {}", LocalDateTime.now());
}
public void asyncCall() throws InterruptedException {
asyncService.myAsyncCall();
}
}
실제 해당 Async 클래스를 사용하는 서비스를 호출했을 때 다음과 같은 로그가 남는다.
2022-10-03 19:28:10.392 INFO 16905 start time = 2022-10-03T19:28:10.392891
2022-10-03 19:28:10.395 INFO 16905 end time = 2022-10-03T19:28:10.395125
2022-10-03 19:28:11.400 INFO 16905 async call = 2022-10-03T19:28:11.400748
서비스에 따라 ThreadPoolTaskExecutor 설정을 다르게 하고 싶을 수 있다.
그랬을 때는 다음과 같이 구성을 해주면 된다.
@Configuration
public class ThreadConfiguration {
@Bean(name = "first")
public ThreadPoolTaskExecutor asyncFirstExecutor() {
final ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(2);
executor.setMaxPoolSize(4);
executor.setThreadNamePrefix("first");
executor.initialize();
return executor;
}
@Bean(name = "second")
public ThreadPoolTaskExecutor asyncSecondExecutor() {
final ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(2);
executor.setMaxPoolSize(4);
executor.setThreadNamePrefix("second");
executor.initialize();
return executor;
}
}
ThreadPoolTaskExecutor 설정 값은 이와 같이 구성할 수 있으며 추가적인 API는 검색을 통해 찾아보시면 될 것 같다.
우선 해당 Bean이름을 편의를 위해 이름을 설정했다.
그리고 해당 이름을 @Async("first") 이와 같이 구성해주면 그에 대한 설정을 쓸 수가 있다.
@Slf4j
@Service
@Async("first")
public class AsyncService {
public void myAsyncCall() throws InterruptedException {
Thread.sleep(1000L);
log.info("async call = {}", LocalDateTime.now());
}
}
2022-10-03 19:28:31.543 INFO 16905 --- [nio-8080-exec-4] c.p.s.async.MyAsyncService : start time = 2022-10-03T19:28:31.543265
2022-10-03 19:28:31.543 INFO 16905 --- [nio-8080-exec-4] c.p.s.async.MyAsyncService : end time = 2022-10-03T19:28:31.543708
2022-10-03 19:28:32.551 INFO 16905 --- [ second2] c.p.s.async.SecondAsyncService : async call2 = 2022-10-03T19:28:32.551425
2022-10-03 19:28:32.551 INFO 16905 --- [ first2] c.p.springplayground.async.AsyncService : async call = 2022-10-03T19:28:32.551425
그랬을 때 스레드 이름이 second 와 first 각각 다르게 찍히는 것을 볼 수가 있다.
돌아가는 과정
@EnableAsync
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({AsyncConfigurationSelector.class})
public @interface EnableAsync {
Class<? extends Annotation> annotation() default Annotation.class;
boolean proxyTargetClass() default false;
AdviceMode mode() default AdviceMode.PROXY;
int order() default Integer.MAX_VALUE;
}
여기서 @Import를 통해 AsyncConfigurationSelector.class 빈 정보들을 가져온다.
AsyncConfigurationSelector
public class AsyncConfigurationSelector extends AdviceModeImportSelector<EnableAsync> {
private static final String ASYNC_EXECUTION_ASPECT_CONFIGURATION_CLASS_NAME = "org.springframework.scheduling.aspectj.AspectJAsyncConfiguration";
public AsyncConfigurationSelector() {
}
@Nullable
public String[] selectImports(AdviceMode adviceMode) {
switch (adviceMode) {
case PROXY:
return new String[]{ProxyAsyncConfiguration.class.getName()};
case ASPECTJ:
return new String[]{"org.springframework.scheduling.aspectj.AspectJAsyncConfiguration"};
default:
return null;
}
}
}
여기서 ProxyAsyncConfiguration.class 정보를 보도록 하자
ProxyAsyncConfiguration
@Configuration(
proxyBeanMethods = false
)
@Role(2)
public class ProxyAsyncConfiguration extends AbstractAsyncConfiguration {
public ProxyAsyncConfiguration() {
}
@Bean(
name = {"org.springframework.context.annotation.internalAsyncAnnotationProcessor"}
)
@Role(2)
public AsyncAnnotationBeanPostProcessor asyncAdvisor() {
Assert.notNull(this.enableAsync, "@EnableAsync annotation metadata was not injected");
AsyncAnnotationBeanPostProcessor bpp = new AsyncAnnotationBeanPostProcessor();
bpp.configure(this.executor, this.exceptionHandler);
Class<? extends Annotation> customAsyncAnnotation = this.enableAsync.getClass("annotation");
if (customAsyncAnnotation != AnnotationUtils.getDefaultValue(EnableAsync.class, "annotation")) {
bpp.setAsyncAnnotationType(customAsyncAnnotation);
}
bpp.setProxyTargetClass(this.enableAsync.getBoolean("proxyTargetClass"));
bpp.setOrder((Integer)this.enableAsync.getNumber("order"));
return bpp;
}
}
해당 클래스를 통해 @EnableAsync 설정한 정보들을 이용한다.
AsyncAnnotationBeanPostProcessor
위 ProxyAsyncConfiguration에서 볼 수 있다시피 AsyncAnnotationBeanPostProcessor를 통해 Advice로 동작하게 된다.
protected Advice buildAdvice(@Nullable Supplier<Executor> executor, @Nullable Supplier<AsyncUncaughtExceptionHandler> exceptionHandler) {
AnnotationAsyncExecutionInterceptor interceptor = new AnnotationAsyncExecutionInterceptor((Executor)null);
interceptor.configure(executor, exceptionHandler);
return interceptor;
}
interceptor를 등록하는 과정이며 자세한 것은 검색해보시길 바란다.
AsyncExecutionAspectSupport
protected Object doSubmit(Callable<Object> task, AsyncTaskExecutor executor, Class<?> returnType) {
if (CompletableFuture.class.isAssignableFrom(returnType)) {
return executor.submitCompletable(task);
}
else if (org.springframework.util.concurrent.ListenableFuture.class.isAssignableFrom(returnType)) {
return ((org.springframework.core.task.AsyncListenableTaskExecutor) executor).submitListenable(task);
}
else if (Future.class.isAssignableFrom(returnType)) {
return executor.submit(task);
}
else if (void.class == returnType) {
executor.submit(task);
return null;
}
else {
throw new IllegalArgumentException(
"Invalid return type for async method (only Future and void supported): " + returnType);
}
}
을 통해 실제 작업을 처리하게 된다.
언제 사용하면 좋을까?
주로 문자 메세지, 이메일 등을 처리하기 위해 많이 사용될 것이다.
그 뿐만 아니라 클라이언트에게 빠른 결과를 보여주고 해당 처리는 비동기로 처리함으로써 유저들에게 더 좋은 서비스를 경험해줄 때 쓰일 수 있다.
'JVM > Spring' 카테고리의 다른 글
[Java] record 이해해보기 (0) | 2022.10.06 |
---|---|
[Spring] @EventListener (0) | 2022.10.06 |
[Spring] @Scheduled, @EnableScheduling (0) | 2022.10.01 |
[Spring] Scope (Prototype & Singleton) (2) | 2022.09.13 |
[Hikari CP] 光 살펴보기 - 4 (0) | 2022.04.29 |
- Total
- Today
- Yesterday
- 백준
- ubuntu
- 카카오
- 알고리즘
- django
- headers
- BFS
- Celery
- Pattern
- 2021 KAKAO BLIND RECRUITMENT
- docker-compose
- dockerignore
- Command Line
- Java
- Spring
- PostgreSQL
- 파이썬
- thread
- 면접
- postgres
- Collections
- setattr
- Python
- 자바
- DRF
- docker
- Linux
- 프로그래머스
- 그래프
- env
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |