Backend/Spring+Boot

threadContextInheritable

findmypiece 2021. 4. 23. 17:42
728x90

사내 레거시 프로젝트 마이그레이션을 위해 분석하던 중 아래와 같은 설정이 보였다.

@Configuration
public class WebConfig {

    @Bean
    public ServletRegistrationBean servletRegistration(ServletRegistrationBean registration) {
        registration.setLoadOnStartup(1);
        registration.addInitParameter("threadContextInheritable", "true");
        return registration;
    }

}

Dispatchservlet의 threadContextInheritable 값을 true로 바꾸는 것인데 threadContextInheritable는 RequestContextHolder 를 자식 스레드에 상속되게 할지 여부를 결정하는 값으로 기본값은 false이다.

 

예를 들어 이 설정은 아래와 같은 상황에서 필요하다. 

@RestController
public class AlbumController {

    private static final Executor THREAD_POOL = Executors.newFixedThreadPool(3);

    @GetMapping("/hello")
    public Object hello2(HttpServletResponse response) throws Exception {

        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            HttpServletRequest httpServletRequest = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
            return httpServletRequest.getRequestURI();
        }, THREAD_POOL);

        String requestUri = future.get();

        return ResponseEntity.ok(requestUri);

    }
}

 

Executors 로 스레드 풀을 생성해서 해당 스레드 내부에서 RequestContextHolder 를 참조하는 경우이다. 이때 threadContextInheritable 가 false 로 설정되어 있다면 아래와 같은 예외가 발생할 것이다.

java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.

 

그리고 registration.setLoadOnStartup(1); 설정은 여기서 해도 되지만 application.yml 파일에서 아래와 같이 하는 것이 더 좋아보인다. 위와 같이 Dispatchservlet 를 커스터마이징 하지 않는 경우도 있을텐데 SpringBoot 에서는 load-on-startup 기본값이 -1 이라 되도록 양수로 할당해 놓는 것이 성능상 유리하기 때문이다.

 

참고로 load-on-startup 이 -1일 경우 Dispatchservlet 초기화가 초기 어플리케이션 로드시 수행되지 않고 최초 클라이언트 요청시 수행되기 때문에 최초 사용자는 두번째 사용자보다 응답시간이 상대적으로 길어질 수 있다.

 


여담으로 Dispatchservlet 설정을 커스터마이징 하는 것은 이것 말고도 ServletContextInitializer 를 구현하는 방법도 있다. 그런데 서칭을 해보니 ServletRegistrationBean 를 사용하는 방식이 선호되는 걸로 보인다.

 

그리고 threadContextInheritable 를 검색하면 스레드풀을 환경에서 주의해서 사용하라는 문구가 정말 많이 나오는데 이는 InheritableThreadLocal 를 직접 사용할때만 해당하는 말이다. InheritableThreadLocal 를 직접 사용할 경우 스레드가 new 로 생성될때만 상속이 진행되기 때문에 풀에서 재사용되는 스레드에서는 InheritableThreadLocal 가 null 로 참조된다.

 

threadContextInheritable 가 true 상태에서 RequestContextHolder 가 InheritableThreadLocal 기반으로 처리되지만 RequestContextHolder 에는 재사용되는 스레드에 대해서도 문제가 없도록 처리가 되어있기 때문에 아무런 문제가 없다. 나는 이걸 확인하려고 반나절을 동안 서칭을 하고 테스트를 했지만 이 글을 읽는 사람들은 괜한 걱정은 하지 말길 빈다.

 

stackoverflow.com/questions/14986519/spring-mvc-how-to-use-a-request-scoped-bean-inside-a-spawned-thread
www.javaer101.com/ko/article/5895566.html
github.com/spring-projects/spring-boot/issues/2481
728x90

'Backend > Spring+Boot' 카테고리의 다른 글

@RestController  (0) 2021.04.27
@ResquestParam, @RequestBody 와 Get, Post  (0) 2021.04.26
RequestContextHolder  (0) 2021.04.23
Spring Security AuthenticationException message  (0) 2021.04.22
SpringBoot MVC 자동구성 제어하기  (0) 2021.04.21