我在Spring服务中定义了一个长期运行的任务。它由Spring MVC控制器启动。我想启动服务并在服务结束前将HttpResponse
返回给调用者。该服务最后将文件保存在文件系统上。
在javascript中,我创建了一个轮询工作来检查服务状态。
在Spring 3.2中,我找到了@Async
注释,但我不明白它与DeferredResult
和Callable
的区别。我何时必须使用@Async
,何时应使用DeferredResult
?
答案 0 :(得分:21)
Async 对方法进行注释,以便异步调用它。
@org.springframework.stereotype.Service
public class MyService {
@org.springframework.scheduling.annotation.Async
void DoSomeWork(String url) {
[...]
}
}
所以Spring可以这样做,你需要定义如何执行。例如:
<task:annotation-driven />
<task:executor id="executor" pool-size="5-10" queue-capacity="100"/>
这样,当您调用service.DoSomeWork(“parameter”)时,调用将被放入执行程序的队列中,以便异步调用。这对于可以同时执行的任务很有用。
您可以使用Async执行任何类型的异步任务。如果您想要定期调用任务,可以使用 @Scheduled (并使用task:scheduler而不是task:executor)。它们是调用java Runnables的简化方法。
DeferredResult&lt;&gt; 用于回答请愿,而不会阻止用于回答的Tomcat HTTP线程。通常是ResponseBody注释方法的返回值。
@org.springframework.stereotype.Controller
{
private final java.util.concurrent.LinkedBlockingQueue<DeferredResult<String>> suspendedRequests = new java.util.concurrent.LinkedBlockingQueue<>();
@RequestMapping(value = "/getValue")
@ResponseBody
DeferredResult<String> getValue() {
final DeferredResult<String> result = new DeferredResult<>(null, null);
this.suspendedRequests.add(result);
result.onCompletion(new Runnable() {
@Override
public void run() {
suspendedRequests.remove(result);
}
});
service.setValue(result); // Sets the value!
return result;
}
}
上一个例子缺少一个重要的事情,它没有显示如何设置延迟结果。在其他一些方法(可能是setValue方法)中,会有一个result.setResult(value)。在调用setResult之后,Spring将调用onCompletion过程并返回HTTP请求的答案(参见https://en.wikipedia.org/wiki/Push_technology#Long_polling)。
但是,如果您只是同步执行setValue,则使用延迟结果没有任何优势。这就是Async的用武之地。您可以使用异步方法在将来的某个点使用另一个线程设置返回值。
@org.springframework.scheduling.annotation.Async
void SetValue(DeferredResult<String> result) {
String value;
// Do some time consuming actions
[...]
result.setResult(value);
}
使用延迟结果不需要异步,它只是一种方法。
在该示例中,存在延迟结果队列,例如,可以监视计划任务以处理其待处理请求。您还可以使用一些非阻塞机制(请参阅http://en.wikipedia.org/wiki/New_I/O)来设置返回值。
要完成图片,您可以搜索有关Java标准期货(http://docs.oracle.com/javase/1.5.0/docs/api/java/util/concurrent/Future.html)和callables(http://docs.oracle.com/javase/1.5.0/docs/api/java/util/concurrent/Callable.html)的信息,这些信息有点等同于Spring DeferredResult和Async。
答案 1 :(得分:3)
DeferredResult
利用Servlet 3.0 AsyncContext。当你需要返回结果时,它不会像其他人那样阻塞线程。
另一大好处是DeferredResult
支持回调。
答案 2 :(得分:2)
您的控制器最终是由servlet容器(我假设它是Tomcat)工作线程执行的功能。您的服务流以Tomcat开头,以Tomcat结束。 Tomcat从客户端获取请求,保留连接,并最终向客户端返回响应。您的代码(控制器或servlet)在中间。
考虑以下流程:
由于Servlet(您的代码)和Servlet容器(Tomcat)是不同的实体,因此要允许此流程(释放tomcat线程,但保持客户端连接),我们需要在their contract中提供此支持,包javax.servlet
,在Servlet 3.0中引入。现在,回到您的问题,当控制器的返回值为DeferredResult
或Callable
时,Spring MVC使用新的Servlet 3.0功能,尽管它们是两回事。 Callable
是java.util
的一部分,是一个接口,它是对Runnable
接口的改进(应该由其实例旨在用于由线程执行)。 Callable
允许返回值,而Runnable
不允许返回值。 DeferredResult
是Spring设计的类,用于在Spring MVC中为异步请求处理提供更多选项(我将描述),并且该类仅保存结果(如其名称所暗示)而您的Callable
实现中包含异步代码。因此,这意味着您可以在控制器中同时使用两者,并使用Callable
运行异步代码,并将结果设置为DeferredResult
,这将是控制器的返回值。那么,使用DeferredResult
作为返回值而不是Callable会得到什么呢? DeferredResult
具有内置的回调,例如onError
,onTimeout
和onCompletion
。它使错误处理变得非常容易。此外,由于它只是结果容器,因此您可以选择任何线程(或线程池)在异步代码上运行。使用Callable,您没有此选择。
关于@Async
,这要简单得多–用@Async
注释bean的方法将使其在单独的线程中执行。默认情况下(可以重写),Spring使用SimpleAsyncTaskExecutor
实际异步运行这些方法。
最后,如果要在执行繁重的处理时释放Tomcat线程并保持与客户端的连接,则控制器应返回Callable
或DeferredResult
。否则,您可以在以@Async
注释的方法上运行代码。