Java Demo示例:Spring WebClient 、RestTemplate、JDK11的HttpClient等http调用方式比较

本文对比了Spring的RestTemplate、WebClient和JDK11的HttpClient在Web应用中的性能和特性。RestTemplate基于阻塞模型,适合简单场景但高并发下性能下降明显。WebClient则是非阻塞、反应式的,能更高效利用系统资源。JDK11的HttpClient提供同步和异步两种方式,异步方法中的CompletableFuture.get()方法仍然是阻塞的。通过示例代码展示了它们在并发请求时的行为差异。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

        对其他服务进行 HTTP 调用是 Web 应用程序中的常见要求。所以,我们需要一个网络客户端工具。

1、RestTemplate也是阻塞的

        长期以来,Spring 一直提供RestTemplate作为 Web 客户端抽象。在底层,RestTemplate使用 Java Servlet API,它基于每个请求对应一个线程模型(thread-per-request)

        这意味着线程将阻塞,直到 Web 客户端收到响应。阻塞代码的问题是由于每个线程消耗了一些内存和 CPU 周期。

        当应用程序中有大量的请求时,就会有大量的线程和连接。这会给服务器资源带来负担。

        如果服务器速度很慢,用户很快就会开始看到应用程序的性能下降甚至无响应。

2、WebClient 是非阻塞的

        WebClient使用 Spring Reactive 框架提供的异步、非阻塞解决方案。

        RestTemplate为每个事件(HTTP 调用)使用调用者线程,而WebClient将为每个事件创建类似于“任务”的东西。在幕后,Reactive 框架会将这些“任务”排队并仅在适当的响应可用时执行它们。

        Reactive 框架使用事件驱动的架构。它提供了通过Reactive Streams API组合异步逻辑的方法。因此,与同步/阻塞方法相比,反应式方法可以处理更多逻辑,同时使用更少的线程和系统资源。

        WebClient是Spring WebFlux库的一部分。因此,我们还可以使用具有反应类型(Mono和Flux)的功能性、流畅的 API 作为声明性组合来编写客户端代码。

3、JDK11的HttpClient

        JDK11的HttpClient可以发送同步请求、也可以发送异步请求。windows64为的JDK11的下载地址,oracle官方的下载有些慢。

链接:https://pan.baidu.com/s/1LaM0b1qv2eG01ITd6vxg6g 
提取码:tiq4

        同步方法

public abstract <T> HttpResponse<T> send(HttpRequest request, HttpResponse.BodyHandler<T> responseBodyHandler) throws IOException, InterruptedException

        异步方法

public abstract <T> CompletableFuture<HttpResponse<T>> sendAsync(HttpRequest request, HttpResponse.BodyHandler<T> responseBodyHandler)

4、比较示例

        为了演示这三种方法之间的性能差异,我们需要进行多并发客户端请求进行性能测试。

        在一定数量的并行客户端请求之后,我们会看到阻塞方法的性能显着下降。

        但是,无论请求的数量如何,反应式/非阻塞方法都应该提供恒定的性能。

        对于本文,我们将实现三个REST接口,一个使用RestTemplate,另一个使用WebClient,最后一个使用jdk11中的HttpClient。

        首先,我们需要Spring Boot WebFlux 启动器依赖项(建议修改idea的maven远程仓库地址):

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

        下面的代码我们的服务,返回一个list,但是休眠2秒。

private final static Logger log = LoggerFactory.getLogger(UserController.class);
private final String url = "http://127.0.0.1:9001/demo-service";

