Spring + SessionScope + CompletableFuture =错误

时间:2018-06-12 10:00:02

标签: java spring

我们的申请中有一个非常奇怪的反应。 该应用程序有一个(单例)服务 - 让我们称之为singletonService。 以及在会话范围内运行的其他服务。 sessionService

singletonService有一个类似CompletableFuture<String> longTask(String param)的方法,经过长期任务后,singletonService必须调用sessionService(方法String transform(String param)

当我们像示例1一样编写longTask()时,一切正常。 transform方法按预期运行。

例1.1

public CompletableFuture<String> longTask(String param) {
    CompletableFuture<String> future = startLongTask(param);
    future.thenApply(sessionService::transform);
    return future;
}

例1.2

public CompletableFuture<String> longTask(String param) {
    CompletableFuture<String> future = startLongTask(param);
    future.thenApplyAsync(sessionService::transform, asyncExecutor);
    return future;
}

但是通过这种方式,我们不会等待transform方法。 最好像示例2一样编写它。

例2.1

public CompletableFuture<String> longTask(String param) {
    return startTaskLongTask(param).thenApply(sessionService::transform);
}

例2.2

public CompletableFuture<String> longTask(String param) {
    return startTaskLongTask(param).thenApplyAsync(sessionService::transform, asyncExecutor);
}

但是示例2总是会异常完成。它会抛出一个org.springframework.beans.factory.BeanCreationException。 整个例外:

  

范围'session'对当前线程无效;考虑   如果您打算引用它,则为该bean定义范围代理   来自单身人士;嵌套异常是java.lang.IllegalStateException:   找不到线程绑定请求:您指的是请求属性吗?   在实际的Web请求之外,或处理外部的请求   原来收到的帖子?如果你实际在里面经营   一个Web请求仍然收到此消息,您的代码可能是   在DispatcherServlet / DispatcherPortlet之外运行:在这种情况下,   使用RequestContextListener或RequestContextFilter来公开   当前的要求。

有人提示,我们必须在哪里搜索问题吗?

添加个人信息

startLongTask()的签名是:private CompletableFuture<String> startLongTask(String param)。 为了简单起见,我用这种方式编写了方法。 实际上,它的改造称为RESTful-api。

asyncExecutor是春天的豆。

/**
 * Creates a context aware {@link Runnable} that wraps the original one.
 *
 * @param task The original {@link Runnable}
 * @return The wrapper
 */
private Runnable getRunnable(final Runnable task) {
    try {
        final RequestAttributes attr = RequestContextHolder.currentRequestAttributes();
        return () -> {
            try {
                RequestContextHolder.setRequestAttributes(attr);
            } catch (final Exception ignored) {
            }
            task.run();
            try {
                RequestContextHolder.resetRequestAttributes();
            } catch (final Exception ignored) {
            }
        };
    } catch (final Exception ignored) {
        return task;
    }
}

transform()的签名是String transform(String param)。该函数使用与会话相关的数据(当前用户的语言环境)。

2 个答案:

答案 0 :(得分:0)

根据您的输入:

示例1(工作井):

public CompletableFuture<String> longTask(String param) {
    CompletableFuture<String> future = startLongTask(param);
    future.thenApply(sessionService::transform);
    return future;
}

示例2(不起作用):

public CompletableFuture<String> longTask(String param) {
    CompletableFuture<String> future = startLongTask(param);
    return future.thenApply(sessionService::transform);        
}

如您所见,这两个示例的唯一区别在于,在示例1中,您将返回CompletableFuture&lt; String&gt;您创建的,在示例2中,您将返回then.Apply()方法;

thenApply方法的Oracle文档:https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletableFuture.html#thenApply-java.util.function.Function-

  

public&lt; U&gt; CompletableFuture&LT; U&GT;然后应用(函数&lt;?super T,?extends   U&GT; fn)

     

从界面复制的说明:CompletionStage返回一个   新的CompletionStage,当这个阶段正常完成时,是   以此阶段的结果作为所提供的参数执行   功能。有关规则,请参阅CompletionStage文档   特殊完成。

     

指定:thenApply在接口CompletionStage&lt; T&gt;中

     

类型参数: U - 函数的返回类型

     

参数: fn - 用于计算值的函数   返回CompletionStage

     

返回: 新的CompletionStage

问题中的错误描述说:

  

如果您实际在网络请求中操作并仍然收到   这条消息,您的代码可能正在运行之外   DispatcherServlet的/ DispatcherPortlet的。

这可能就是这里的原因,thenApply创建的CompletableFuture对象在DispatcherServlet / DispatcherPortlet之外。

希望你现在明白。 :)

答案 1 :(得分:0)

我们找到了解决问题的方法。 Shubham Kadlag问了正确的问题。 :)
sessionService需要来自会话的信息 - &gt;用户的语言环境。 但是为什么我们尝试在longTask()(在其他线程中运行)之后获取语言环境?

现在,我们在longTask()之前获取区域设置,并将其作为参数提供给transform()。因此,transform不再依赖于会话,现在位于singletonService内。 看这个例子:

public CompletableFuture<String> longTask(String param) {
    Locale locale = sessionService.getLocale();
    return startTaskLongTask(param).thenApply(result -> transform(result, locale));
}