반응형
여승철
INTP 개발자
여승철
  • 분류 전체보기 (376)
    • CS (16)
      • 면접 준비 (7)
      • 운영체제 (0)
      • 네트워크 (2)
      • HTTP (6)
      • 스프링(Spring) IoC 컨테이너 (0)
      • 알고리즘 (1)
    • Web (13)
    • AWS (6)
    • Java (43)
    • JSP & Servlet (65)
      • 개념 (42)
      • 실습 (23)
    • Spring Framework (33)
    • Spring Boot (10)
    • Spring Data (22)
      • JPA (14)
      • Query DSL (7)
      • Redis (1)
    • Spring Security (9)
    • Spring Batch (4)
    • MyBatis (10)
    • Front-End (51)
      • JS (27)
      • Vue.js (17)
      • React (5)
      • JQuery (0)
      • d3.js (2)
    • DBMS (24)
      • SQL, RDBMS (16)
      • MongoDB (5)
      • Redis (3)
    • Kafka (3)
    • 리눅스 (Linux) (4)
    • 디자인 패턴 (3)
    • VCS (8)
    • API (0)
    • TOOL (3)
    • Reading Book (28)
      • 이펙티브 자바 (11)
      • Clean Code (10)
      • 1분 설명력 (4)
      • HOW TO 맥킨지 문제해결의 기술 (3)
    • C# (4)
    • NSIS (6)
    • ETC (11)

블로그 메뉴

  • 홈
  • 태그

인기 글

태그

  • 로그인
  • querydsl
  • 스트림
  • Dao
  • jsp
  • servlet
  • EC2
  • Spring Batch
  • HTTP
  • ubuntu
  • 게시판
  • JSTL
  • 환경 세팅
  • mybatis
  • 디자인 패턴
  • 맥킨지
  • controller
  • 이펙티브 자바
  • 회원 관리
  • JDBC

최근 댓글

최근 글

hELLO· Designed By 정상우.
여승철

INTP 개발자

Spring 비동기 처리 @Async, CompletableFuture, TaskDecorator
Spring Framework

Spring 비동기 처리 @Async, CompletableFuture, TaskDecorator

2022. 9. 9. 00:54
반응형

`비동기 처리`는 작업을 별도의 스레드에서 실행하고 결과를 나중에 처리하는 방식입니다. 이를 통해 특정 로직의 실행이 끝날 때 까지 기다리지 않고 다음 코드를 실행할 수 있으며, 결과가 준비되면 이벤트를 받거나 콜백을 통해 처리합니다. 


Spring Framework 비동기 처리 방식

  • `Callable`과 `DeferredResult`
    • Spring Framework에서 스레드 기반의 비동기 처리를 지원합니다.
    • `Callable`: 비동기 작업을 `Callable`로 감싸고 `AsyncTaskExecutor`를 통해 별도의 스레드에서 실행합니다. 작업이 완료되면 `Callable`을 반환합니다.
    • `DeferredResult`: 비동기 작업의 결과를 나중에 처리할 수 있는 `DeferredResult`를 반환합니다. 작업이 완료된 후 반환한 `DeferredResult`에 결과를 세팅하고 응답을 보낼 수 있습니다.

  • `@Async` ⭐ ⭐ ⭐
    @Async 어노테이션은 메서드에 적용하여 해당 메서드를 비동기적으로 실행하게 하는 방식입니다. 이 방식은 비교적 간단하게 비동기 처리를 구현할 수 있으며, 비동기 작업의 결과를 `Future`나 `CompletableFuture`로 반환할 수 있습니다. 또한, Spring이 제공하는 `TaskExecutor` 빈을 통해 스레드 풀을 구성하고 작업을 분산 처리할 수도 있습니다.

  • ListenableFuture
    ListenableFuture 인터페이스는 비동기 작업의 결과에 대한 콜백 메서드를 등록할 수 있도록 합니다. 콜백 메서드를 통해 논블로킹 처리가 가능합니다. 첫번째 파라미터는 성공시 실행할 것을, 두 번째 파라미터는 실패시 실행할 것을 지정해주면 됩니다.

  • CompletableFuture
    CompletableFuture는 Java 8에서 추가된 클래스로, 비동기 작업의 결과를 처리하기 위한 기능을 제공합니다.  ListenableFuture와 유사한 기능을 제공하며, 추가적으로 비동기 작업의 연결, 조합, 변환 등을 쉽게 처리할 수 있습니다.

 


@Async 사용을 위한 비동기 기능 설정

1. @EnableAsync 활성화

설정 클래스에 `@EnableAsync`를 적용하여, Spring Framework가 비동기 처리를 지원하도록 활성화합니다.