@GetMapping("/demo-service")
private List<String> getAllTweets() {
    try {
        Thread.sleep(2000L); // delay
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    return Arrays.asList("RestTemplate","WebClient","HttpClient");
}

(1)使用RestTemplate调用服务

        现在让我们实现另一个 REST,它将通过 Web 客户端调用我们的服务。

@GetMapping("/resttemplate-blocking")
public List<String> getResttemplateBlocking() {
    log.info("请求开始");
    RestTemplate restTemplate = new RestTemplate();
    ResponseEntity<List<String>> response = restTemplate.exchange(
            url, HttpMethod.GET, null,
            new ParameterizedTypeReference<List<String>>(){});
    List<String> result = response.getBody();
    log.info("请求完成");
    return result;
}

        当我们调用这个端点时,由于RestTemplate的同步特性,代码将阻塞等待来自我们服务的响应。此方法中的其余代码将仅在收到响应时运行。

        以下是我们将在日志中看到的内容,按时间看确认是阻塞的:

2022-05-10 19:39:07.740  INFO 21088 --- [nio-9001-exec-1] c.h.skydance.controller.UserController   : 请求开始
2022-05-10 19:39:09.799  INFO 21088 --- [nio-9001-exec-1] c.h.skydance.controller.UserController   : 请求完成

(2)使用WebClient调用服务

@GetMapping(value = "/flux-non-blocking", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> getFluxNonBlocking() {
    log.info("Flux请求开始!");
    Flux<String> flux = WebClient.create()
            .get()
            .uri(url)
            .retrieve()
            .bodyToFlux(String.class);
    log.info("Flux请求结束!");
    flux.subscribe(result -> log.info(result.toString()));
    log.info("Flux数据接收完成!");
    return flux;
}

        在这种情况下,WebClient返回一个Flux发布者,方法执行完成。一旦结果可用,发布者将开始向其订阅者发送推文。

        让我们参考代码观察一下日志,确认是非阻塞的,请注意,此endpoint方法在收到响应之前完成。

2022-05-10 19:40:38.551  INFO 21088 --- [nio-9001-exec-9] c.h.skydance.controller.UserController   : Flux请求开始!
2022-05-10 19:40:38.552  INFO 21088 --- [nio-9001-exec-9] c.h.skydance.controller.UserController   : Flux请求结束!
2022-05-10 19:40:38.552  INFO 21088 --- [nio-9001-exec-9] c.h.skydance.controller.UserController   : Flux数据接收完成!
2022-05-10 19:40:40.574  INFO 21088 --- [ctor-http-nio-2] c.h.skydance.controller.UserController   : ["RestTemplate","WebClient","HttpClient"]

(3)使用JDK11的HttpClient的同步方式

@GetMapping(value = "/httpclient-blocking", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public String getHttpclientBlocking() {
    log.info("httpclient send请求开始");
    HttpClient client = HttpClient.newHttpClient();
    HttpRequest request = HttpRequest.newBuilder().uri(URI.create(url)).build();
    HttpResponse<String> response = null;
    try {
        response = client.send(request, HttpResponse.BodyHandlers.ofString());
    } catch (IOException e) {
        e.printStackTrace();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    log.info("httpclient send请求结束");
    return response.body();
}

        查看日志输出,是阻塞式的。

2022-05-10 19:42:27.862  INFO 21088 --- [nio-9001-exec-3] c.h.skydance.controller.UserController   : httpclient send请求开始
2022-05-10 19:42:29.884  INFO 21088 --- [nio-9001-exec-3] c.h.skydance.controller.UserController   : httpclient send请求结束

(4)使用JDK11的HttpClient的异步方式

@GetMapping(value = "/httpclient-non-blocking", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public String getHttpclientAsyncNonBlocking() {
    try {
        log.info("httpclient sendAsync请求开始!");
        HttpClient client = HttpClient.newHttpClient();
        HttpRequest request = HttpRequest.newBuilder().uri(URI.create(url)).build();
        CompletableFuture<String> result = client.sendAsync(request, HttpResponse.BodyHandlers.ofString()).thenApply(HttpResponse::body);
        log.info("httpclient sendAsync请求结束!");
        String arr = result.get();
        log.info("httpclient sendAsync请求结束 打印请求结果:" + arr);
        return arr;
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (ExecutionException e) {
        e.printStackTrace();
    }
    return null;
}

        查看结果日志的顺序,请求是非阻塞的,但是与Flux不同,这里的CompletableFuture的get方法是阻塞的。

2022-05-10 19:43:42.396  INFO 21088 --- [nio-9001-exec-2] c.h.skydance.controller.UserController   : httpclient sendAsync请求开始!
2022-05-10 19:43:42.398  INFO 21088 --- [nio-9001-exec-2] c.h.skydance.controller.UserController   : httpclient sendAsync请求结束!
2022-05-10 19:43:44.404  INFO 21088 --- [nio-9001-exec-2] c.h.skydance.controller.UserController   : httpclient sendAsync请求结束 打印请求结果:["RestTemplate","WebClient","HttpClient"]

        参考代码地址 

java_example/httprequest at main · bashendixie/java_example · GitHubContribute to bashendixie/java_example development by creating an account on GitHub.icon-default.png?t=N7T8https://github.com/bashendixie/java_example/tree/main/httprequest

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

坐望云起

如果觉得有用,请不吝打赏

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值