Java 8之后的那些新特性(四):网络请求 Java Http Client「终于解决」

Java (71) 2023-04-13 12:12

Hi,大家好,我是编程小6,很荣幸遇见你,我把这些年在开发过程中遇到的问题或想法写出来,今天说一说Java 8之后的那些新特性(四):网络请求 Java Http Client「终于解决」,希望能够帮助你!!!。

在今天这个编程时代,无时无刻不需要与网络打交道。因此,一个良好的网络请求框架是编程中必不可少的一个类库了。

而在Java 8占据主流的时代中,做为Java 程序员,最喜欢或使用的最多的几个网络类库分别应该是:

  • Apache Http Client
  • Spring RestTemplate

当然,JDK本身也有一个HttpUrlConnection的类,是用来做网络请求的。

但你知道么,在JDK 11的时候,Java引进了一个新的Java Http Client网络请求的新特性。

这又是怎么一回事呢?

这一次,我来聊聊Java 8之后的新特性,网络请求Java Http Client。本篇是Java 8之后的那些新特性系列的第四篇,这个系列的其它几篇文章分别是:

  1. Java 8之后的那些新特性(一):局部变量var
  2. Java 8之后的那些新特性(二):文本块 Text Blocks
  3. Java 8之后的那些新特性(三):Java System Logger

HttpUrlConnection

首先,来说下HttpUrlConnection这个JDK自带的网络请求实现。

事实上,直接使用HttpUrlConnection的应该比较少,HttpUrlConnection非常基础,封装不够,这使得基于HttpUrlConnection来做一个简单的GET请求你都得从头开始写一大堆代码。

    @Test
    void testHttpURLConnection() throws Exception{
        var url = new URL("https://taoofcoding.tech");
        var con = (HttpURLConnection) url.openConnection();
        con.setRequestMethod("GET");
        var in = new BufferedReader(
                new InputStreamReader(con.getInputStream()));
        String inputLine;
        StringBuffer content = new StringBuffer();
        while ((inputLine = in.readLine()) != null) {
            content.append(inputLine);
        }
        in.close();
        Assertions.assertNotNull(content);
    }

这还是简单的GET请求,你可以想像下其它POST,PUT等这些需要传递大量BODY内容是需要写多少这样类似的代码。

而且HttpUrlConnection还有很多其它问题,比如不支持HTTP/2,不支持异步响应式网络请求等。这使得它真正应用并不多。

第三方类库

由于HttpUrlConnection的缺点,于是更优雅的类库来补充就成为一种必然的事。而在这些第三方类库中,以Apache Http Client与Spring的RestTemplate两个最为流行。

Apache是因为其在Java生态中一贯的影响力与质量,它提供了非常多的对JDK的补充类库,而且质量极高。而RestTemplate则是搭乘Spring这股春风,由于Spring的不断流行,使得RestTemplate也跟着流行起来了。

我们来看下Apache Http Client与RestTemplate的代码是怎么样的。

Apache Http Client

    @Test
   //apache http client旧有模式
    void testApacheHttpClient() throws Exception {
        try(var httpclient = HttpClients.createDefault()){
            var httpGet = new HttpGet("https://taoofcoding.tech");
            try(var response  = httpclient.execute(httpGet)){
                var content = EntityUtils.toString(response.getEntity());
                Assertions.assertNotNull(content);
            }
        }
    }

    @Test
    //apache httpe client Fluent API模式
    void testApacheFluentHttpClient() throws IOException {
        var content = Request.get("https://taoofcoding.tech")
                .execute().returnContent();
        Assertions.assertNotNull(content);
    }

RestTemplate

    @Test
    void testRestTemplate(){
        var restTemplate = new RestTemplate();
        var response = restTemplate.getForEntity("https://taoofcoding.tech",String.class);
        var content = response.getBody();
        Assertions.assertNotNull(content);
    }

从上面这个简单的示例代码中,可以看到,http client也好,restTemplate也好,比起HttpUrlConnection都要简洁很多。

而且,从功能上,这些第三方类库在以下功能点上也远优于HttpUrlConnection

  • 都提供了对HTTP/2的支持
  • Apache Http Client支持异步网络请求(响应式网络请求),Spring 5则提供了WebClient来支持响应式网络请求
  • 请求连接池管理等能力

而从简洁度与功能完善度上来说,Apache Http Client更佳,特别是5之后它提供了Fluent Api,使得代码更简洁与优雅了。

Java Http Client

