@Async는 Spring에서 제공하는 비동기 처리를 지원하는 애노테이션으로, 특정 메서드가 호출될 때 별도의 스레드에서 비동기적으로 실행되도록 합니다. 이를 통해 응답 시간 단축, 병렬 처리, 서버 성능 최적화와 같은 이점을 얻을 수 있습니다.
1. @Async란?
Spring의 @Async는 메서드의 작업을 메인 스레드와 독립적으로 실행하게 함으로써, 메서드가 실행되는 동안 다른 작업을 수행할 수 있도록 합니다. 비동기 처리가 필요한 경우에 사용하며, Executor나 스레드 풀과 함께 사용하여 효율적인 비동기 환경을 구성할 수 있습니다.
사용 예시
아래는 @Async를 사용하여 메서드를 비동기적으로 호출하는 예시입니다.
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
@Service
public class AsyncService {
@Async
public void asyncMethod() {
System.out.println("비동기 작업 시작: " + Thread.currentThread().getName());
try {
Thread.sleep(2000); // 2초 지연
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("비동기 작업 완료");
}
}
설명
• @Async가 붙은 asyncMethod()는 메인 스레드와는 별도의 스레드에서 실행됩니다.
• 이 메서드를 호출하면 바로 반환되며, 2초의 지연이 발생하더라도 메인 스레드는 이를 기다리지 않고 진행됩니다.
2. @Async 설정 방법
@Async 애노테이션을 사용하려면 다음과 같은 설정이 필요합니다.
2.1 @EnableAsync 활성화
@Async를 사용하려면 Spring 애플리케이션에 비동기 처리를 활성화해야 합니다. @EnableAsync 애노테이션을 설정 클래스에 추가합니다.
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
@Configuration
@EnableAsync
public class AppConfig {
// 비동기 처리를 활성화하는 설정 클래스
}
2.2 Executor 설정 (선택 사항)
기본적으로 @Async는 SimpleAsyncTaskExecutor를 사용하여 스레드를 생성하지만, 성능과 효율성을 높이기 위해 커스텀 Executor를 설정할 수 있습니다.
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.TaskExecutor;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean(name = "asyncExecutor")
public TaskExecutor asyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(25);
executor.setThreadNamePrefix("AsyncExecutor-");
executor.initialize();
return executor;
}
}
• CorePoolSize: 기본적으로 유지할 스레드 수입니다.
• MaxPoolSize: 최대 스레드 수로, 대기열에 있는 작업을 처리하기 위해 추가로 생성할 수 있는 최대 스레드 수를 지정합니다.
• QueueCapacity: 최대 대기열 크기를 설정하여, 대기열이 가득 차면 새로운 스레드를 생성하지 않고 요청을 대기시킵니다.
이렇게 커스텀 Executor를 설정하면, @Async 애노테이션에서 @Async("asyncExecutor")와 같이 설정하여 사용할 수 있습니다.
3. @Async의 반환값과 Future
@Async 메서드는 일반적으로 void를 반환하지만, 결과값을 기다릴 수 있는 Future 또는 **CompletableFuture**를 사용할 수도 있습니다.
Future 사용 예시
@Async 메서드가 Future를 반환하면, 작업이 완료될 때까지 대기할 수 있습니다.
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import java.util.concurrent.CompletableFuture;
@Service
public class AsyncService {
@Async
public CompletableFuture<String> asyncMethodWithReturn() {
try {
Thread.sleep(2000); // 2초 지연
} catch (InterruptedException e) {
e.printStackTrace();
}
return CompletableFuture.completedFuture("비동기 작업 완료");
}
}
설명
• CompletableFuture: 비동기 작업의 결과를 반환하며, get()을 통해 결과가 준비될 때까지 대기할 수 있습니다.
• @Async 메서드는 반환된 Future가 완료될 때까지 작업을 유지합니다.
Future 결과 확인
메인 스레드에서 Future.get()을 호출하면 비동기 작업의 결과를 기다릴 수 있습니다.
public class Main {
public static void main(String[] args) throws Exception {
CompletableFuture<String> result = asyncService.asyncMethodWithReturn();
System.out.println("비동기 작업 결과: " + result.get()); // 결과를 기다림
}
}
4. @Async의 주요 활용 예
4.1 파일 처리, 이메일 전송 등 비동기 작업
@Async는 파일 처리, 이메일 전송, 푸시 알림 등 서버의 응답을 기다리지 않아도 되는 작업에서 유용하게 사용할 수 있습니다.
4.2 대규모 데이터 처리
대규모 데이터를 배치 작업으로 처리할 때, 작업을 여러 스레드로 나누어 처리하여 성능을 향상할 수 있습니다.
4.3 비동기 API 호출
외부 API를 호출할 때, 비동기로 실행하면 서버의 응답 시간이 단축되고 사용자가 빠르게 응답을 받을 수 있습니다.
5. @Async 사용 시 주의사항
5.1 트랜잭션 전파
@Async 메서드가 트랜잭션 내에서 호출될 경우, 트랜잭션 전파가 제대로 이루어지지 않을 수 있습니다. 트랜잭션이 필요한 작업은 비동기 방식 대신 트랜잭션 전파 속성을 명시하는 것이 좋습니다.
5.2 예외 처리
@Async 메서드에서 발생한 예외는 메인 스레드에서 잡히지 않으므로, AsyncUncaughtExceptionHandler를 통해 처리해야 합니다.
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.Executor;
@Configuration
public class AsyncExceptionConfig {
@Bean
public AsyncUncaughtExceptionHandler asyncExceptionHandler() {
return (ex, method, params) -> {
System.out.println("비동기 작업에서 예외 발생: " + ex.getMessage());
};
}
}
5.3 비동기 메서드 접근 제한
@Async 메서드는 프록시 패턴을 사용하여 비동기로 실행되기 때문에, 동일 클래스 내에서 호출하면 비동기 방식이 적용되지 않습니다. 이 경우, 외부에서 @Async 메서드를 호출하거나, 다른 서비스에 분리하여 호출해야 합니다.
6. @Async와 Spring Security
Spring Security와 함께 사용할 때, @Async 메서드에서는 SecurityContext가 유지되지 않아, 비동기 메서드 내에서 인증 정보가 사라질 수 있습니다. 이를 해결하기 위해 DelegatingSecurityContextCallable과 함께 사용할 수 있습니다.
import org.springframework.security.concurrent.DelegatingSecurityContextCallable;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
@Service
public class AsyncSecurityService {
@Async
public void asyncMethodWithSecurityContext() {
Callable<String> task = () -> {
// SecurityContextHolder의 인증 정보 접근
return SecurityContextHolder.getContext().getAuthentication().getName();
};
DelegatingSecurityContextCallable<String> securedTask = new DelegatingSecurityContextCallable<>(task);
executor.submit(securedTask);
}
}
결론
@Async는 Java에서 스레드 풀과 비동기 작업을 간단히 구현할 수 있게 해주는 강력한 도구로, Spring 환경에서 비동기 작업을 효율적으로 처리합니다. 서버의 리소스를 최적화하고, 비동기 요청 처리 속도를 높이려면 적절한 스레드 풀을 설정하고 @Async와 함께 사용하면 좋습니다. 또한, Spring Security와 함께 사용할 경우 인증 정보 전파에도 유용하게 사용할 수 있습니다.
'Spring Security' 카테고리의 다른 글
hasAuthority와 그 외의 다양한 표현식 (0) | 2024.10.30 |
---|---|
CSRF(Cross-Site Request Forgery) (2) | 2024.10.30 |
DelegatingSecurityContextCallable (0) | 2024.10.29 |
Callable과 Runnable (0) | 2024.10.29 |
Tomcat Executor (0) | 2024.10.29 |