Как включить область запроса в async task executor
В моем приложении у меня есть несколько асинхронных веб-сервисов. Сервер принимает запрос, возвращает ответ 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.
поскольку он работает в SimpleAsyncTaskExecutor
, а не в DispatcherServlet
моя асинхронная обработка запроса
taskExecutor.execute(new Runnable() {
@Override
public void run() {
asyncRequest(request);
}
});
где taskExecutor:
<bean id="taskExecutor" class="org.springframework.core.task.SimpleAsyncTaskExecutor" />
Ответы
Ответ 1
Мы столкнулись с одной и той же проблемой - для выполнения кода в фоновом режиме с помощью @Async, поэтому он не смог использовать какой-либо Session- или RequestScope beans. Мы решили это следующим образом:
- Создайте собственный TaskPoolExecutor, который сохраняет информацию с областями с задачами
- Создайте специальный Callable (или Runnable), который использует эту информацию для установки и очистки контекста для фонового потока
- Создайте конфигурацию переопределения для использования пользовательского исполнителя
Примечание: это будет работать только для Session и Request scoped beans, а не для контекста безопасности (как в Spring Безопасность). Вам нужно будет использовать другой метод для настройки контекста безопасности, если это то, что вам нужно.
Примечание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()));
}
}
Callable:
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
Самый простой способ - использовать декоратор задач, например так:
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;
}
Нет необходимости в дополнительном держателе или исполнителе пользовательского пула потоков.
Ответ 3
Невозможно получить объект области запроса в дочернем асинхронном потоке, поскольку исходный родительский поток обработки запросов, возможно, уже передал ответ клиенту, и все объекты запроса уничтожены. Одним из способов обработки таких сценариев является использование настраиваемой области видимости, например SimpleThreadScope.
одна проблема с SimpleThreadScope состоит в том, что дочерние потоки не будут наследовать переменные области видимости родителей, поскольку он использует простой ThreadLocal для внутреннего использования. Чтобы преодолеть это, реализуйте пользовательскую область, которая в точности похожа на SimpleThreadScope, но использует InheritableThreadLocal для внутреннего использования. За дополнительной информацией обращайтесь к Spring MVC: Как использовать bean-объект в области запросов внутри порожденного потока?
Ответ 4
Решения, упомянутые ранее, не помогли мне.
Причина, по которой решение не работает, как упоминалось в посте @Thilak, заключается в том, что как только исходный родительский поток передал ответ клиенту, объекты запроса могут быть собраны сборщиком мусора.
Но с некоторыми изменениями в решении @Armadillo я смог заставить его работать. Я использую весеннюю загрузку 2.2
Вот за чем я следовал.
- Создайте пользовательский TaskPoolExecutor, который хранит (после клонирования) область
информация с заданиями.
- Создать специальный Callable (или Runnable)
который использует клонированную информацию для установки текущих значений контекста
и очистите контекст для асинхронного потока.
Исполнитель (такой же, как в сообщении @Armadillo):
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()));
}
}
Callable:
public class ContextAwareCallable<T> implements Callable<T> {
private Callable<T> task;
private final RequestAttributes requestAttributes;
public ContextAwareCallable(Callable<T> task, RequestAttributes requestAttributes) {
this.task = task;
this.requestAttributes = cloneRequestAttributes(requestAttributes);
}
@Override
public T call() throws Exception {
try {
RequestContextHolder.setRequestAttributes(requestAttributes);
return task.call();
} finally {
RequestContextHolder.resetRequestAttributes();
}
}
private RequestAttributes cloneRequestAttributes(RequestAttributes requestAttributes){
RequestAttributes clonedRequestAttribute = null;
try{
clonedRequestAttribute = new ServletRequestAttributes(((ServletRequestAttributes) requestAttributes).getRequest(), ((ServletRequestAttributes) requestAttributes).getResponse());
if(requestAttributes.getAttributeNames(RequestAttributes.SCOPE_REQUEST).length>0){
for(String name: requestAttributes.getAttributeNames(RequestAttributes.SCOPE_REQUEST)){
clonedRequestAttribute.setAttribute(name,requestAttributes.getAttribute(name,RequestAttributes.SCOPE_REQUEST),RequestAttributes.SCOPE_REQUEST);
}
}
if(requestAttributes.getAttributeNames(RequestAttributes.SCOPE_SESSION).length>0){
for(String name: requestAttributes.getAttributeNames(RequestAttributes.SCOPE_SESSION)){
clonedRequestAttribute.setAttribute(name,requestAttributes.getAttribute(name,RequestAttributes.SCOPE_SESSION),RequestAttributes.SCOPE_SESSION);
}
}
if(requestAttributes.getAttributeNames(RequestAttributes.SCOPE_GLOBAL_SESSION).length>0){
for(String name: requestAttributes.getAttributeNames(RequestAttributes.SCOPE_GLOBAL_SESSION)){
clonedRequestAttribute.setAttribute(name,requestAttributes.getAttribute(name,RequestAttributes.SCOPE_GLOBAL_SESSION),RequestAttributes.SCOPE_GLOBAL_SESSION);
}
}
return clonedRequestAttribute;
}catch(Exception e){
return requestAttributes;
}
}
}
Внесенное мной изменение состоит в том, чтобы ввести cloneRequestAttributes() для копирования и установки атрибута RequestAttribute, чтобы значения оставались доступными даже после того, как исходный родительский поток передал ответ клиенту.
Конфигурация:
Поскольку существуют другие асинхронные конфигурации, и я не хотел, чтобы это поведение было применимо к другим асинхронным исполнителям, я создал свою собственную конфигурацию исполнителя задач.
@Configuration
@EnableAsync
public class TaskExecutorConfig {
@Bean(name = "contextAwareTaskExecutor")
public TaskExecutor getContextAwareTaskExecutor() {
ContextAwarePoolExecutor taskExecutor = new ConAwarePoolExecutor();
taskExecutor.setMaxPoolSize(20);
taskExecutor.setCorePoolSize(5);
taskExecutor.setQueueCapacity(100);
taskExecutor.setThreadNamePrefix("ContextAwareExecutor-");
return taskExecutor;
}
}
И, наконец, в асинхронном методе я использую имя исполнителя.
@Async("contextAwareTaskExecutor")
public void asyncMethod() {
}
Альтернативное решение:
Мы оказались в этой проблеме, пытаясь повторно использовать существующий класс компонентов. Хотя решение заставило его выглядеть так, как будто это удобно. Это намного меньше хлопот (клонирование объектов и резервирование пула потоков), если бы мы могли ссылаться на соответствующие значения области запроса в качестве параметров метода. В нашем случае мы планируем провести рефакторинг кода таким образом, чтобы класс компонента, который использует bean-объект области действия запроса и повторно используется из асинхронного метода, принял значения в качестве параметров метода. Компонент запроса в области видимости удаляется из повторно используемого компонента и перемещается в класс компонента, который вызывает его метод.
Чтобы поместить то, что я только что описал, в коде:
Наше текущее состояние:
@Async("contextAwareTaskExecutor")
public void asyncMethod() {
reUsableCompoment.executeLogic() //This component uses the request scoped bean.
}
Измененный код:
@Async("taskExecutor")
public void asyncMethod(Object requestObject) {
reUsableCompoment.executeLogic(requestObject); //Request scoped bean is removed from the component and moved to the component class which invokes it menthod.
}
Ответ 5
Ответ @Armadillo побудил меня написать реализацию для Runnable.
Пользовательская реализация для TaskExecutor:
/**
* This custom ThreadPoolExecutor stores scoped/context information with the tasks.
*/
public class ContextAwareThreadPoolExecutor extends ThreadPoolTaskExecutor {
@Override
public Future<?> submit(Runnable task) {
return super.submit(new ContextAwareRunnable(task, RequestContextHolder.currentRequestAttributes()));
}
@Override
public ListenableFuture<?> submitListenable(Runnable task) {
return super.submitListenable(new ContextAwareRunnable(task, RequestContextHolder.currentRequestAttributes()));
}
}
Пользовательская реализация для Runnable:
/**
* This custom Runnable class can use to make background threads context aware.
* It store and clear the context for the background threads.
*/
public class ContextAwareRunnable implements Runnable {
private Runnable task;
private RequestAttributes context;
public ContextAwareRunnable(Runnable task, RequestAttributes context) {
this.task = task;
// Keeps a reference to scoped/context information of parent thread.
// So original parent thread should wait for the background threads.
// Otherwise you should clone context as @Arun A answer
this.context = context;
}
@Override
public void run() {
if (context != null) {
RequestContextHolder.setRequestAttributes(context);
}
try {
task.run();
} finally {
RequestContextHolder.resetRequestAttributes();
}
}
}