[TIL] JpaAuditor, RequestContextHolder

Soeng_dev·2024년 12월 12일

RequestContextHolder를 통한 요청 컨텍스트 접근

RequestContextHolder.getRequestAttributes()를 사용해 HTTP 요청 정보를 가져올 수 있다.
RequestAttributes가 ServletRequestAttributes 인스턴스인 경우만 HTTP 요청을 다룰 수 있다.

요청 컨텍스트가 없는 경우 처리

비동기 작업(예: @Async 메서드, CompletableFuture 등)이나 스케줄러에서 호출되면 ServletRequestAttributes를 찾을 수 없다.
이 경우 예외를 던지거나 대체값을 반환하는 방법을 사용한다.

예외 처리

IllegalStateException
요청 컨텍스트가 없으면 IllegalStateException을 던진다.

private HttpServletRequest getCurrentHttpRequest() {
    RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
    if (requestAttributes instanceof ServletRequestAttributes) {
        return ((ServletRequestAttributes) requestAttributes).getRequest();
    }
    throw new IllegalStateException("No current HTTP request available. This can happen outside of an HTTP request context (e.g., async tasks or scheduled tasks).");
}

커스텀 예외
요청 컨텍스트가 없을 때 커스텀 예외를 던져 더 명확한 메시지를 전달한다.

public class RequestContextNotFoundException extends RuntimeException {
    public RequestContextNotFoundException(String message) {
        super(message);
    }
}
private HttpServletRequest getCurrentHttpRequest() {
    RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
    if (requestAttributes instanceof ServletRequestAttributes) {
        return ((ServletRequestAttributes) requestAttributes).getRequest();
    }
    throw new RequestContextNotFoundException("No HTTP request context available. This is likely caused by non-HTTP or background task execution.");
}

비동기 작업에서 요청 컨텍스트 전파

비동기 작업에서 요청 컨텍스트를 전파하려면 TaskDecorator를 사용한다.

1. TaskDecorator 구현

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

public class ContextCopyingDecorator implements TaskDecorator {
    @Override
    public Runnable decorate(Runnable runnable) {
        RequestAttributes context = RequestContextHolder.getRequestAttributes();
        return () -> {
            try {
                RequestContextHolder.setRequestAttributes(context);
                runnable.run();
            } finally {
                RequestContextHolder.resetRequestAttributes();
            }
        };
    }
}

2. 스레드 풀에 적용

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

@Configuration
public class AsyncConfig {

    @Bean
    public ThreadPoolTaskExecutor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10);
        executor.setMaxPoolSize(20);
        executor.setQueueCapacity(100);
        executor.setThreadNamePrefix("AsyncExecutor-");
        executor.setTaskDecorator(new ContextCopyingDecorator());
        return executor;
    }
}

@EnableJpaAuditing에서 AuditorAware 설정

AuditorAware를 사용하려면 반드시 Spring Bean으로 등록해야 한다.
@Component, @Bean 등을 사용해 AuditorAwareImpl을 Spring 컨텍스트에 등록한다.

@Component
public class AuditorAwareImpl implements AuditorAware<Long> {
    @Override
    public Optional<Long> getCurrentAuditor() {
        Span currentSpan = Span.current();
        if (!currentSpan.getSpanContext().isValid()) {
            return Optional.empty();
        }
        String userId = currentSpan.getAttribute("user.id");
        if (userId != null) {
            return Optional.of(Long.valueOf(userId));
        }
        return Optional.empty();
    }
}

@Component 없이 AuditorAware 사용

@Component 대신, @Bean으로 AuditorAware를 등록할 수 있다.

@Configuration
public class AuditorAwareConfig {

    @Bean
    public AuditorAware<Long> auditorAware() {
        return new AuditorAwareImpl();
    }
}
profile
Software Engineer

0개의 댓글