呼叫我,或异步 REST

位置:首页>文章>详情   分类: Java教程 > 编程技术   阅读(231)   2024-01-13 15:34:57

本文是使用 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

相关阅读

Java HashSet 教程展示了如何使用 Java HashSet 集合。 Java哈希集 HashSet 是一个不包含重复元素的集合。此类为基本操作(添加、删除、包含和大小)提供恒定时间性...
SpringApplicationBuilder 教程展示了如何使用 SpringApplicationBuilder 创建一个简单的 Spring Boot 应用程序。 春天 是用于创建企业应...
通道是继 buffers 之后 java.nio 的第二个主要新增内容,我们在之前的教程中已经详细了解了这一点。通道提供与 I/O 服务的直接连接。 通道是一种在字节缓冲区和通道另一端的实体(通...
课程大纲 Elasticsearch 是一个基于 Lucene 的搜索引擎。它提供了一个分布式的、支持多租户的全文搜索引擎,带有 HTTP Web 界面和无模式的 JSON 文档。 Elasti...
解析器是强大的工具,使用 ANTLR 可以编写可用于多种不同语言的各种解析器。 在这个完整的教程中,我们将: 解释基础:什么是解析器,它可以用来做什么 查看如何设置 ANTLR 以便在 Java...
Java 是用于开发各种桌面应用程序、Web 应用程序和移动应用程序的最流行的编程语言之一。以下文章将帮助您快速熟悉 Java 语言,并迈向 API 和云开发等更复杂的概念。 1. Java语言...
Java中的继承是指子类继承或获取父类的所有非私有属性和行为的能力。继承是面向对象编程的四大支柱之一,用于提高层次结构中类之间的代码可重用性。 在本教程中,我们将了解 Java 支持的继承类型,...
Java Message Service 是一种支持正式通信的 API,称为 网络上计算机之间的消息传递。 JMS 为支持 Java 程序的标准消息协议和消息服务提供了一个通用接口。 JMS 提...
之前,我介绍了spring 3 + hibernate 集成 示例和struts 2 hello world 示例。在本教程中,我将讨论在将 spring 框架与 struts 与 hibern...
Java 项目中的一项常见任务是将日期格式化或解析为字符串,反之亦然。解析日期意味着你有一个代表日期的字符串,例如“2017-08-3”,你想把它转换成一个代表 Java 中日期的对象,例如Ja...