用IntelliJ和Gradle体验JDK9的新HTTP客户端模块

多年来,JDK内置的HTTP客户端,也就是HttpURLConnection,从没有得到过更新,已经非常老旧,只支持HTTP/1.1、不支持NIO不说,用起来也很麻烦。所以,很多人会使用第三方的HTTP客户端库,比如Apache HttpClient,或是基于Netty的Async Http Client。为了跟上时代,JDK9新增了jdk.incubator.httpclient模块,提供了一个支持HTTP2,WebSocket和Async的HTTP客户端。下载JDK9之后,我第一时间体验了这个新客户端,在这里简单介绍一下,并且记录一下遇到的一些坑。

// 建立Gradle工程

首先,在IntelliJ的向导中创建一个工程。当然了,不是IntelliJ IDEA工程。我习惯用构建工具来管理工程,这样不用IntelliJ的人也能轻松导入。在构建工具中,我比较喜欢Gradle,因为它需要写的字符数比较少。要注意的是,JDK要选择JDK9,而Gradle Distribution选择使用Gradle Wrapper提供的。

创建工程之后,修改gradle/gradle-wrapper.properties里面的Gradle下载链接,替换为最新版的链接;或者也可以运行例如

./gradlew wrapper --gradle-version=4.2.1 --distribution-type=bin

来将Wrapper引用的Gradle版本更新为最新。

然后,修改build.gradle,将sourceCompatibility改成1.9。

// 试用新HTTP客户端API

新的HTTP客户端API主要由三个类组成——HttpClientHttpRequestHttpResponse。下面就写一个简单的小程序来尝试一下。

package io.dante.jdk9.http;

import jdk.incubator.http.HttpClient;
import jdk.incubator.http.HttpRequest;
import jdk.incubator.http.HttpResponse;

import java.net.URI;
import java.nio.charset.Charset;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;

public class JDK9HttpClient {

	public static void main(String[] args) {
		new JDK9HttpClient().get();
	}

	public JDK9HttpClient() {
		_executor = new ScheduledThreadPoolExecutor(5);

		_httpClient = HttpClient.newBuilder()
			.version(HttpClient.Version.HTTP_1_1)
			.executor(_executor)
			.build();
	}

	public void get() {
		HttpRequest httpRequest = HttpRequest.newBuilder()
			.GET()
			.uri(URI.create("https://www.baidu.com"))
			.build();

		CompletableFuture<HttpResponse<String>> f = _httpClient.sendAsync(
			httpRequest, HttpResponse.BodyHandler.asString(Charset.defaultCharset()));

		f.thenAcceptAsync(
			response -> {
				System.out.println(response.body());
			},
			_executor);
	}

	private final ScheduledThreadPoolExecutor _executor;
	private final HttpClient _httpClient;

}

这个程序只是用来展示一下新的API,写得非常简略,线程池都没有正确退出。可以看到,JDK9 HTTP客户端的API与以前的截然不同,HttpClientHttpRequest的实例使用Builder模式来创建,写起来更加直观和流畅;提供了异步方法来发送请求,配合CompletableFuture可以编写比较复杂的异步程序。当然,还有很多新特性,在这段程序中并没有列出。比如,HttpRequest.BodyProcessor是一个Flow.Publisher,而HttpResponse.BodyProcessor则是一个Flow.Subcriber,而Flow正是Java 9新增的Reactive编程框架。总之,新的HTTP客户端,不但在API设计上向流行的第三方客户端靠拢,还与其他Java 8和9中的新API深度集成,应该能够在很多时候替代第三方客户端。

// 编译和运行

如果直接尝试用Gradle编译工程,就会在编译上面的类的时候失败。Gradle给出的编译错误是,使用了模块里的类,但是模块并不在模块图中。前面只是在代码中导入了httpclient模块中的类,但是模块化的一个特点就是,要声明自己所用到的模块。这里,我们需要进行一些修改,才能让Gradle正确地编译和运行依赖于JDK9模块的程序。

首先,IntelliJ已经提供了对JDK9模块描述文件的支持。在root包下右键添加一个module-info.java之后,IntelliJ就会自动在代码编辑器中给出错误提示,并给出自动修复选项。实际上,就是在描述文件中,添加一行requires来声明对模块的依赖:

module JDK9.Play {
	requires jdk.incubator.httpclient;
}

这样一来,JDK就能够正确处理模块依赖关系了。

接下来是Gradle。最新版的Gradle并没有内置对JDK9模块的支持,但是可以通过一些“hack”来解决。一种方法是去为相应的task附加代码,可以参考Building Java 9 Modules里面的长篇大论;另一种方法则是直接这个教程的最下方,使用最后一章介绍的experimental-jigsaw插件。这里我选择后者。将插件添加到build.gradle之后,就可以编译成功了。

// In the end

虽然新HTTP客户端模块的名字jdk.incubator.httpclient告诉我们这还是个实验性功能,但据说在下一个大版本中,就会有名为java.httpclient的正式版模块了。希望这一天早些到来。