Java11新特性-效能翻倍的HttpClient
ztj100 2025-01-19 02:00 16 浏览 0 评论
古老的背景
从JDK1.1开始,JDK中就有HttpURLConnection来提供了网络连接的能力,但是由于实现的比较古早,其有很多的局限性。比如HttpURLConnection是通过底层提供的socket连接来进行通信,而每一个HttpURLConnection实例只能发送一个请求,之后只能通过close()释放请求的网络资源,或在持久化连接时用disconnect()来关闭关闭底层socket。而其基类URLConnection是为了支持很多协议而设计的,但诸如FTP这种协议已经不咋用了。
HttpURLConnection并不是不能使用,由于不需要依赖,在一些demo项目的时候也会偶尔拿来用。但HttpURLConnection本身已经太过古早,并且很难说HttpURLConnection能够胜任包含各种鉴权信息、各种COOKIE信息的访问请求。
针对这种情况网络上各种大神提供了更多高级的封装,比较流行的有Apache的HttpClient、Okhttp Client、Spring Cloud Feign之类的。这些封装提供了更丰富的资源与更便捷的封装,也支持了更高级功能如HTTP/2协议、异步请求等。
不过到了JDK9的时候,Java提供了一个新的Http请求工具HttpClient,经过了JDK10的再次预览,最终在JAVA11中作为正式功能提供使用,同时也完全替换了仅有阻塞模式的HttpURLConnection。
HttpClient简介
作为JDK11中正式推出的新Http连接器,支持的功能还是比较新的,主要的特性有:
- 完整支持HTTP 2.0 或者HTTP 1.1
- 支持 HTTPS/TLS
- 有简单的阻塞使用方法
- 支持异步发送,异步时间通知
- 支持WebSocket
- 支持响应式流
HTTP2.0其他的客户端也能支持,而HttpClient使用CompletableFuture作为异步的返回数据。WebSocket的支持则是HttpClient的优势。响应式流的支持是HttpClient的一大优势。
而HttpClient中的NIO模型、函数式编程、CompletableFuture异步回调、响应式流让HttpClient拥有极强的并发处理能力,所以其性能极高,而内存占用则更少。
HttpClient的主要类有:
- java.net.http.HttpClient
- java.net.http.HttpRequest
- java.net.http.HttpResponse
- java.net.http.WebSocket(本文就不介绍这个了)
细节会在后文介绍,但是WebSocket用的比较少,本文就略过了。
核心使用
HttpClient 的核心类主要就是 HttpClient、HttpRequest以及HttpResponse,它们都是位于java.net.http 包下,接下来对他们进行一下介绍。
HttpClient
HttpClient类是最核心的类,它支持使用建造者模式进行复杂对象的构建,主要的参数有:
- Http 协议的版本 (HTTP 1.1 或者 HTTP 2.0),默认是 2.0。
- 是否遵从服务器发出的重定向
- 连接超时时间
- 代理
- 认证
//可以用参数调整
HttpClient client = HttpClient.newBuilder()
.version(Version.HTTP_1_1)
.followRedirects(Redirect.NORMAL)
.connectTimeout(Duration.ofSeconds(20))
.proxy(ProxySelector.of(new InetSocketAddress("proxy.example.com", 8080)))
.authenticator(Authenticator.getDefault())
.build();
//也可以直接全部默认的便捷创建
HttpClient clientSimple = HttpClient.newHttpClient();
当创建了HttpClient实例后,可以通过其发送多条请求,不用重复创建。
HttpRequest
HttpRequest 是用语描述请求体的类,也支持通过建造者模式构建复杂对象,主要的参数有:
- 请求地址
- 请求方法:GET,POST,DELETE 等(默认是GET)
- 请求体 (按需设置,例如 GET 不用 body,但是 POST 要设置)
- 请求超时时间(默认)
- 请求头
//使用参数组合进行对象构建,读取文件作为请求体
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("http://www.baidu.com"))
.timeout(Duration.ofSeconds(20))
.header("Content-type","application/json")
.POST(HttpRequest.BodyPublishers.ofFile(Paths.get("data.json")))
.build();
//直接GET访问
HttpRequest requestSimple = HttpRequest.newBuilder(URI.create("http://www.baidu.com")).build();
HttpRequest 是一个不可变类,可以被多次发送。
HttpResponse
HttpResponse没有提供外部可以创建的实现类,它是一个接口,从client的返回值中创建获得。接口中的主要方法为:
public interface HttpResponse<T> {
public int statusCode();
public HttpRequest request();
public Optional<HttpResponse<T>> previousResponse();
public HttpHeaders headers();
public T body();
public URI uri();
public Optional<SSLSession> sslSession();
public HttpClient.Version version();
}
HttpResponse的返回内容与常识一致,这里就不展开介绍了。
信息发送
HttpClient中可以使用同步发送或者异步发送。
同步 send()
同步发送后,请求会一直阻塞到收到response为止。
final HttpResponse<String> send = client.send(httpRequest, HttpResponse.BodyHandlers.ofString());
System.out.println(send.body());
其中 send的第二个参数是通过HttpResponse.BodyHandlers的静态工厂来返回一个可以将 response 转换为目标类型T的处理器(handler),本例子中的类型是String。HttpResponse.BodyHandlers.ofString()的实现方法为:
public static BodyHandler<String> ofString() {
return (responseInfo) -> BodySubscribers.ofString(charsetFrom(responseInfo.headers()));
}
其中,BodySubscribers.ofString() 的方法实现是:
public static BodySubscriber<String> ofString(Charset charset) {
Objects.requireNonNull(charset);
return new ResponseSubscribers.ByteArraySubscriber<>(
bytes -> new String(bytes, charset)
);
}
可以看到最终是返回了一个ResponseSubscribers ,而Subscribers则是我们之前《JDK9响应式编程》中讨论过的订阅者。这个构造方法的入参Function<byte[],T>定义了订阅者中的finisher属性,而这个属性将在响应式流完成订阅的时在onComplete()`方法中调用。
异步 sendAsync()
异步请求发送之后,会立刻返回 CompletableFuture,然后可以使用CompletableFuture中的方法来设置异步处理器。
client.sendAsync(httpRequest, HttpResponse.BodyHandlers.ofString())
.thenApply(HttpResponse::body)
.thenAccept(System.out::println)
.join();
而就如同JDK中响应式流中发布者的submit()方法与offer()方法一样,HttpClient中的send()方法知识sendAsync方法的特例,在send()方法中是先调用sendAsync()方法,然后直接阻塞等待响应结束再返回,部分核心代码为:
@Override
public <T> HttpResponse<T>
send(HttpRequest req, BodyHandler<T> responseHandler)
throws IOException, InterruptedException
{
CompletableFuture<HttpResponse<T>> cf = null;
// if the thread is already interrupted no need to go further.
// cf.get() would throw anyway.
if (Thread.interrupted()) throw new InterruptedException();
try {
cf = sendAsync(req, responseHandler, null, null);
return cf.get();
} catch (InterruptedException ie) {
if (cf != null )
cf.cancel(true);
throw ie;
}
...
响应式流
HttpClient 作为 Request 的发布者 (publisher),将 Request 发布到服务器,作为 Response 的订阅者 (subscriber),从服务器接收 Response。而上文中我们在send()的部分发现,调用链的最底端返回的是一个ResponseSubscribers订阅者。
当然,就如同HttpResponse.BodyHandlers.ofString(),HttpClient默认提供了一系列的默认订阅者,用语处理数据的转换:
HttpRequest.BodyPublishers::ofByteArray(byte[])
HttpRequest.BodyPublishers::ofByteArrays(Iterable)
HttpRequest.BodyPublishers::ofFile(Path)
HttpRequest.BodyPublishers::ofString(String)
HttpRequest.BodyPublishers::ofInputStream(Supplier<InputStream>)
HttpResponse.BodyHandlers::ofByteArray()
HttpResponse.BodyHandlers::ofString()
HttpResponse.BodyHandlers::ofFile(Path)
HttpResponse.BodyHandlers::discarding()
所以在HttpClient的时候我们也可以自己创建一个实现了Flow.Subscriber<List<ByteBuffer>>接口的订阅者,用于消费数据。响应式流完整的简单的例子如下:
public class HttpClientTest {
public static void main(String[] args) throws IOException, InterruptedException {
final HttpClient client = HttpClient.newHttpClient();
final HttpRequest httpRequest = HttpRequest.newBuilder(URI.create("http://www.baidu.com")).build();
HttpResponse.BodySubscriber<String> subscriber = HttpResponse.BodySubscribers.fromSubscriber(new StringSubscriber(),StringSubscriber::getBody);
client.sendAsync(httpRequest,responseInfo -> subscriber)
.thenApply(HttpResponse::body)
.thenAccept(System.out::println)
.join();
}
static class StringSubscriber implements Flow.Subscriber<List<ByteBuffer>>{
Flow.Subscription subscription;
List<ByteBuffer> response = new ArrayList<>();
String body;
public String getBody(){
return body;
}
@Override
public void onSubscribe(Flow.Subscription subscription) {
this.subscription = subscription;
subscription.request(1);
}
@Override
public void onNext(List<ByteBuffer> item) {
response.addAll(item);
subscription.request(1);
}
@Override
public void onError(Throwable throwable) {
System.err.println(throwable);
}
@Override
public void onComplete() {
byte[] data = new byte[response.stream().mapToInt(ByteBuffer::remaining).sum()];
int offset = 0;
for(ByteBuffer buffer:response){
int remain = buffer.remaining();
buffer.get(data,offset,remain);
offset += remain;
}
body = new String(data);
}
}
}
最后
HttpClient是JDK11正式上线的高性能Http客户端。其底层基于响应式流,通过上层封装还提供了异步信息发送、同步信息发送,以及其他完成的HTTP协议内容。在进行响应式编程的方面,HttpClient也是一个十分优秀的参照目标。
相关推荐
- 从IDEA开始,迈进GO语言之门(idea got)
-
前言笔者在学习GO语言编程的时候,GO语言在国内还没有像JAVA/Php/Python那样普及,绕了不少的弯路,要开始入门学习一门编程语言,最好就先从选择一个好的编程语言的开发环境开始,有了这个开发环...
- 基于SpringBoot+MyBatis的私人影院java网上购票jsp源代码Mysql
-
本项目为前几天收费帮学妹做的一个项目,JavaEEJSP项目,在工作环境中基本使用不到,但是很多学校把这个当作编程入门的项目来做,故分享出本项目供初学者参考。一、项目介绍基于SpringBoot...
- 基于springboot的个人服装管理系统java网上商城jsp源代码mysql
-
本项目为前几天收费帮学妹做的一个项目,JavaEEJSP项目,在工作环境中基本使用不到,但是很多学校把这个当作编程入门的项目来做,故分享出本项目供初学者参考。一、项目介绍基于springboot...
- 基于springboot的美食网站Java食品销售jsp源代码Mysql
-
本项目为前几天收费帮学妹做的一个项目,JavaEEJSP项目,在工作环境中基本使用不到,但是很多学校把这个当作编程入门的项目来做,故分享出本项目供初学者参考。一、项目介绍基于springboot...
- 贸易管理进销存springboot云管货管账分析java jsp源代码mysql
-
本项目为前几天收费帮学妹做的一个项目,JavaEEJSP项目,在工作环境中基本使用不到,但是很多学校把这个当作编程入门的项目来做,故分享出本项目供初学者参考。一、项目描述贸易管理进销存spring...
- SpringBoot+VUE员工信息管理系统Java人员管理jsp源代码Mysql
-
本项目为前几天收费帮学妹做的一个项目,JavaEEJSP项目,在工作环境中基本使用不到,但是很多学校把这个当作编程入门的项目来做,故分享出本项目供初学者参考。一、项目介绍SpringBoot+V...
- 目前见过最牛的一个SpringBoot商城项目(附源码)还有人没用过吗
-
帮粉丝找了一个基于SpringBoot的天猫商城项目,快速部署运行,所用技术:MySQL,Druid,Log4j2,Maven,Echarts,Bootstrap...免费给大家分享出来前台演示...
- SpringBoot+Mysql实现的手机商城附带源码演示导入视频
-
今天为大家带来的是基于SpringBoot+JPA+Thymeleaf框架的手机商城管理系统,商城系统分为前台和后台、前台用的是Bootstrap框架后台用的是SpringBoot+JPA都是现在主...
- 全网首发!马士兵内部共享—1658页《Java面试突击核心讲》
-
又是一年一度的“金九银十”秋招大热门,为助力广大程序员朋友“面试造火箭”,小编今天给大家分享的便是这份马士兵内部的面试神技——1658页《Java面试突击核心讲》!...
- SpringBoot数据库操作的应用(springboot与数据库交互)
-
1.JDBC+HikariDataSource...
- SpringBoot 整合 Flink 实时同步 MySQL
-
1、需求在Flink发布SpringBoot打包的jar包能够实时同步MySQL表,做到原表进行新增、修改、删除的时候目标表都能对应同步。...
- SpringBoot + Mybatis + Shiro + mysql + redis智能平台源码分享
-
后端技术栈基于SpringBoot+Mybatis+Shiro+mysql+redis构建的智慧云智能教育平台基于数据驱动视图的理念封装element-ui,即使没有vue的使...
- Springboot+Mysql舞蹈课程在线预约系统源码附带视频运行教程
-
今天发布的是由【猿来入此】的优秀学员独立做的一个基于springboot脚手架的Springboot+Mysql舞蹈课程在线预约系统,系统项目源代码在【猿来入此】获取!https://www.yuan...
- SpringBoot+Mysql在线众筹系统源码+讲解视频+开发文档(参考论文
-
今天发布的是由【猿来入此】的优秀学员独立做的一个基于springboot脚手架的在线众筹管理系统,主要实现了普通用户在线参与众筹基本操作流程的全部功能,系统分普通用户、超级管理员等角色,除基础脚手架外...
- Docker一键部署 SpringBoot 应用的方法,贼快贼好用
-
这两天发现个Gradle插件,支持一键打包、推送Docker镜像。今天我们来讲讲这个插件,希望对大家有所帮助!GradleDockerPlugin简介...
你 发表评论:
欢迎- 一周热门
- 最近发表
-
- 从IDEA开始,迈进GO语言之门(idea got)
- 基于SpringBoot+MyBatis的私人影院java网上购票jsp源代码Mysql
- 基于springboot的个人服装管理系统java网上商城jsp源代码mysql
- 基于springboot的美食网站Java食品销售jsp源代码Mysql
- 贸易管理进销存springboot云管货管账分析java jsp源代码mysql
- SpringBoot+VUE员工信息管理系统Java人员管理jsp源代码Mysql
- 目前见过最牛的一个SpringBoot商城项目(附源码)还有人没用过吗
- SpringBoot+Mysql实现的手机商城附带源码演示导入视频
- 全网首发!马士兵内部共享—1658页《Java面试突击核心讲》
- SpringBoot数据库操作的应用(springboot与数据库交互)
- 标签列表
-
- idea eval reset (50)
- vue dispatch (70)
- update canceled (42)
- order by asc (53)
- spring gateway (67)
- 简单代码编程 贪吃蛇 (40)
- transforms.resize (33)
- redisson trylock (35)
- 卸载node (35)
- np.reshape (33)
- torch.arange (34)
- node卸载 (33)
- npm 源 (35)
- vue3 deep (35)
- win10 ssh (35)
- exceptionininitializererror (33)
- vue foreach (34)
- idea设置编码为utf8 (35)
- vue 数组添加元素 (34)
- std find (34)
- tablefield注解用途 (35)
- python str转json (34)
- java websocket客户端 (34)
- tensor.view (34)
- java jackson (34)