-
Spring Event 동작 원리 - 4Spring Framework 2023. 9. 16. 00:01728x90
이전 포스팅에 이어 EventListener에서 @Async는 어떻게 동작하는지 알아보고자 합니다.
Spring Boot @Async
@EnableASync, @Async 어노테이션을 활용하여 비동기 메서드로 동작을 수행할 수 있습니다.
@Async는 AOP에 의해 동작하며 최종적으로는 AsyncExecutionAspectSupport클래스의 doSubmit 메서드를 통해 이루어집니다.
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); } }
이때 CompletableFuture, ListenableFuture, Furture 등 리턴타입에 따라 달라집니다.
@Async를 활용했을 때 해당 클래스가 어떻게 호출되는지 디버깅을 통해 역추적해보면 다음과 같습니다.
- AsyncExecutionAspectSupport클래스의 duSubmit 메서드
- AsyncExecutionInterceptor클래스의 invoke 메서드
- ReflectiveMethodInvocation클래스의 proceed 메서드
- CglibAopProxy의 proceed 메서드
- ApplicationListenerMethodAdapter의 doInvoke 메서드
- ApplicationListenerMethodAdapter의 process 메서드
- 등등....
- 점점 익숙했던 클래스들이 나오기 시작합니다.
그러면 @Async를 활용하지 않았을 때는 어떻게 호출되는지 확인해 보겠습니다.
- ApplicationListenerMethodAdapter의 doInvoke 메서드까지만 호출되고 CglibAopProxy 클래스는 호출되지 않습니다.
다시 AsyncExecutionAspectSupport
비동기 메서드 실행을 위한 클래스로 메서드 단위로 실행을 수행합니다.
생성자를 통하여 기본 Executor를 설정할 수 있으며, 개별 메서드의 어노테이션 속성으로도 특정 Executor bean을 지정하여 실행할 수 있습니다.
public Object invoke(final MethodInvocation invocation) throws Throwable { ... 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<?> future) { return future.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()); }
determineAsyncExecutor 메서드에서 method의 한정자를 가져옵니다.
여기서 getExecutorQualifier메서드를 활용하여 Async 메서드에서 지정된 Executor의 이름을 통해 가져오고 없다면 기본 deafultExecutor를 사용합니다.
이후 Callable 타입의 task를 만들고 doSubmit에서는 실제 executor를 호출하여 비동기 처리를 수행합니다.
SimpleAsyncTaskExecutor
Thread의 task에 대해 비동기적 실행을 담당하는 TaskExecutor를 구현하는 클래스입니다.
주의할 점으로 이 구현은 스레드를 재사용하지 않습니다.
다수의 짧은 작업을 수행할 때는 TaskExecutor의 구현을 고려해야 합니다.
스레드를 재사용하지 않는다는 것은 매 작업마다 새로운 스레드를 만든다는 것을 의미합니다.
참고자료
https://brunch.co.kr/@springboot/401
https://blog.naver.com/gngh0101/222073112894
'Spring Framework' 카테고리의 다른 글
Spring Boot Graceful shutdown 동작과정 (0) 2023.11.03 Spring @Transactional의 rollback 동작과정 디버깅 (0) 2023.10.30 Spring Boot가 다중 요청을 처리하는 방법 (1) 2023.09.14 Spring Event 동작 원리 -3 (0) 2023.09.13 Spring Event 동작 원리 - 2 (0) 2023.09.12