本文是使用 Spring Boot + Java 8 构建的异步 REST 应用程序的一个非常简单的示例。Spring Boot 使 Web 应用程序的开发变得异常简单,但为了进一步简化任务,我采用了来自 Spring 存储库的示例名为 rest-service ,将其分叉到我自己的存储库 并根据我的目的更改它以创建两个应用程序:客户端和服务器。
我们的服务器应用 将是一个简单的 REST 网络服务,它将查询 GitHub 以获取一些用户数据并将其返回。我们的客户端应用也将是一个 REST 网络服务……它将查询第一个应用!
server 代码主要由服务和控制器组成。 服务 使用带有@Async 注释的异步方法,如下所示。
@Service public class GitHubLookupService { private static final Logger logger = LoggerFactory.getLogger(GitHubLookupService.class); private final RestTemplate restTemplate; public GitHubLookupService(RestTemplateBuilder restTemplateBuilder) { this.restTemplate = restTemplateBuilder.build(); } @Async CompletableFuture<User> findUser(String user) throws InterruptedException { logger.info("Looking up " + user); String url = String.format("https://api.github.com/users/%s", user); User results = restTemplate.getForObject(url, User.class); // Artificial delay of 1s for demonstration purposes Thread.sleep(1000L); return CompletableFuture.completedFuture(results); } }
服务器控制器:
@RestController public class GitHubController { private final GitHubLookupService lookupService; @Autowired public GitHubController(GitHubLookupService lookupService) { this.lookupService = lookupService; } @RequestMapping("/user/{name}") public CompletableFuture<TimedResponse<User>> findUser(@PathVariable(value = "name") String name) throws InterruptedException, ExecutionException { long start = System.currentTimeMillis(); ServerResponse response = new ServerResponse(Thread.currentThread().getName()); return lookupService.findUser(name) .thenApply(user -> { response.setData(user); response.setTimeMs(System.currentTimeMillis() - start); response.setCompletingThread(Thread.currentThread().getName()); return response; }); } }
我们这里有一个来自 Java 8 的简单 CompletableFuture,我们借助 thenApply() 将其转换为我们需要的格式,它允许我们添加一些关于当前的数据thread 以确保执行确实是异步发生的,也就是说,完成工作的线程不是开始工作的线程。我们可以确定它,运行应用程序并检查调用结果:
marina@Marinas-MacBook-Pro:~$ http http://localhost:8080/user/mchernyavskaya HTTP/1.1 200 Content-Type: application/json;charset=UTF-8 Date: Mon, 02 Oct 2017 18:07:54 GMT Transfer-Encoding: chunked { "completingThread": "SimpleAsyncTaskExecutor-1", "data": { "avatar_url": "https://avatars2.githubusercontent.com/u/538843?v=4", "company": "OLX", "location": "Berlin, Germany", "name": "Maryna Cherniavska", "url": "https://api.github.com/users/mchernyavskaya" }, "error": false, "startingThread": "http-nio-8080-exec-1", "timeMs": 2002 }
现在我们需要创建一个客户端应用,它将调用服务器应用。在 Spring 中有一个非常方便使用 REST 的类,叫做 RestTemplate。然而,RestTemplate 是同步的,我们在服务器应用程序中发生的所有漂亮的异步处理对客户端应用程序根本没有帮助。这两个应用程序是完全独立的。客户端应用程序只知道它将处理一个运行时间相当长的调用。由于客户端应用程序知道这一点,并且由于它可能不想在服务器应用程序查询的整个过程中占用线程,我们也将使其异步。 AsyncRestTemplate 来拯救!
我们的客户端应用程序将更加简单,主要由控制器代码组成。要在一台本地计算机上运行这两个应用程序,我们需要使用 -Dserver.port=8082 参数更改服务器的端口。所以,我们的服务器现在在 localhost:8080 上,客户端在 localhost:8082 上。
客户端控制器主要如下。
@RestController public class GitHubController { private static final String BASE_URL = "http://localhost:8080/"; private final AsyncRestTemplate asyncRestTemplate = new AsyncRestTemplate(); @RequestMapping("/async/user/{name}") public ListenableFuture<ClientResponse> findUserAsync(@PathVariable(value = "name") String name) throws InterruptedException, ExecutionException { long start = System.currentTimeMillis(); ClientResponse clientResponse = new ClientResponse(Thread.currentThread().getName()); ListenableFuture<ResponseEntity<ServerResponse>> entity = asyncRestTemplate.getForEntity(BASE_URL + name, ServerResponse.class); entity.addCallback(new ListenableFutureCallback<ResponseEntity<ServerResponse>>() { @Override public void onFailure(Throwable ex) { clientResponse.setError(true); clientResponse.setCompletingThread(Thread.currentThread().getName()); clientResponse.setTimeMs(System.currentTimeMillis() - start); } @Override public void onSuccess(ResponseEntity<ServerResponse> result) { clientResponse.setData(result.getBody()); clientResponse.setCompletingThread(Thread.currentThread().getName()); clientResponse.setTimeMs(System.currentTimeMillis() - start); } }); } }
我们正在获取服务器响应并将其包装到更多关于时间和当前线程的数据中,以便更好地了解正在发生的事情。 AsyncRestTemplate 为我们提供了一个 ListenableFuture,但我们从中创建了一个 CompletableFuture,因为它允许我们手动控制未来返回的时刻,还转换过程中的输出。
当我们调用客户端服务时,它返回以下数据:
marina@Marinas-MacBook-Pro:~$ http http://localhost:8082/async/user/mchernyavskaya HTTP/1.1 200 Content-Type: application/json;charset=UTF-8 Date: Mon, 02 Oct 2017 18:28:36 GMT Transfer-Encoding: chunked { "completingThread": "SimpleAsyncTaskExecutor-1", "data": { "completingThread": "SimpleAsyncTaskExecutor-3", "data": { "avatar_url": "https://avatars2.githubusercontent.com/u/538843?v=4", "company": "OLX", "location": "Berlin, Germany", "name": "Maryna Cherniavska", "url": "https://api.github.com/users/mchernyavskaya" }, "error": false, "startingThread": "http-nio-8080-exec-7", "timeMs": 1403 }, "error": false, "startingThread": "http-nio-8082-exec-3", "timeMs": 1418 }
您可以在此处更多参考有关 Spring 中的异步方法的信息,但这个简单的示例应该可以帮助您了解其工作原理。完整代码在存储库中。希望它有点用!
标签2: Java教程地址:https://www.cundage.com/article/jcg-call-asynchronous-rest.html