@Configuration
@EnableAsync
public class AppConfig {
    // 설정 내용
}

2. TaskExecutor 빈 설정

비동기 작업을 처리할 `스레드 풀`을 구성하기 위해 `TaskExecutor` 빈을 설정해줍니다.

아래 예제 코드에서는 Spring이 제공하는 구현체 중 하나인 `ThreadPoolTaskExecutor`를 사용하였습니다. 또한 필요에 따라 세밀한 제어를 위해 스레드 풀의 크기 설정을 조정할 수 있습니다.

아래 예제는 `getAsyncExecutor()`메서드를 오버라이드하여 `ThreadPoolTaskExecutor`를 구성하여 반환한 예제입니다.

@Configuration
@EnableAsync
public class AppConfig implements AsyncConfigurer {

    private int CORE_POOL_SIZE = 10;
    private int MAX_POOL_SIZE = 100;
    private int QUEUE_CAPACITY = 1000;

    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(CORE_POOL_SIZE);  // 스레드 풀의 기본 크기
        executor.setMaxPoolSize(MAX_POOL_SIZE);    // 스레드 풀의 최대 크기
        executor.setQueueCapacity(QUEUE_CAPACITY); // 대기열 크기
        executor.initialize();                     // 스레드 풀 초기화
        return executor;
    }
}

3. @Async 어노테이션 적용

비동기로 실행할 메서드에 `@Async`를 부여해주면 비동기 처리(별도의 쓰레드)를 수행합니다. 호출자는 이 메서드를 호출하고 나서 다른 작업을 수행할 수 있습니다.

비동기 메서드를 호출하면Spring은 내부적으로 `TaskExecutor`를 사용하여 비동기 작업을 처리하고 작업이 완료되면 `Future` 객체나 `CompletableFuture`를 반환하여 비동기 작업의 결과를 처리할 수 있습니다.

@Service
public class AsyncService {

    @Async
    public void asyncMethod() {
        // 비동기로 실행될 코드
    }
}

4. 호출자에서 비동기 메서드 호출

비동기 메서드인 `asyncMethod()`를 호출하는 예제입니다. 이때 호출자는 `asyncMethod()`가 실행 중일 때 다른 작업을 수행할 수 있습니다.

@Controller
public class AsyncController {
    
    @Autowired
    private AsyncService asyncService;
    
    @GetMapping("/doAsync")
    public String doAsync() {
    
        // 비동기 메서드 호출
        myService.asyncMethod();
        
        // ... 다른 작업들
        // (호출자는 비동기 메서드가 실행 중일 때 다른 작업 수행 가능)
        
        return "result";
    }
}

 


@Async과 CompletableFuture

`@Async` 어노테이션은 메서드에 적용되며, 해당 메서드를 비동기적으로 실행하도록 지정합니다. 메서드가 호출되면 Spring은 내부적으로 별도의 스레드를 생성하여 작업을 비동기적으로 실행합니다. `@Async` 어노테이션은 메서드의 반환 타입이 `void` 또는 `Future<T>`일 수 있습니다. 반환 타입이 `Future<T>`인 경우, 비동기 작업이 완료되면 `Future` 객체를 통해 결과를 얻을 수 있습니다.

`CompletableFuture`는 Java 8에서 추가된 클래스로, 비동기 작업의 결과를 처리하기 위한 기능을 제공합니다. `CompletableFuture`는 `Future` 인터페이스를 확장하고, 비동기 작업의 결과를 처리하거나 다른 비동기 작업을 조합하는데 사용됩니다. `CompletableFuture`는 다양한 메서드를 제공하여 작업을 구성하고 조합할 수 있으며, 비동기 작업의 결과를 처리하기 위한 콜백 메서드를 등록할 수 있습니다.

`@Async` 어노테이션을 사용하면 Spring은 내부적으로 비동기 작업을 처리하기 위해 `CompletableFuture`를 활용합니다. 예를 들어, `@Async` 어노테이션이 적용된 메서드는 `CompletableFuture.supplyAsync` 또는 `CompletableFuture.runAsync` 메서드를 호출하여 해당 메서드를 비동기적으로 실행합니다. `CompletableFuture`는 비동기 작업의 결과를 `Future` 객체로 래핑하여 반환하므로, 비동기 작업이 완료된 후에 결과를 처리할 수 있습니다.

 

즉, `@Async` 어노테이션을 사용하여 메서드를 비동기적으로 실행하고, `CompletableFuture`를 활용하여 비동기 작업의 결과를 처리할 수 있습니다.

 

예시1: 기본 동작

1. 비동기 작업을 수행할 메서드에 `@Async` 어노테이션을 적용하고 메서드는 `CompletableFuture`를 반환하도록 설정합니다.

