Использование bean-компонента с ограничением области действия вне реального веб-запроса

У меня есть веб-приложение с логикой интеграции Spring, работающей с ним в отдельном потоке. Проблема в том, что в какой-то момент моя логика интеграции Spring пытается использовать bean-компонент запроса, а затем я получаю следующие ошибки:

Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'scopedTarget.tenantContext': 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.


Caused by: 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.

У меня установлен ContextLoaderListener:

<listener>
    <listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>

Мой компонент с областью действия имеет такую ​​аннотацию (поскольку я слышал, что проксирование моего компонента может помочь):

@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)  
public class TenantContext  implements Serializable {

Возможно ли то, что я делаю? Если да, то что мне здесь не хватает? Если нет, есть ли другие предложения о том, как я могу этого добиться?


person Diego Urenia    schedule 02.07.2013    source источник
comment
Вы пытаетесь запустить код после того, как запрос был обработан, или запрос ожидает асинхронной обработки?   -  person OrangeDog    schedule 21.07.2016
comment
@OrangeDog, на вопрос был дан ответ, и ответ уже принят. Еще в 2013 году я многого не знал, а теперь понимаю, что это была ошибка новичка, но все равно спасибо.   -  person Diego Urenia    schedule 21.07.2016
comment
Я должен был прочитать ваши комментарии там, чтобы получить ответ. Тогда тебе не нужен мой ответ о том, как ты вообще можешь это сделать?   -  person OrangeDog    schedule 21.07.2016


Ответы (5)


Вы можете использовать bean-компоненты с областью запроса (и сеанса) только в потоке веб-контейнера, в котором выполняется запрос.

Я предполагаю, что этот поток ожидает асинхронного ответа от вашего потока SI?

Если это так, вы можете привязать bean-компонент с областью действия запроса к сообщению, возможно, в заголовке или где-нибудь в полезной нагрузке.

person Gary Russell    schedule 02.07.2013
comment
Спасибо за ответ. На самом деле я не думаю, что этот поток ожидает ответа, поскольку я просто пытаюсь сохранить объект, и для этого мне нужна информация из этого bean-компонента TenantContext. - person Diego Urenia; 02.07.2013
comment
Тогда вы вообще не сможете использовать bean-компонент запроса, потому что, по определению, запрос больше не существует. - person Gary Russell; 02.07.2013
comment
Большое спасибо, Гэри, тогда я найду другой способ. - person Diego Urenia; 02.07.2013
comment
В документации RequestContextListener описывается как [A] listener that exposes the request to the current thread. Это кажется противоречащим тому, что вы описали в своем ответе. Не могли бы вы уточнить? - person Jon; 22.01.2014
comment
В этом контексте текущий поток означает поток контейнера сервлета, обрабатывающий HTTP-запрос, а не какой-то произвольный поток, которому вы передаете запрос. - person Gary Russell; 22.01.2014

Для Spring 4 Frameworks добавьте servletContext.addListener (new RequestContextListener ());

public class WebApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[] { RootConfiguration.class };
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[] { WebMvcConfiguration.class };
    }

    @Override
    protected String[] getServletMappings() {
        return new String[] { "/" };
    }

    @Override
    protected Filter[] getServletFilters() {
        return new Filter[] { new HiddenHttpMethodFilter() };
    }

    **@Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        super.onStartup(servletContext);
        servletContext.addListener(new RequestContextListener());
    }**
}
person Deepak    schedule 04.06.2015
comment
Для Spring 3 добавьте это в свой web.xml ‹listener› ‹listener-class› org.springframework.web.context.request.RequestContextListener ‹/listener-class› ‹/listener› - person Deepak; 04.06.2015
comment
Ответ цитировался несколько раз ... Я не пробовал, но не вижу причин, почему он должен работать для созданного потока, поскольку я полностью согласен с предоставленной аргументацией здесь. - person dma_k; 31.03.2016

Используйте RequestContextFilter со свойством threadContextInheritable, установленным в значение true. Это заставляет дочерний поток наследовать родительский контекст, который содержит сам объект запроса. Также убедитесь, что исполнитель не использует повторно потоки в пуле, потому что объект запроса очень специфичен для этого запроса и не может использоваться совместно с различными запросами. Одним из таких исполнителей является SimpleAsyncTaskExecutor.

Для получения дополнительной информации см. Область «сеанс» не активна для текущего потока; IllegalStateException: запроса с привязкой к потоку не найдено.

person Thilak    schedule 15.10.2014

Для spring -boot 2.4 и spring framework 5 оба RequestContextFilter и RequestContextListener у меня не работали.

Покопавшись в коде, я обнаружил, что DispatcherServlet перезапишет inheritable из RequestContextHolder, установленного RequestContextFilter или любым другим, см. DispatcherServlet.processRequest и DispatcherServlet.initContextHolders.

Итак, решение довольно простое, без каких-либо других компонентов:

@Configuration
class whateverNameYouLike {
   @Bean
   DispatcherServlet dispatcherServlet() {
       DispatcherServlet srvl = new DispatcherServlet();
       srvl.setThreadContextInheritable(true);
       return srvl;
   }
}

Но обратите внимание, что само решение применяется только к новым потокам, созданным текущим потоком запроса, а не к какому-либо пулу потоков.

Для случаев пула потоков вы можете положиться на дополнительный класс-оболочку :

public class InheritableRequestContextTaskWrapper {
    private Map parentMDC = MDC.getCopyOfContextMap();
    private RequestAttributes parentAttrs = RequestContextHolder.currentRequestAttributes();

    public <T, R> Function<T, R> lambda1(Function<T, R> runnable) {
        return t -> {
            Map orinMDC = MDC.getCopyOfContextMap();
            if (parentMDC == null) {
                MDC.clear();
            } else {
                MDC.setContextMap(parentMDC);
            }

            RequestAttributes orinAttrs = null;
            try {
                orinAttrs = RequestContextHolder.currentRequestAttributes();
            } catch (IllegalStateException e) {
            }
            RequestContextHolder.setRequestAttributes(parentAttrs, true);
            try {
                return runnable.apply(t);
            } finally {
                if (orinMDC == null) {
                    MDC.clear();
                } else {
                    MDC.setContextMap(orinMDC);
                }
                if (orinAttrs == null) {
                    RequestContextHolder.resetRequestAttributes();
                } else {
                    RequestContextHolder.setRequestAttributes(orinAttrs, true);
                }
            }
        };
    }
}

А потом используйте это так :

InheritableRequestContextTaskWrapper wrapper = new InheritableRequestContextTaskWrapper();
List<String> res = pool.submit(() -> ids.parallelStream().map(
    wrapper.lambda1((String id) -> {
        try {
           // do something and return the result string
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("Error occurred in async tasks", e);
        }
    })).collect(Collectors.toList())).get();

person uqb    schedule 16.07.2020

Вы можете опубликовать запрос в новом потоке следующим образом:

import org.springframework.web.context.request.RequestContextListener;
 ...
ServletRequestEvent requestEvent = new ServletRequestEvent(req.getServletContext(), req);
RequestContextListener requestContextListener = new RequestContextListener();
requestContextListener.requestInitialized(requestEvent);
 ...
requestContextListener.requestDestroyed(requestEvent);

Если вы заглянете внутрь requestInitialized()-method, вы найдете ThreadLocal-переменную, содержащую запрос. Теперь вы можете успешно выполнить автоматическую пересылку вашего запроса.

person Grim    schedule 01.10.2020