Spring @Async

  • 메소드를 비동기 처리하면 호출자는 호출한 메소드가 완료될 때 까지 기다릴 필요가 없다.
  • @Async를 사용하여 별도의 쓰레드에서 실행시킬 것이다.

Async Configuration

// Java Config
@Configuration
@EnableAsync
public class SpringAsyncConfig {
  ...
}

// XML Config
<task:executor id="myexecutor" pool-size="5"/>
<task:annotation-driven executor="myexecutor"/>

@Async

제약사항

  • @Async로 명시된 메소드는 반드시 public으로 선언되어야 한다. 메소드가 public 이어야 프록시가 될 수 있기 때문이다.
  • 같은 클래스 내에서 해당 메소드를 호출할 경우 비동기로 작동하지 않는다. 셀프호출은 프록시를 우회하고 해당 메소드를 직접 호출하기 때문이다.

void 반환형

@Async
public void asyncMethodWithVoidReturnType() {
    System.out.println("Execute method asynchronously. "
      + Thread.currentThread().getName());
}

Future 반환형

@Async
public Future<String> asyncMethodWithReturnType() {
    System.out.println("Execute method asynchronously - "
      + Thread.currentThread().getName());
    try {
        Thread.sleep(5000);
        return new AsyncResult<String>("hello world !!!!");
    } catch (InterruptedException e) {
        //
    }

    return null;
}

Executor

  • 스프링은 기본값으로 SimpleAsyncTaskExecutor를 사용하여 실제 메소드들을 비동기 실행한다. 기본 설정은 메소드/애플리케이션 레벨로 재정의하여 사용할 수 있다.
  • 다음과 같이 설정하면 @Async로 지정된 메소드를 실행하는 기본 실행자가 변경된다. 실행자의 상세 옵션들도 변경할 수 있다.

Method Level Override

  • 실행자 빈을 설정하고 @Async 속성에 실행자명을 명시해야 한다.
@Configuration
@EnableAsync
public class SpringAsyncConfig {

    @Bean(name = "threadPoolTaskExecutor")
    public Executor threadPoolTaskExecutor() {
        return new ThreadPoolTaskExecutor();
    }
}

@Async("threadPoolTaskExecutor")
public void asyncMethodWithConfiguredExecutor() {
    System.out.println("Execute method with configured executor - "  + Thread.currentThread().getName());
}

Application Level Override

  • AsyncConfigurer 인터페이스를 구현하여 getAsyncExecutor() 메소드를 오버라이드해야 한다.
@Configuration
@EnableAsync
public class SpringAsyncConfig implements AsyncConfigurer {

  @Override
  public Executor getAsyncExecutor() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(5);
    executor.setMaxPoolSize(100);
    executor.setQueueCapacity(100);
    executor.setThreadNamePrefix("TEST_");
    executor.initialize();
    return executor;
  }

}

Exception Handling

  • 반환형이 void일 때, 예외는 호출 스레드에 전달되지 않는다. 예외 처리를 위한 설정이 추가적으로 필요하다.
@Configuration
@EnableAsync
public class SpringAsyncConfig implements AsyncConfigurer {

  @Override
  public Executor getAsyncExecutor() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(5);
    executor.setMaxPoolSize(100);
    executor.setQueueCapacity(100);
    executor.setThreadNamePrefix("TEST_");
    executor.initialize();
    return executor;
  }

  @Override
  public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
    return new SimpleAsyncUncaughtExceptionHandler();
    // Custom 가능
    // return new CustomAsyncExceptionHandler();
  }

}
  • AsyncUncaughtExceptionHandler 인터페이스를 구현하여 커스텀 비동기 예외처리자를 만들 수도 있다. handleUncaughtException() 메소드는 잡히지 않은 비동기 예외가 발생할때 호출된다.
public class CustomAsyncExceptionHandler implements AsyncUncaughtExceptionHandler {

    @Override
    public void handleUncaughtException(Throwable throwable, Method method, Object... obj) {
        System.out.println("Exception message : " + throwable.getMessage());
        System.out.println("Method name : " + method.getName());
        for (Object param : obj) {
            System.out.println("Parameter value : " + param);
        }
    }

}

Etc

  • Spring Boot 기반의 웹 애플리케이션은 HTTP 요청이 들어왔을 때 내장된 서블릿 컨테이너가 관리하는 독립적인 1개의 Worker 쓰레드에 의해 동기 방식으로 실행된다. 하지만 요청 처리 중 @Async가 명시된 메소드를 호출하면 앞서 등록한ThreadPoolTaskExecutor 빈에 의해 관리되는 또 다른 독립적인 Worker 쓰레드로실행된다. 별도의 쓰레드로 동작하기 때에 원래의 요청 쓰레드는 그대로 다음 문장을 실행하여 HTTP 응답을 마칠 수 있다.

  • Async 어노테이션을 사용하는 순간 서로 다른 Thread에서 동작하기 때문에 앞에서 생성한 Transaction이 연장되지 않으므로 주의해야 한다.

레퍼런스 - http://www.baeldung.com/spring-async

'Development > Spring' 카테고리의 다른 글

DatabaseConfiguration 적용  (0) 2018.08.17
Configuration Condition  (0) 2018.07.16
multiple 'X-Frame-Options' headers with conflicting 에러  (0) 2017.12.15
비동기 메소드 HttpServletRequest 에러  (0) 2017.12.03
캐시 어노테이션  (0) 2017.10.06

+ Recent posts