Использование bean-объекта области действия вне реального веб-запроса
У меня есть веб-приложение, в котором есть логика Spring Integration, работающая с ним в отдельном потоке. Проблема заключается в том, что в какой-то момент моя логика Spring Integration пытается использовать 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>
Мой Scoped Bean помечается так (поскольку я слышал, что проксирование моего компонента поможет):
@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class TenantContext implements Serializable {
Возможно ли то, что я делаю? Если да, то что мне здесь не хватает? Если нет, то есть еще какие-нибудь предложения о том, как мне этого добиться?
3 ответа
Вы можете использовать bean-объекты, ограниченные запросом (и сессией), в потоке веб-контейнера, в котором выполняется запрос.
Я предполагаю, что поток ожидает асинхронного ответа от вашего потока SI?
Если это так, вы можете привязать bean-объект в области запроса к сообщению, возможно, в заголовке или где-то в полезной нагрузке.
Для пружинного чехла 2.4 и пружинного каркаса 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();
Для Spring 4 Frameworks добавьте servletContext.addListener(новый 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());
}**
}
Используйте RequestContextFilter со свойством threadContextInheritable, установленным в true. Это заставляет дочерний поток наследовать родительский контекст, который содержит сам объект запроса. Также убедитесь, что исполнитель не повторно использует потоки в пуле, потому что объект запроса очень специфичен для этого запроса и не может быть разделен между различными запросами. Одним из таких исполнителей является SimpleAsyncTaskExecutor.
Для получения дополнительной информации см. Область "сессия" не активна для текущего потока; IllegalStateException: не найден связанный с потоком запрос.
Вы можете опубликовать запрос в новом потоке следующим образом:
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-переменную, содержащую запрос. Теперь вы можете успешно выполнить автоматическую пересылку вашего запроса.