복붙노트

[SPRING] 비동기 태스크 실행 프로그램에서 요청 범위를 사용하는 방법

SPRING

비동기 태스크 실행 프로그램에서 요청 범위를 사용하는 방법

내 응용 프로그램에서 일부 비동기 웹 서비스가 있습니다. 서버가 요청을 수락하고 OK 응답을 반환하고 AsyncTaskExecutor를 사용하여 처리 요청을 시작합니다. 내 질문은 요청 범위를 여기에서 사용 가능하게하는 방법이다. 왜냐하면이 처리에서 나는 다음과 같이 주석이 달린 클래스를 가져와야하기 때문이다 :

@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)

이제 예외가 생깁니다.

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'scopedTarget.requestContextImpl': Scope 'request' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton; nested exception is 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/DispatcherPortlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.

DispatcherServlet이 아니라 SimpleAsyncTaskExecutor에서 실행되기 때문에

내 비동기 요청 처리

taskExecutor.execute(new Runnable() {

    @Override
    public void run() {
        asyncRequest(request);
    }
});

여기서 taskExecutor는 다음과 같습니다.

<bean id="taskExecutor" class="org.springframework.core.task.SimpleAsyncTaskExecutor" />

해결법

  1. ==============================

    1.@Async를 사용하여 백그라운드에서 코드를 실행하는 데 필요한 동일한 문제가 발생하여 Session 또는 RequestScope 빈을 사용할 수 없었습니다. 우리는 다음과 같은 방법으로 그것을 해결했습니다 :

    @Async를 사용하여 백그라운드에서 코드를 실행하는 데 필요한 동일한 문제가 발생하여 Session 또는 RequestScope 빈을 사용할 수 없었습니다. 우리는 다음과 같은 방법으로 그것을 해결했습니다 :

    참고 : 이것은 세션 및 요청 범위 콩에서만 작동하며 보안 컨텍스트에서는 작동하지 않습니다 (Spring Security에서와 같이). 보안 컨텍스트를 설정하는 데 다른 방법을 사용해야합니다.

    주 2 : 간결성을 위해 Callable 및 submit () 구현 만 표시했습니다. Runnable과 execute ()에 대해서도 동일한 작업을 수행 할 수 있습니다.

    다음은 코드입니다.

    집행자:

    public class ContextAwarePoolExecutor extends ThreadPoolTaskExecutor {
        @Override
        public <T> Future<T> submit(Callable<T> task) {
            return super.submit(new ContextAwareCallable(task, RequestContextHolder.currentRequestAttributes()));
        }
    
        @Override
        public <T> ListenableFuture<T> submitListenable(Callable<T> task) {
            return super.submitListenable(new ContextAwareCallable(task, RequestContextHolder.currentRequestAttributes()));
        }
    }
    

    호출 가능 :

    public class ContextAwareCallable<T> implements Callable<T> {
        private Callable<T> task;
        private RequestAttributes context;
    
        public ContextAwareCallable(Callable<T> task, RequestAttributes context) {
            this.task = task;
            this.context = context;
        }
    
        @Override
        public T call() throws Exception {
            if (context != null) {
                RequestContextHolder.setRequestAttributes(context);
            }
    
            try {
                return task.call();
            } finally {
                RequestContextHolder.resetRequestAttributes();
            }
        }
    }
    

    구성 :

    @Configuration
    public class ExecutorConfig extends AsyncConfigurerSupport {
        @Override
        @Bean
        public Executor getAsyncExecutor() {
            return new ContextAwarePoolExecutor();
        }
    }
    
  2. ==============================

    2.원래의 부모 요청 처리 스레드가 클라이언트에 대한 응답을 이미 커밋하고 모든 요청 객체가 파괴되었으므로 하위 비동기 스레드에서 요청 범위 개체를 가져올 수 없습니다. 이러한 시나리오를 처리하는 한 가지 방법은 SimpleThreadScope와 같은 사용자 지정 범위를 사용하는 것입니다.

    원래의 부모 요청 처리 스레드가 클라이언트에 대한 응답을 이미 커밋하고 모든 요청 객체가 파괴되었으므로 하위 비동기 스레드에서 요청 범위 개체를 가져올 수 없습니다. 이러한 시나리오를 처리하는 한 가지 방법은 SimpleThreadScope와 같은 사용자 지정 범위를 사용하는 것입니다.

    SimpleThreadScope의 문제점 중 하나는 내부 ThreadLocal을 사용하기 때문에 자식 스레드가 부모 범위 변수를 상속받지 않는다는 것입니다. 이를 극복하기 위해 SimpleThreadScope와 완전히 유사하지만 InheritableThreadLocal을 내부적으로 사용하는 사용자 정의 범위를 구현하십시오. 더 많은 정보를 원하시면 Spring MVC : 스폰 된 스레드 내에서 요청 범위의 bean을 사용하는 방법은 무엇입니까?

  3. ==============================

    3.가장 쉬운 방법은 다음과 같은 작업 꾸미기를 사용하는 것입니다.

    가장 쉬운 방법은 다음과 같은 작업 꾸미기를 사용하는 것입니다.

    static class ContextCopyingDecorator implements TaskDecorator {
        @Nonnull
        @Override
        public Runnable decorate(@Nonnull Runnable runnable) {
            RequestAttributes context =
                    RequestContextHolder.currentRequestAttributes();
            Map<String, String> contextMap = MDC.getCopyOfContextMap();
            return () -> {
                try {
                    RequestContextHolder.setRequestAttributes(context);
                    MDC.setContextMap(contextMap);
                    runnable.run();
                } finally {
                    MDC.clear();
                    RequestContextHolder.resetRequestAttributes();
                }
            };
        }
    }
    

    이 데코레이터를 태스크 실행자에 추가하려면 구성 루틴에 추가해야합니다.

    @Override
    @Bean
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor poolExecutor = new ThreadPoolTaskExecutor();
        poolExecutor.setTaskDecorator(new ContextCopyingDecorator());
        poolExecutor.initialize();
        return poolExecutor;
    }
    

    추가 소유자 또는 사용자 정의 스레드 풀 태스크 실행 프로그램이 필요하지 않습니다.

  4. from https://stackoverflow.com/questions/23732089/how-to-enable-request-scope-in-async-task-executor by cc-by-sa and MIT license