Servlet 3中的異步支持爲在另外一個線程中處理HTTP請求提供了可能性。當有一個長時間運行的任務時,這是特別有趣的,由於當另外一個線程處理這個請求時,容器線程被釋放,而且能夠繼續爲其餘請求服務。
這個主題已經解釋了不少次,Spring框架提供的關於這個功能的相似乎有一點混亂——在一個Controller中返回Callable 和 DeferredResult。
在這篇文章中,我將實施這兩個例子,以顯示其差別。
這裏所顯示的全部示例都包括執行一個控制器,該控制器將執行一個長期運行的任務,而後將結果返回給客戶機。長時間運行的任務由taskservice處理:html
@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建立的,咱們將執行下面的類來運行咱們的例子:java
@SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
在這個例子中,一個請求到達控制器。servlet線程不會被釋放,直到長時間運行的方法被執行,咱們退出@requestmapping註釋的方法。web
@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秒後)。app
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
在這個例子中,不是直接返回的結果,咱們將返回一個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返回了。這並不意味着客戶端收到了一個響應。與客戶端的通訊仍然是開放的等待結果,但接收到的請求的線程已被釋放,並能夠服務於另外一個客戶的請求。async
首先,咱們須要建立一個deferredresult對象。此對象將由控制器返回。咱們將完成和Callable相同的事,當咱們在另外一個線程處理長時間運行的任務的時候釋放servlet線程。ide
@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方法:this
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)
若是咱們運行這個例子,咱們將獲得以下結果:spa
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
站在必定高度來看這問題,Callable和Deferredresult作的是一樣的事情——釋放容器線程,在另外一個線程上異步運行長時間的任務。不一樣的是誰管理執行任務的線程:Callable執行線程完畢即返回;Deferredresult經過設置返回對象值(deferredResult.setResult(result));)返回,能夠在任何地方控制返回。