이 에러는 현재 실행 중인 스레드에 웹 요청(HttpServletRequest) 정보가 바인딩되어 있지 않다는 의미입니다. 주로 비동기 작업(@Async)이나 백그라운드 스레드에서 Spring MVC의 요청 컨텍스트에 접근하려 할 때 발생합니다.
🔍 에러 원인과 시스템에 미치는 영향
이 에러는 스레드의 경계를 넘나드는 요청 정보 접근 때문에 발생합니다. Spring MVC는 웹 요청을 처리하는 스레드에 HttpServletRequest와 같은 데이터를 연결합니다. 하지만 @Async나 new Thread()로 새로운 스레드를 생성하면, 이 스레드는 기존의 요청 정보를 공유하지 않습니다. 따라서 새로운 스레드에서 RequestContextHolder를 사용해 요청 정보에 접근하려 할 때 에러가 발생합니다.
이 에러는 단순한 로그 기록을 넘어 시스템에 다음과 같은 영향을 미칠 수 있습니다.
- 기능 오류: 사용자 세션 정보나 인증/인가 데이터(예: 헤더의 토큰)에 접근해야 하는 비동기 작업이 제대로 수행되지 않아 서비스 기능 자체가 멈추거나 잘못된 결과를 반환할 수 있습니다.
- 데이터 무결성 문제: 요청과 관련된 특정 데이터를 처리하는 비동기 작업이 실패하면, DB에 데이터가 부분적으로만 저장되거나, 불완전한 상태로 남는 등 데이터 무결성 문제가 발생할 수 있습니다.
- 성능 저하 및 자원 낭비: 에러가 반복적으로 발생하면 애플리케이션 로그에 수많은 스택 트레이스(Stack Trace)가 쌓여 디스크 공간을 빠르게 채우고, 불필요한 예외 처리 로직으로 인해 CPU 자원을 낭비할 수 있습니다. 이는 시스템 전반의 성능 저하로 이어집니다.
- 사용자 경험 악화: 요청을 비동기로 처리하는 동안 에러가 발생하면, 사용자는 요청이 실패했는지 알 수 없거나, 예상치 못한 오류 페이지를 마주하게 되어 서비스에 대한 신뢰도를 잃을 수 있습니다.
💡 해결 방안: 요청 컨텍스트 전파하기
이 문제의 핵심은 비동기 스레드에게 부모 스레드의 요청 정보를 넘겨주는 것입니다.
1. @Async 사용 시 TaskExecutor 설정 (가장 추천)
Spring Boot에서 @Async를 사용한다면, TaskExecutor를 직접 구성하여 요청 컨텍스트를 자동으로 전파하도록 설정하는 것이 가장 안정적입니다. TaskDecorator를 이용해 부모 스레드의 요청 컨텍스트를 자식 스레드로 복사하고, 작업이 끝나면 정리하는 로직을 추가합니다.
예시 코드:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.TaskDecorator;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import java.util.concurrent.Executor;
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean(name = "taskExecutor")
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(25);
executor.setThreadNamePrefix("MyAsync-");
executor.setTaskDecorator(new TaskDecorator() {
@Override
public Runnable decorate(Runnable runnable) {
RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
return () -> {
try {
RequestContextHolder.setRequestAttributes(requestAttributes);
runnable.run();
} finally {
RequestContextHolder.resetRequestAttributes();
}
};
}
});
executor.initialize();
return executor;
}
}
이제 @Async를 사용할 때 taskExecutor 빈을 지정해주기만 하면 됩니다.
@Async("taskExecutor")
public void someAsyncMethod() {
// 이제 이 메서드 안에서도 RequestContextHolder에 접근 가능!
String sessionId = RequestContextHolder.currentRequestAttributes().getSessionId();
// ...
}
2. 필요한 데이터만 직접 전달하기
비동기 메서드에서 일부 데이터(예: 특정 헤더, 세션 ID)만 필요한 경우, HttpServletRequest 객체 전체를 넘기는 대신 필요한 데이터만 추출해서 파라미터로 전달하는 것이 좋습니다. 이 방법은 스레드 안정성 문제로부터 자유롭고 코드가 명확해집니다.
public String someControllerMethod(HttpServletRequest request) {
String authorizationHeader = request.getHeader("Authorization");
myService.myAsyncMethod(authorizationHeader);
return "OK";
}
@Async
public void myAsyncMethod(String authorizationHeader) {
// 이제 authorizationHeader를 바로 사용할 수 있습니다.
// ...
}'개발 (Development) > Java' 카테고리의 다른 글
| [Java] Object를 특정 클래스 타입으로 변환 (0) | 2025.11.28 |
|---|---|
| [Java] 객체 리스트를 특정 속성으로 정렬하는 방법 (2) | 2025.10.06 |
| [Java/Spring Boot] JdbcTemplate으로 여러 데이터베이스 연결하기 (0) | 2025.09.14 |
| [Java/Spring Boot] Spring Boot에서 OAuth2 인증 401 오류 해결하기 (0) | 2025.09.14 |
| [Java] JPA에서 DTO는 Interface로 구현할까? Class로 구현할까? (4) | 2025.08.03 |