好吧,我们回到这篇文章的主题上,Apache Http Client是个很优秀的类库,这是没有太多疑问的,但这就如同日志功能一样,它毕竟不属于JDK级别的。

JAVA语言一直在不断的改进,它也知道旧的HttpUrlConnection是比较难用,而且非常罗嗦的。于是在Java 11的时候,Java引进了新的Java Http Client,用来取代旧有的HttpUrlConnection。

Java Http Client的特点是:

  • 它支持HTTP/2
  • 它支持同步及异步网络请求两种模式
  • 它使用的Fluent方式来设置各种网络请求参数

我们用最新的Java Http Client来重写上面的示例。

    @Test
    //java http client同步模式
    void testJavaHttpClient() throws IOException, InterruptedException {
        var client = HttpClient.newHttpClient();
        var request = HttpRequest.newBuilder()
                .uri(URI.create("https://taoofcoding.tech"))
                .timeout(Duration.ofMinutes(1))
                .build();
        var response = client.send(request, HttpResponse.BodyHandlers.ofString());
        Assertions.assertNotNull(response.body());
    }

    @Test
    //java http client异步模式
    void testAsyncJavaHttpClient() throws ExecutionException, InterruptedException {
        var client = HttpClient.newHttpClient();
        var request = HttpRequest.newBuilder()
                .uri(URI.create("https://taoofcoding.tech/"))
                .timeout(Duration.ofMinutes(1))
                .build();

        var completableFuture = client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
                .thenApply(response -> { System.out.println(response.statusCode());
                    return response; } )
                .thenApply(HttpResponse::body)
                .thenAccept(System.out::println);

        completableFuture.get();
    }

从代码上可以明显看到,它与Java Http Client在简洁与优雅已经差不多在同一个级别了。

但在功能丰富度上,仍然比不上Apache Http Client,比如没有请求连接池管理等。

优势与缺点

和在上一篇文章中说的Java System Logger一样,JDK级别的东西的一个最大的优势是你不需要再多依赖一个第三方库,并且JDK级别的功能你可以期望它是稳定的,一直被维护的。

一旦JDK有的东西,实现上又很简洁与优雅,就没有太多理由再去用第三方类库。

毕竟,依赖任何一个第三方类库都存在依赖传递的问题,你依赖的东西可能依赖另一些类库,这些不同的依赖会给代码增加依赖方面的复杂度。而使用JDK级别的就没有此担忧了。

还是说缺点吧。

从我的使用过程中看,这个Java Http Client缺少了处理网络请求中一个非常有用的点,就是对响应结果的封装处理。

我在这里用基于restTemplate的代码示例来说明。

    @Test
    void testRegisterUser(){
       //我们直接将结果转换为UserVO对象了
        ResponseEntity<UserVO> registerResponse = restTemplate.exchange(
                baseUrl()+"/v1/users/register", 
                HttpMethod.PUT,
                createHttpEntityFromString(randomRegisterUser()),
                UserVO.class);
        Assertions.assertTrue(registerResponse.getStatusCode().is2xxSuccessful());
    }

每个网络请求,我们拿到结果后,期望的并不会是一个String或inputStream这样的结果,而是期望将这个String或InputStream转化为一个具体我们期望的类。

比如上述代码中,我们期望返回直接就是UserVO,而不是String或一个InputStream。

好在第三方类库在这方面都支持非常到位。

但Java Http Client则默认你只能期望将结果转换为以下类型.

HttpResponse.BodyHandlers::ofByteArray()
HttpResponse.BodyHandlers::ofString()
HttpResponse.BodyHandlers::ofFile(Path)
HttpResponse.BodyHandlers::discarding()

当然,你可以自己实现对应的接口,来转换为具体的类。但这也意味着我们得额外多做一步。

那我们思考下,为什么Java Http Client没有提供这么一个有用的功能?

原因就是:

JDK不能依赖第三方类库,而JDK本身提供任何功能都是非常谨慎的

把一个JSON字符转换为具体的类,这个能力JDK本身并没有提供,所以Java Http Client当然做不到这一点。

而对于第三方类库来说,它可以自行实现这个功能或再多依赖一个第三方框架来实现这个功能就好了。

所以,JDK级别的能力一定是最可靠的,这也是为什么我认为我们要非常关注JDK级别的新特性的原因所在。

关于Java Http Client的就聊到这了,未来你会愿意使用Java Http Client么?

对我而言,这是没有任何疑问的选择。

好了,下周我再继续和大家聊一聊Java 8之后的新特性。

发表回复