DeferredResult例項一:理解Spring的Callable和DeferredResult
1-介紹
Servlet 3中的非同步支援為在另一個執行緒中處理HTTP請求提供了可能性。當有一個長時間執行的任務時,這是特別有趣的,因為當另一個執行緒處理這個請求時,容器執行緒被釋放,並且可以繼續為其他請求服務。
這個主題已經解釋了很多次,Spring框架提供的關於這個功能的類似乎有一點混亂——在一個Controller中返回Callable 和 DeferredResult。
在這篇文章中,我將實施這兩個例子,以顯示其差異。
這裡所顯示的所有示例都包括執行一個控制器,該控制器將執行一個長期執行的任務,然後將結果返回給客戶機。長時間執行的任務由taskservice處理:
@Service public class TaskServiceImpl implements TaskService { private final Logger logger = LoggerFactory.getLogger(this.getClass()); @Override public String execute() { try { Thread.sleep(5000); logger.info("Slow task executed"); return "Task finished"; } catch (InterruptedException e) { throw new RuntimeException(); } } }
這個web應用是用Spring Boot建立的,我們將執行下面的類來執行我們的例子:
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
2-阻塞的Controller
在這個例子中,一個請求到達控制器。servlet執行緒不會被釋放,直到長時間執行的方法被執行,我們退出@requestmapping註釋的方法。
@RestController
public class BlockingController {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final TaskService taskService;
@Autowired
public BlockingController(TaskService taskService) {
this.taskService = taskService;
}
@RequestMapping(value = "/block", method = RequestMethod.GET, produces = "text/html")
public String executeSlowTask() {
logger.info("Request received");
String result = taskService.execute();
logger.info("Servlet thread released");
return result;
}
}
如果我們執行這個例子http://localhost:8080/block,在日誌裡我們會發現servlet request不會被釋放,直到長時間的任務執行完(5秒後)。
2017-04-19 10:28:56,860 INFO (BlockingController.java:23)- Request received
2017-04-19 10:29:01,861 INFO (TaskServiceImpl.java:16)- Slow task executed
2017-04-19 10:29:01,863 INFO (BlockingController.java:25)- Servlet thread released
3-返回Callable
在這個例子中,不是直接返回的結果,我們將返回一個Callable:
@RestController
public class AsyncCallableController {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final TaskService taskService;
@Autowired
public AsyncCallableController(TaskService taskService) {
this.taskService = taskService;
}
@RequestMapping(value = "/callable", method = RequestMethod.GET, produces = "text/html")
public Callable<String> executeSlowTask() {
logger.info("Request received");
Callable<String> callable = taskService::execute;
logger.info("Servlet thread released");
return callable;
}
}
返回Callable意味著Spring MVC將呼叫在不同的執行緒中執行定義的任務。Spring將使用TaskExecutor來管理執行緒。在等待完成的長期任務之前,servlet執行緒將被釋放。
2017-04-19 10:34:35,554 INFO (AsyncCallableController.java:23)- Request received
2017-04-19 10:34:35,559 INFO (AsyncCallableController.java:25)- Servlet thread released
2017-04-19 10:34:40,585 INFO (TaskServiceImpl.java:16)- Slow task executed
你可以看到我們在長時間執行的任務執行完畢之前就已經從servlet返回了。這並不意味著客戶端收到了一個響應。與客戶端的通訊仍然是開放的等待結果,但接收到的請求的執行緒已被釋放,並可以服務於另一個客戶的請求。
4-返回DeferredResult
首先,我們需要建立一個deferredresult物件。此物件將由控制器返回。我們將完成和Callable相同的事,當我們在另一個執行緒處理長時間執行的任務的時候釋放servlet執行緒。
@RestController
public class AsyncDeferredController {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final TaskService taskService;
@Autowired
public AsyncDeferredController(TaskService taskService) {
this.taskService = taskService;
}
@RequestMapping(value = "/deferred", method = RequestMethod.GET, produces = "text/html")
public DeferredResult<String> executeSlowTask() {
logger.info("Request received");
DeferredResult<String> deferredResult = new DeferredResult<>();
CompletableFuture.supplyAsync(taskService::execute)
.whenCompleteAsync((result, throwable) -> deferredResult.setResult(result));
logger.info("Servlet thread released");
return deferredResult;
}
}
所以,返回DeferredResult和返回Callable有什麼區別?不同的是這一次執行緒是由我們管理。建立一個執行緒並將結果set到DeferredResult是由我們自己來做的。
用completablefuture建立一個非同步任務。這將建立一個新的執行緒,在那裡我們的長時間執行的任務將被執行。也就是在這個執行緒中,我們將set結果到DeferredResult並返回。
是在哪個執行緒池中我們取回這個新的執行緒?預設情況下,在completablefuture的supplyasync方法將在forkjoin池執行任務。如果你想使用一個不同的執行緒池,你可以通過傳一個executor到supplyasync方法:
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)
如果我們執行這個例子,我們將得到如下結果:
2017-04-19 10:42:17,092 INFO (TaskServiceImpl.java:16)- Slow task executed
2017-04-19 10:42:23,112 INFO (AsyncDeferredController.java:25)- Request received
2017-04-19 10:42:23,114 INFO (AsyncDeferredController.java:29)- Servlet thread released
5-結論
站在一定高度來看這問題,Callable和Deferredresult做的是同樣的事情——釋放容器執行緒,在另一個執行緒上非同步執行長時間的任務。不同的是誰管理執行任務的執行緒:Callable執行執行緒完畢即返回;Deferredresult通過設定返回物件值(deferredResult.setResult(result));)返回,可以在任何地方控制返回。