Spring Boot 애플리케이션에서 비동기 태스크는 일반적으로 @Async 어노테이션을 사용하여 구현됩니다. 이를 통해 백그라운드 스레드에서 메서드를 실행할 수 있어 애플리케이션의 성능과 응답 속도를 향상시킬 수 있습니다. 그러나 비동기 태스크에서 HttpServletRequest 객체에 접근해야 하는 경우, 몇 가지 도전에 직면하게 됩니다. HttpServletRequest는 스레드에 바인딩되어 있지만, 비동기 태스크는 다른 스레드에서 실행되기 때문입니다. 그럼에도 불구하고, 몇 가지 전략과 Spring의 지원을 통해 비동기 태스크에서 HttpServletRequest에 접근하는 것이 가능합니다.
해결 방안
1. RequestContextHolder를 활용한 HttpServletRequest 전달
Spring은 RequestContextHolder 클래스를 제공하며, 이를 사용하면 현재 요청의 HttpServletRequest 객체에 접근할 수 있습니다. RequestContextHolder는 ThreadLocal을 사용하여 요청을 저장하므로 스레드에 바인딩됩니다. 비동기 메서드에서 RequestContextHolder를 사용할 때는 요청 컨텍스트가 비동기 실행 스레드로 전달될 수 있도록 해야 합니다.
비동기 태스크에서 요청 컨텍스트를 유지하려면, 비동기 태스크를 실행하기 전에 RequestContextHolder의 setRequestAttributes 메서드를 통해 요청 컨텍스트를 수동으로 전달해야 합니다.
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
@Service
public class BackgroundTaskService {
@Async
public void processAsyncOperation() {
RequestAttributes context = RequestContextHolder.getRequestAttributes();
if (context != null) {
HttpServletRequest request = ((ServletRequestAttributes) context).getRequest();
String requestUri = request.getRequestURI();
String method = request.getMethod();
// 요청 정보를 활용한 비즈니스 로직 수행
}
// 비동기 로직 실행
}
}
비동기 메서드 호출 전에 요청 컨텍스트가 설정되었는지 확인합니다:
@Service
public class BusinessService {
@Autowired
private BackgroundTaskService backgroundTaskService;
public void executeBusinessLogic(HttpServletRequest httpRequest) {
RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(httpRequest));
backgroundTaskService.processAsyncOperation();
RequestContextHolder.resetRequestAttributes();
}
}
2. @Async 어노테이션의 mode 속성 활용
Spring 4.3부터 @Async 어노테이션은 mode 속성을 제공하며, 이를 통해 비동기 메서드의 실행 모드를 지정할 수 있습니다. mode를 AspectJ로 설정하면 Spring이 비동기 메서드를 호출할 때 요청 컨텍스트를 유지합니다.
@Async(mode = AdviceMode.ASPECTJ)
public void processAsyncOperation() {
RequestAttributes context = RequestContextHolder.getRequestAttributes();
if (context != null) {
HttpServletRequest request = ((ServletRequestAttributes) context).getRequest();
Map<String, String> headers = new HashMap<>();
Enumeration<String> headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
String headerName = headerNames.nextElement();
headers.put(headerName, request.getHeader(headerName));
}
// 헤더 정보를 활용한 처리
}
// 비동기 로직 실행
}