@Service
public class AsyncService {
    
    @Async
    public CompletableFuture<String> doAsyncTask() {
        // 비동기적으로 실행되어야 하는 작업을 수행합니다.
        // 작업이 완료되면 결과를 CompletableFuture에 설정합니다.
        String result = "비동기 작업 완료";
        return CompletableFuture.completedFuture(result);
    }
}

 

2. 비동기 작업을 호출한 후, CompletableFuture를 사용하여 결과를 처리합니다.

@RestController
public class AsyncController {
    
    private final AsyncService asyncService;
    
    public MyController(AsyncService asyncService) {
        this.asyncService = asyncService;
    }
    
    @GetMapping("/async-task")
    public CompletableFuture<String> handleAsyncTask() {
    
        // 비동기 작업을 호출하고 CompletableFuture를 반환받습니다.
        CompletableFuture<String> futureResult = asyncService.doAsyncTask();
        
        // CompletableFuture를 통해 비동기 작업의 결과를 처리합니다.
        futureResult.thenApply(result -> {
            // 비동기 작업이 완료된 후 실행될 로직을 작성합니다.
            // result는 비동기 작업의 결과입니다.
            return "비동기 작업 결과: " + result;
        });
        
        return futureResult;
    }
}

`CompletableFuture` 메서드

`CompletableFuture`를 사용하면 비동기 작업의 결과를 콜백 함수나 연산 체인을 통해 처리할 수 있습니다. 아래는 `CompletableFuture`의 메서드 중 일부입니다.

  • thenApply(Function<? super T,? extends U> fn)
    • CompletableFuture의 결과를 가공하고 변환하는데 사용됩니다.
    • Function<T, U>를 인자로 받아 결과를 변환한 CompletableFuture<U>를 반환합니다.
  • thenAccept(Consumer<? super T> action)
    • CompletableFuture의 결과를 소비하고 추가 작업을 수행하는데 사용됩니다.
    • Consumer<T>를 인자로 받아 추가 작업을 수행합니다.
  • thenRun(Runnable action)
    • CompletableFuture의 결과를 사용하지 않고 추가 작업을 수행하는데 사용됩니다.
    • Runnable을 인자로 받아 추가 작업을 수행합니다.
  • exceptionally(Function<Throwable,? extends T> fn)
    • CompletableFuture에서 발생한 `예외`를 처리하는데 사용됩니다.
    • Function<Throwable, T>를 인자로 받아 예외를 처리하고 대체 값을 반환하는 CompletableFuture<T>를 반환합니다.
  • handle(BiFunction<? super T,Throwable,? extends U> fn)
    • CompletableFuture의 결과 또는 `예외`를 처리하는데 사용됩니다.
    • BiFunction<T, Throwable, U>를 인자로 받아 결과 또는 예외를 처리하고 결과를 반환하는 CompletableFuture<U>를 반환합니다.
  • whenComplete(BiConsumer<? super T,? super Throwable> action)
    • CompletableFuture의 결과 또는 `예외`를 소비하고 추가 작업을 수행하는데 사용됩니다.
    • BiConsumer<T, Throwable>를 인자로 받아 결과 또는 예외를 처리하고 추가 작업을 수행합니다.
  • allOf(CompletableFuture<?>... cfs)
    • 여러 개의 CompletableFuture를 동시에 실행하고, 모든 CompletableFuture의 완료를 기다리는데 사용됩니다.
    • 모든 CompletableFuture가 완료되면 CompletableFuture<Void>를 반환합니다.
  • anyOf(CompletableFuture<?>... cfs)
    • 여러 개의 CompletableFuture 중 가장 빨리 완료된 CompletableFuture를 반환하는데 사용됩니다.
    • 가장 빨리 완료된 CompletableFuture의 결과 타입을 반환합니다.

 

예시2: 예외 발생

@Service
public class AsyncService {

    @Async
    public CompletableFuture<String> doSomethingAsync() {
        try {
            // 비동기로 수행할 작업
            // ...
            // 예외 발생 가능성이 있는 작업
            if (someCondition) {
                throw new RuntimeException("오류!");
            }
            return CompletableFuture.completedFuture("작업 성공!");
        } catch (Exception e) {
            return CompletableFuture.failedFuture(e);
        }
    }
}
  • 작업이 성공하면 결과 값을 감싸고, 예외가 발생하면 예외를 감싼 `CompletableFuture`를 반환합니다.
  • `failedFuture()` 메서드를 사용하여 예외를 감싸지 않고 예외를 바로 던지더라도 상관없습니다.  
    `filedFuture()`를 사용하는 이유는 예외를 명시적으로 처리할 수 있기 때문입니다.
public class MyApp {

