[Spring] Async란?
@Async 붙히고(아무 설정 안함) debug 모드로 실행을 해보면 다음과 같이 실행된다.
그런 다음, AsyncExecutionInterceptor
를 통해 실행되는데 실행되는 코드를 보면 다음과 같다.
@Override
@Nullable
public Object invoke(final MethodInvocation invocation) throws Throwable {
Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
Method specificMethod = ClassUtils.getMostSpecificMethod(invocation.getMethod(), targetClass);
final Method userDeclaredMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
AsyncTaskExecutor executor = determineAsyncExecutor(userDeclaredMethod);
if (executor == null) {
throw new IllegalStateException(
"No executor specified and no default executor set on AsyncExecutionInterceptor either");
}
Callable<Object> task = () -> {
try {
Object result = invocation.proceed();
if (result instanceof Future) {
return ((Future<?>) result).get();
}
}
catch (ExecutionException ex) {
handleError(ex.getCause(), userDeclaredMethod, invocation.getArguments());
}
catch (Throwable ex) {
handleError(ex, userDeclaredMethod, invocation.getArguments());
}
return null;
};
return doSubmit(task, executor, invocation.getMethod().getReturnType());
}
determinAsyncExecutor()
를 호출 하여 executor를 구한 뒤, task를 등록하고 doSubmit()
을 호출한다.
@Nullable
protected AsyncTaskExecutor determineAsyncExecutor(Method method) {
AsyncTaskExecutor executor = this.executors.get(method);
if (executor == null) {
Executor targetExecutor;
String qualifier = getExecutorQualifier(method);
if (StringUtils.hasLength(qualifier)) {
targetExecutor = findQualifiedExecutor(this.beanFactory, qualifier);
}
else {
targetExecutor = this.defaultExecutor.get();
}
if (targetExecutor == null) {
return null;
}
executor = (targetExecutor instanceof AsyncListenableTaskExecutor ?
(AsyncListenableTaskExecutor) targetExecutor : new TaskExecutorAdapter(targetExecutor));
this.executors.put(method, executor);
}
return executor;
}
처음 호출을 하게 되면, executors는 없어서 null이 되고, qualifier는 "" 문자열이 되어 defaultExecutor.get()
을 호출한다.
this.executors.put(method, executor)
를 호출하여 defaultExecutor를 저장하게 된다. 그 다음 부터는 등록되어있는 executor를 호출하고 return 된다.
참고로 applicationTaskExecutor를 따로 설정하지 않으면 default core size = 8이며, maxPoolSize = 2147483647이다.
doSubmit은 AsyncExecutionAspectSupport
를 통해 실행되며, 다음과 같다.
@Nullable
protected Object doSubmit(Callable<Object> task, AsyncTaskExecutor executor, Class<?> returnType) {
if (CompletableFuture.class.isAssignableFrom(returnType)) {
return CompletableFuture.supplyAsync(() -> {
try {
return task.call();
}
catch (Throwable ex) {
throw new CompletionException(ex);
}
}, executor);
}
else if (ListenableFuture.class.isAssignableFrom(returnType)) {
return ((AsyncListenableTaskExecutor) executor).submitListenable(task);
}
else if (Future.class.isAssignableFrom(returnType)) {
return executor.submit(task);
}
else {
executor.submit(task);
return null;
}
}
returnType에 따라 CompletableFuture, ListenableFuture, Future, Void에 맞춰 실행된다.
나는 returnType이 Void이기 때문에, else 구문을 타며 실행 후 return null
을 호출 한다.
CompletableFuture, ListenableFuture, Future에 대한 내용은 https://brunch.co.kr/@springboot/401 이 블로그에 자세하게 정리해 놓으셨다.