출처 : https://jieun0113.tistory.com/73
Request를 보내고 Task를 처리하는도중 파일I/O같은 무거운 작업을 만나게되면 이를 처리하느라 뒤에 모든 할일이 지연이됩니다 즉 그만큼 Response도 늦게되는거죠 ( Sync )
만약 Thread를 추가로 생성해 파일I/O같은 무거운작업은 Worker Thread에서 진행하고 나머지 작업은 빠르게 처리한뒤 Response를 먼저 보내버리게되면 일은 처리가 끝나지 않았어도 Response를 보낼 수 있게되는거죠 ( ASync )
@Import(AsyncConfigurationSelector.class)
public @interface EnableAsync {
Class<? extends Annotation> annotation() default Annotation.class;
AdviceMode mode() default AdviceMode.PROXY;
boolean proxyTargetClass() default false;
}ㅖ
Default는 Proxy모드로 이는 Spring AOP에서 지원하는 방식이기에 Local에서 접근할때에는 Async가 적용되지 않는다 만약 ASPECTJ로 Mode를 바꾸면 이거는 weaving사용해 직접 컴파일이나 클래스시점에 바이트코드를 넣기때문에 보다 다양하게 AOP를 적용시키고 싶으면 ASPECTJ로 바꾸면 된다
proxyTargetClass는 Proxy Mode일때만 사용가능하며 true일시 interface(리플렉션), false일시 CGLIB방식의 AOP를 적용한다
annotation()을 통해 어떤 @Annotation을 사용할지도 지정할 수 있다
@Import(AsyncConfigurationSelector.class)를 통해 위에서 적용한 Mode에 따라 다르게 Import해온다
public String[] selectImports(AdviceMode adviceMode) {
switch (adviceMode) {
case PROXY:
return new String[] {ProxyAsyncConfiguration.class.getName()};
case ASPECTJ:
return new String[] {ASYNC_EXECUTION_ASPECT_CONFIGURATION_CLASS_NAME};
}
}
ASPECTJ : org.springframework.scheduling.aspectj.AspectJAsyncConfiguration 해당 클래스를 기준으로 Import
PROXY : ProxyAsyncConfiguration 기준으로 Import를 해온다
PROXY Mode에대해서만 좀더 알아보자
ProxyAsyncConfiguration.class
@Bean(name = TaskManagementConfigUtils.ASYNC_ANNOTATION_PROCESSOR_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)public AsyncAnnotationBeanPostProcessor asyncAdvisor() {
AsyncAnnotationBeanPostProcessor bpp = new AsyncAnnotationBeanPostProcessor();
...
if (customAsyncAnnotation != AnnotationUtils.getDefaultValue(EnableAsync.class, "annotation")) {
bpp.setAsyncAnnotationType(customAsyncAnnotation);
}
bpp.setProxyTargetClass(this.enableAsync.getBoolean("proxyTargetClass"));
...
return bpp;
}
위에서 설정한 Annotation Value대로 설정하는걸 볼 수 있다
이 중 AsyncAnnotationBeanPostProcessor가 실제 비동기 AOP를 적용시키는 Bean임을 알 수 있는데 이에대해서 자세히 알아보자
AsyncAnnotationBeanPostProcesor.class
@Override
public void setBeanFactory(BeanFactory beanFactory) {
...
AsyncAnnotationAdvisor advisor = new AsyncAnnotationAdvisor(this.executor, this.exceptionHandler);
if (this.asyncAnnotationType != null) {
advisor.setAsyncAnnotationType(this.asyncAnnotationType);
}
advisor.setBeanFactory(beanFactory);
this.advisor = advisor;
}
AsyncAnnotationAdvisor.class
public AsyncAnnotationAdvisor(
@Nullable Supplier<Executor> executor, @Nullable Supplier<AsyncUncaughtExceptionHandler> exceptionHandler) {
...
asyncAnnotationTypes.add(Async.class);
...
asyncAnnotationTypes.add((Class<? extends Annotation>)
ClassUtils.forName("javax.ejb.Asynchronous", AsyncAnnotationAdvisor.class.getClassLoader()));
this.advice = buildAdvice(executor, exceptionHandler);
this.pointcut = buildPointcut(asyncAnnotationTypes);
}
protected Advice buildAdvice(
@Nullable Supplier<Executor> executor, @Nullable Supplier<AsyncUncaughtExceptionHandler> exceptionHandler) {
AnnotationAsyncExecutionInterceptor interceptor = new AnnotationAsyncExecutionInterceptor(null);
interceptor.configure(executor, exceptionHandler);
return interceptor;
}
위의 setBeanFactory를 통해 실제 Adivosr(PointCut, Advice)와 @Annotation이 등록되는걸 볼 수 있다
AnnotationAsyncExecutionInterceptor은 이따 밑에서 알아보자
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
if (bean instanceof Advised) {
Advised advised = (Advised) bean;
if (!advised.isFrozen() && isEligible(AopUtils.getTargetClass(bean))) {
...
advised.addAdvisor(this.advisor);
return bean;
}
if (isEligible(bean, beanName)) {
ProxyFactory proxyFactory = prepareProxyFactory(bean, beanName);
proxyFactory.addAdvisor(this.advisor);
...
return proxyFactory.getProxy(classLoader);
}
return bean;
}
BestAfterProcessor에서 ProxyFactory를 통해 Advice가 등록되는걸 볼 수 있다
@Override
public Object invoke(final MethodInvocation invocation) throws Throwable {
...
AsyncTaskExecutor executor = determineAsyncExecutor(userDeclaredMethod);
...
Callable<Object> task = () -> {
Object result = invocation.proceed();
if (result instanceof Future) {
return ((Future<?>) result).get();
}
...
}
...
return doSubmit(task, executor, invocation.getMethod().getReturnType());
}
AnnotationAsyncExecutionInterceptor는 위에서 등록된 Advice로 Proxy객체에 접근할때 해당 Advice가 실행되는걸 알 수 있다
invoke메서드는 determineAsyncExectuor에 의해 Async전략을 정한뒤 해당 Thread를 실행시켜 Callback메서드를 받아온다
기본적으로 Async는 Bean이 등록된 상태로 Advice를 진행하는 것 이다
Pointcut cpc = new AnnotationMatchingPointcut(asyncAnnotationType, true);
Pointcut mpc = new AnnotationMatchingPointcut(null, asyncAnnotationType, true);
다음과같이 클래스대상 / 메서드대상으로 PointCut을 한다
SimpleAsyncTaskExecutor.class
protected void doExecute(Runnable task) {
Thread thread = (this.threadFactory != null ? this.threadFactory.newThread(task) : createThread(task));
thread.start();
}
계속 만들어서 사용
ThreadPoolTaskExecutor.class
public void execute(Runnable task) {
Executor executor = getThreadPoolExecutor();
try {
executor.execute(task);
}
}
ThreadPool사용
ConcurrentTaskExecutor.class
@Override
public Future<?> submit(Runnable task) {
return super.submit(ManagedTaskBuilder.buildManagedTask(task, task.toString()));
}
@Override
public ListenableFuture<?> submitListenable(Runnable task) {
return super.submitListenable(ManagedTaskBuilder.buildManagedTask(task, task.toString()));
}
Future사용
이외의 더 많은 TaskExecutor가 존재하며 Scheduler또한 위 인터페이스를 구현한다
@Configuration
@RequestMapping("/test")
public class TestController2 {
@ResponseBody
public String testBean() throws InterruptedException {
this.runAsync();
return "Clear";
}
@Bean
@Async
public TestBean2 runAsync() throws InterruptedException {
this.logger.info("After Good Async2");
return new TestBean2("Time : " + LocalDateTime.now());;
}
}
Bean만 등록되어있으면 비동기는 실행이 되는것 인데 해당 Async는 정상적으로 출력이 될까 ?
설명이 반대로 되어있습니다! 공식 문서에는 다음과 같이 되어있어요