    public static void main(String[] args) {
        AsyncService asyncService = new AsyncService();

        CompletableFuture<String> future = asyncService.asyncMethod();

        future.thenAccept(result -> {
            // 작업이 완료되었을 때 결과 처리
            System.out.println("결과: " + result);
        }).exceptionally(e -> {
            // 예외 처리
            System.err.println("예외 발생: " + e.getMessage());
            return null;
        });
    }
}

`CompletableFuture`에서 예외가 발생한 경우에 `exceptionally()`가 호출됩니다. 이를 통해 예외가 발생하면 해당 예외를 처리하는 로직을 수행할 수 있습니다.


TaskDecorator: 비동기 작업 데코레이터

`TaskDecorator`는 Spring의 TaskExecutor에 대한 데코레이터 인터페이스입니다.

비동기 작업을 실행하는 동안 작업 실행 전후에 추가적인 작업을 수행할 수 있도록 해줍니다.

이를 통해 비동기 작업의 작업 실행 컨텍스트를 변경하거나, 작업에 대한 추가적인 로깅, 보안 체크, 성능 모니터링 등을 수행할 수 있습니다.

import org.springframework.core.task.TaskDecorator;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

public class CustomTaskDecorator implements TaskDecorator {

    @Override
    public Runnable decorate(Runnable runnable) {
        // 현재 요청의 RequestAttributes를 가져옴
        RequestAttributes attributes = RequestContextHolder.currentRequestAttributes();
        
        return () -> {
            try {
                // 작업 실행 전에 RequestAttributes를 설정
                RequestContextHolder.setRequestAttributes(attributes);
                
                // 작업 실행
                runnable.run();
            } finally {
                // 작업 실행 후에 RequestAttributes를 제거
                RequestContextHolder.resetRequestAttributes();
            }
        };
    }
}
  • `decorate()`메서드를 오버라이드해서 작업 실행 전후에 추가적인 작업을 수행할 수 있도록 하였습니다.
@Configuration
@EnableAsync
public class AsyncConfig {

    private int CORE_POOL_SIZE = 10;
    private int MAX_POOL_SIZE = 100;
    private int QUEUE_CAPACITY = 1000;

    @Bean
    public ThreadPoolTaskExecutor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(CORE_POOL_SIZE);  // 스레드 풀의 기본 크기
        executor.setMaxPoolSize(MAX_POOL_SIZE);    // 스레드 풀의 최대 크기
        executor.setQueueCapacity(QUEUE_CAPACITY); // 대기열 크기
        executor.setThreadNamePrefix("async-");    // 스레드 접두어
        
        // CustomTaskDecorator를 TaskExecutor에 등록
        executor.setTaskDecorator(new CustomTaskDecorator());
        
        executor.initialize();
        return executor;
    }
}

`AsyncConfigurer`를 구현하는 방식과 `Executor`를 빈으로 등록하는 방식은 같습니다. 차이점은 복잡하고 많은 커스터마이징이 필요한 경우 `AsyncConfigurer`를 구현하는 방식이 용이하다는 점입니다.

 


RejectedExecutionHandler: 거부 작업 처리

스레드 풀이 작업을 처리할 수 있는 한계에 도달하면, 새로운 요청 작업이 거부될 수 있습니다. 이런 경우 `RejectetdExecutionHandler`를 사용하여 처리 방식을 지정할 수 있습니다.

ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();

//...

executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
  • `AbortPolicy`: Default, 거부된 실행 요청이 발생하면 `RejectedExecutionException`을 던지고 예외를 발생시킵니다.
  • `CallerRunsPolicy`: 거부된 실행 요청이 발생하면 해당 요청을 호출한 스레드에서 직접 실행합니다. 이 경우, 스레드 풀이 작업을 처리할 수 있는 능력을 초과하는 경우에도 실행 요청을 처리할 수 있는 임시 방법으로 사용될 수 있습니다. 하지만 호출 스레드는 블로킹될 수 있으므로 주의해야 합니다.
  • `DiscardPolicy`: 거부된 실행 요청을 무시합니다.
  • `DiscardOldestPolicy`: 거부된 실행 요청 대신 가장 오래된 요청을 제거하고 새로운 요청을 수락합니다.
반응형

'Spring Framework' 카테고리의 다른 글

Spring ResponseEntity  (0) 2022.09.21
Spring Redirect: 다른 URL로 리다이렉트  (0) 2022.09.14
Spring 파일 업로드  (0) 2022.09.07
Spring @SessionAttributes, @SessionStatus: Model과 연동을 통한 상태 유지  (0) 2022.09.07
Spring REST API 개념과 흐름도  (0) 2022.09.06
    여승철
    여승철

    티스토리툴바