当前位置: 首页 > 知识库问答 >
问题:

如何在异步任务执行器中启用请求作用域

唐阳飙
2023-03-14

在我的应用程序中,我有一些异步web服务。服务器接受请求,返回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.

因为它在SimpleAsynctaskeExecutor中运行,而不是在DispatcherServlet中运行

我的异步处理请求

taskExecutor.execute(new Runnable() {

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

其中,taskExecutor是:

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

共有3个答案

有品
2023-03-14

无法在子异步线程中获取请求范围的对象,因为原始父请求处理线程可能已经将响应提交给客户端并且所有请求对象都被销毁。处理此类场景的一种方法是使用自定义范围,例如SimpleThreadScope。

SimpleThreadScope的一个问题是,子线程不会继承父作用域变量,因为它在内部使用SimpleThreadLocal。为了克服这个问题,实现一个与SimpleThreadScope完全相似的自定义范围,但在内部使用InheritableThreadLocal。有关更多信息,请参阅今春注册MVC:如何在派生线程中使用请求范围的bean?

戚飞
2023-03-14

最简单的方法是使用这样的任务装饰器

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;
}

不需要额外的持有者或自定义线程池任务执行器。

2021的一个小更新:使用当前版本的Spring Boot,仅仅存在一个类型为TaskDecorator的bean就足够了。创建上下文后,任务装饰器将用于装饰Spring Boot创建的执行器。

柯曜文
2023-03-14

我们遇到了同样的问题——需要使用@Async在后台执行代码,因此它无法使用任何Session-或Request estScope bean。我们通过以下方式解决了它:

  • 创建一个自定义TaskPoolExecitor,用于存储任务的范围信息
  • 创建一个特殊的Callable(或Runnable),它使用信息来设置和清除后台线程的上下文
  • 创建覆盖配置以使用自定义执行程序

注意:这只适用于会话和请求范围的bean,而不适用于安全上下文(如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();
    }
}
 类似资料:
  • 问题内容: 在我的应用程序中,我有一些异步Web服务。服务器接受请求,返回OK响应,并开始使用AsyncTaskExecutor处理请求。我的问题是如何在此处启用请求范围,因为在此处理中,我需要获取由以下内容注释的类: 现在我得到异常: 因为它在SimpleAsyncTaskExecutor而不是在DispatcherServlet 我的请求异步处理 taskExecutor在哪里: 问题答案:

  • 这是在一次Android采访中被问到的。有人问我是否可以从异步任务 1 的 doInBackground() 方法(让它成为 Task1)启动另一个异步任务(让它成为 Task2)。我浏览了文档,其中说了以下内容: 必须在UI线程上创建任务实例。 必须在 UI 线程上调用 execute(Params...)。 根据这些陈述,我认为从另一个任务的后台方法启动一个任务是不可能的。此外,async任务

  • 在Server程序中如果需要执行很耗时的操作,比如一个聊天服务器发送广播,Web服务器中发送邮件。如果直接去执行这些函数就会阻塞当前进程,导致服务器响应变慢。 Swoole提供了异步任务处理的功能,可以投递一个异步任务到TaskWorker进程池中执行,不影响当前请求的处理速度。 程序代码 基于第一个TCP服务器,只需要增加onTask和onFinish 2个事件回调函数即可。另外需要设置task

  • Spring留档声明,即使要执行同步超文本传输协议调用,我们也必须从RestTemboard切换到。 目前,我有以下代码: 当然,我可以在这里使用CountdownLatch,但它看起来像是API滥用。 如何执行同步请求?

  • 问题内容: 我的服务器上托管了一个json文件。当我尝试向json文件发出Ajax“ GET”请求时,它失败。 请参阅Safari中的控制台,其中显示“无法加载资源”。 Firebug显示“ 200 OK”,但未显示响应。甚至Firebug也不会显示JSON标签。 我相信这是因为不允许使用AJAX进行跨域请求。 我想知道如何克服这个问题?另外,如果要在服务器上启用跨域请求,则认为需要创建文件或其他

  • 问题内容: 我正在Flask中编写一个应用程序,除了同步和阻塞之外,它的运行情况非常好。我特别有一项任务,该任务调出第三方API,该任务可能需要几分钟才能完成。我想拨打该电话(实际上是一系列电话)并使其运行。同时控制权返回给Flask。 我的看法如下: 现在,我要做的就是 运行并提供在方法返回时要执行的回调,而Flask可以继续处理请求。这是我需要Flask异步运行的唯一任务,并且我想就如何最好地