我们的申请中有一个非常奇怪的反应。
该应用程序有一个(单例)服务 - 让我们称之为singletonService
。
以及在会话范围内运行的其他服务。 sessionService
。
singletonService
有一个类似CompletableFuture<String> longTask(String param)
的方法,经过长期任务后,singletonService
必须调用sessionService
(方法String transform(String param)
)
当我们像示例1一样编写longTask()
时,一切正常。
transform
方法按预期运行。
public CompletableFuture<String> longTask(String param) {
CompletableFuture<String> future = startLongTask(param);
future.thenApply(sessionService::transform);
return future;
}
public CompletableFuture<String> longTask(String param) {
CompletableFuture<String> future = startLongTask(param);
future.thenApplyAsync(sessionService::transform, asyncExecutor);
return future;
}
但是通过这种方式,我们不会等待transform
方法。
最好像示例2一样编写它。
public CompletableFuture<String> longTask(String param) {
return startTaskLongTask(param).thenApply(sessionService::transform);
}
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)
。该函数使用与会话相关的数据(当前用户的语言环境)。
答案 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));
}