北京時間 9 月 26 日,Oracle 官方宣佈 Java 11 正式發佈
一、JDK HTTP Client介紹
JDK11中的17個新特性
JDK11中引入HTTP Client的動機
既有的HttpURLConnection存在許多問題
- 其基類URLConnection當初是設計爲支持多協議,但其中大多已經成爲非主流(ftp, gopher…)
- API的設計早於HTTP/1.1,過度抽象
- 難以使用,存在許多沒有文檔化的行爲
- 它只支持阻塞模式(每個請求/響應占用一個線程)
<!--more-->
HTTP Client發展史
在JDK11 HTTP Client出現之前
在此之前,可以使用以下工具作爲Http客戶端
- JDK HttpURLConnection
- Apache HttpClient
- Okhttp
- Spring Rest Template
- Spring Cloud Feign
- 將Jetty用作客戶端
- 使用Netty庫。還
初探JDK HTTP Client
我們來看一段HTTP Client的常規用法的樣例 ——
執行GET請求,然後輸出響應體(Response Body)。
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("http://openjdk.java.net/"))
.build();
client.sendAsync(request, asString())
.thenApply(HttpResponse::body)
.thenAccept(System.out::println)
.join();
第一步:創建HttpClient
一般使用JDK 11中的HttpClient的第一步是創建HttpClient對象並進行配置。
- 指定協議(http/1.1或者http/2)
- 轉發(redirect)
- 代理(proxy)
- 認證(authenticator)
HttpClient client = HttpClient.newBuilder()
.version(Version.HTTP_2)
.followRedirects(Redirect.SAME_PROTOCOL)
.proxy(ProxySelector.of(new InetSocketAddress("www-proxy.com", 8080)))
.authenticator(Authenticator.getDefault())
.build();
第二步:創建HttpRequest
從HttpRequest的builder組建request
- 請求URI
- 請求method(GET, PUT, POST)
- 請求體(request body)
- Timeout
- 請求頭(request header)
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("http://openjdk.java.net/"))
.timeout(Duration.ofMinutes(1))
.header("Content-Type", "application/json")
.POST(BodyPublisher.fromFile(Paths.get("file.json")))
.build()
第三步:send
- http client可以用來發送多個http request
- 請求可以被以同步或異步方式發送
1. 同步發送
同步發送API阻塞直到HttpResponse返回
HttpResponse<String> response =
client.send(request, BodyHandler.asString());
System.out.println(response.statusCode());
System.out.println(response.body());
2. 異步發送
- 異步發送API立即返回一個CompletableFuture
- 當它完成的時候會獲得一個HttpResponse
client.sendAsync(request, BodyHandler.asString())
.thenApply(response -> { System.out.println(response.statusCode());
return response; } )
.thenApply(HttpResponse::body)
.thenAccept(System.out::println);
※CompletableFuture是在java8中加入的,支持組合式異步編程
二、從提升單機併發處理能力的技術來看HttpClient的實現細節
提升單機併發處理能力的技術
- 多CPU/多核
- 多線程
- 非阻塞(non-blocking)
Java NIO
Java NIO爲Java帶來了非阻塞模型。
Lambda表達式
- Lambda表達式可以方便地利用多CPU。
- Lambda表達式讓代碼更加具有可讀性
回調
假設以下代碼是一個聊天應用服務器的一部分,該應用以Vert.x框架實現。
(Eclipse Vert.x is a tool-kit for building reactive applications on the JVM.)
向connectHandler方法輸入一個Lambda表達式,每當有用戶連接到聊天應用時,都會調用該Lambda表達式。這就是一個回調。
這種方式的好處是,應用不必控制線程模型——Vert.x框架爲我們管理線程,打理好一切相關複雜性,程序員只考慮和回調就夠了。
vertx.createServer()
.connectHandler(socket -> {
socket.dataHandler(new User(socket, this));
}).listen(10_000);
注意,這種設計裏,不共享任何狀態。對象之間通過向事件總線發送消息通信,根本不需要在代碼中添加鎖或使用synchronized關鍵字。併發編程變得更加簡單。
Future
大量的回調會怎樣?請看以下僞代碼
(1)->{
(2)->{
(3)->{
(4)->{}
}
}
}
大量回調會形成“末日金字塔”。
如何破解? 使用Future
Future1=(1)->{}
Future2=(Future1.get())->{}
Future3=(Future2.get())->{}
Future4=(Future3.get())->{}
但這會造成原本期望的並行處理,變成了串行處理,帶來了性能問題。
我們真正需要的是將Future和回調聯合起來使用。下面將要講的CompletableFuture就是結合了Future和回調,其要點是組合不同實例而無需擔心末日金字塔問題。
CompletableFuture
(new CompletableFuture()).thenCompose((1)->{})
.thenCompose((2)->{})
.thenCompose((3)->{})
.thenCompose((4)->{})
.join()
Reactive Streams
Reactive Streams是一個倡議,它提倡提供一種帶有非阻塞背壓的異步流處理的標準(Reactive Streams is an initiative to provide a standard for asynchronous stream processing with non-blocking back pressure)。
JDK 9中的java.util.concurrent.Flow中的概念,與Reactive Streams是一對一對等的。java.util.concurrent.Flow是Reactive Streams標準的實現之一。
多CPU的並行機制讓處理海量數據的速度更快,消息傳遞和響應式編程讓有限的並行運行的線程執行更多的I/O操作。
HttpClient的實現細節
請求響應的body與reactive streams
- 請求響應的body暴露爲reactive streams
- http client是請求的body的消費者
- http client是響應的body的生產者
HttpRequest內部
public abstract class HttpRequest {
...
public interface BodyPublisher
extends Flow.Publisher<ByteBuffer> { ... }
}
HttpResponse的內部
public abstract class HttpResponse<T> {
...
public interface BodyHandler<T> {
BodySubscriber<T> apply(int statusCode, HttpHeaders responseHeaders);
}
public interface BodySubscriber<T>
extends Flow.Subscriber<List<ByteBuffer>> { ... }
}