@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 이 블로그에 자세하게 정리해 놓으셨다.

참고 자료