package chapter15;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class HttpClientDemo {
public static void main(String[] args) throws Exception {
HttpClient httpClient = HttpClient.newHttpClient();
URI uri = URI.create("https://www.xdclass.net/");
HttpRequest.Builder builder = HttpRequest.newBuilder();
HttpRequest httpRequest = builder.uri(uri).build();
HttpResponse<String> httpResponse = httpClient.send(httpRequest,HttpResponse.BodyHandlers.ofString());
System.out.println("响应状态码:" + httpResponse.statusCode());
System.out.println("响应体:" + httpResponse.body());
}
}
HttpClient httpClient = HttpClient.newHttpClient();
作用: 创建一个 HttpClient 的实例。
目的: 这是我们发起所有网络请求的“发起者”。
详解:
HttpClient.newHttpClient() 是一个静态工厂方法,它会创建一个具有默认配置的 HttpClient。
这个 httpClient 对象是高度可复用的。在实际应用中,你应该只创建一个 HttpClient 实例,然后在整个应用程序中共享它来发送多个请求。它内部管理着连接池和线程等资源,复用可以大大提高性能。
URI uri = URI.create("https://www.xdclass.net/");
作用: 根据一个字符串创建一个 URI 对象。
目的: 为即将构建的 HTTP 请求提供一个格式正确、经过验证的目标地址。使用 URI 类型比直接使用 String 更健壮,因为 URI.create() 会检查语法的正确性。
HttpRequest.Builder builder = HttpRequest.newBuilder();
作用: 创建一个 HttpRequest 的构造器(Builder)。
目的: 这是建造者模式(Builder Pattern)的运用。HttpRequest 对象被设计成不可变的(Immutable),意味着一旦创建就不能修改。建造者模式提供了一种流畅、可读性强的方式来分步构建这个复杂的不可变对象。你先创建一个“蓝图”(Builder),在上面设置各种属性,最后再一次性“建造”出最终的对象。
HttpRequest httpRequest = builder.uri(uri).build();
作用: 使用 builder 来配置并最终构建出 HttpRequest 对象。
目的: 生成一个代表我们意图的、完整的请求对象。
详解:
builder.uri(uri): 在 builder 上设置请求的目标 URI。这个方法会返回 builder 本身,这使得方法链式调用成为可能(例如 builder.uri(uri).header(…).GET())。
.build(): 这是建造过程的最后一步。它会收集所有在 builder 上设置的信息,并创建一个最终的、不可变的 HttpRequest 实例。
注意: 我们没有明确指定请求方法(如 .GET() 或 .POST(…))。在这种情况下,API 默认使用 GET 方法,这对于访问网页来说是最常见的操作。
HttpResponse<String> httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
作用: 发送 HTTP 请求并获取响应。这是整个程序中最核心的一步。
目的: 执行实际的网络通信。
详解:
httpClient.send(…): 这是一个同步方法,意味着代码会在这里阻塞(等待),直到服务器的完整响应被接收回来。
httpRequest: 第一个参数,是我们刚刚构建的请求对象。
HttpResponse.BodyHandlers.ofString(): 第二个参数,这是一个响应体处理器 (Body Handler)。它告诉 HttpClient 应该如何处理接收到的响应体数据。
ofString() 这个处理器表示:“请将服务器返回的原始字节流,根据响应头中 Content-Type 指定的字符集(如果未指定,则默认为 UTF-8)解码成一个 Java String。”
这个 API 非常灵活,还提供了其他处理器,如 ofByteArray() (处理成字节数组)、ofFile(path) (直接存为文件)、discarding() (忽略响应体)等。
HttpResponse<String>: send 方法的返回类型。泛型 <String> 与我们提供的 BodyHandlers.ofString() 相匹配,这是一种类型安全的设计。如果我们用了 ofByteArray(),这里的类型就会是 HttpResponse<byte[]>。
System.out.println("响应状态码:" + httpResponse.statusCode());
作用: 从响应对象中获取状态码并打印到控制台。
目的: 检查请求是否成功。
详解:
httpResponse.statusCode(): 返回一个 int 类型的值,代表 HTTP 状态码。常见的值有:
200: OK (成功)
404: Not Found (未找到)
500: Internal Server Error (服务器内部错误)
System.out.println("响应体:" + httpResponse.body());
作用: 从响应对象中获取响应体并打印到控制台。
目的: 查看从服务器获取到的实际内容。
详解:
httpResponse.body(): 返回响应体。因为我们在 send 方法中指定了 BodyHandlers.ofString(),所以这个方法的返回值类型就是 String。对于一个网页请求,这里打印出的将会是该网页的 HTML 源代码。
}
}
main 方法和 HttpClientDemo 类的结束括号。
3. 代码核心思想与要点
现代化与流畅性: 整个 API 设计采用了现代 Java 的风格,如工厂方法 (newHttpClient)、建造者模式和方法链式调用,使得代码非常清晰易读。
不可变性: HttpRequest 和 HttpResponse 都是不可变对象,这使得它们在多线程环境中是线程安全的,可以安全地共享。
灵活性: 通过不同的 BodyHandler,可以非常灵活地决定如何处理响应数据,无论是转为字符串、存为文件还是直接丢弃,都非常方便。
同步与异步: 代码中展示的是 send() 同步方法,API 还提供了一个 sendAsync() 方法,它会返回一个 CompletableFuture,用于进行非阻塞的异步编程,这在构建高性能网络应用时非常有用。
案例比喻
一、把互联网想象成一个巨大的邮政系统
你的电脑: 就是你自己,寄信人。
服务器 (Server): 就是收信人,比如 “小顶课堂”(xdclass.net)的总部。服务器本质上也是一台电脑,但它24小时开机,专门用来存放网站内容并等待别人来索取。
互联网: 就是连接你和总部的邮政网络(包括邮筒、邮局、卡车、飞机等)。
二-A、代码在做什么:寄信请求的过程
现在,我们把你的Java代码和寄信的步骤一一对应起来。
// 准备工作1: 找一个可靠的快递公司
HttpClient httpClient = HttpClient.newHttpClient();
HTTP Client (客户端): 把它想象成一个快递公司,比如顺丰或邮政。它是一个专业的机构,知道所有寄信的规则、路线和如何处理各种问题。你不需要自己跑去送信,你只需要把信交给它就行。
代码含义: “嘿,Java,给我找一家默认的、可靠的‘HTTP快递公司’,我接下来要用它寄信。”
// 准备工作2: 写下收信人的地址
URI uri = URI.create("https://www.xdclass.net/");
URI (统一资源标识符): 这就是收信人的地址,写得非常标准,邮递员一看就懂。
https://: 表示这封信需要加密传输(像是一个加密的保险箱),保证内容不被偷看。
www.xdclass.net: 就是收信人的具体门牌号,“小顶课堂网站总部”。
代码含义: “我要把信寄到这个地址去。”
// 准备工作3: 准备信封和信纸
HttpRequest.Builder builder = HttpRequest.newBuilder();
HttpRequest httpRequest = builder.uri(uri).build();
HTTP Request (请求): 这就是你准备的一封完整的信。一封信包括:
信封: 上面写着收信人地址(uri)。
信的内容: 对于这次请求,信的内容是空的,因为我们只是去“要东西”,而不是“送东西”。
寄信方式 (Method): 你是怎么寄的?是要求对方给你回信(GET),还是你要寄东西给对方(POST)?
GET (获取): 就像你寄一张明信片,上面写着:“你好,请把你们最新的宣传手册寄给我一份。” 你的代码默认就是用的GET方式。
POST (提交): 就像你寄一个包裹,里面装着你填好的报名表。
代码含义: “请准备一封信(HttpRequest),信封上写好刚才那个地址。信的内容是空的,因为我只是想从他们那里**获取(GET)**一些信息。”
// 行动: 把信交给快递公司,并告诉他们回信怎么处理
HttpResponse<String> httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
httpClient.send(…): 这是**“寄出”**这个动作。你把准备好的信交给了快递公司。
代码会在这里停下等待: 快递员需要时间去送信,对方也需要时间准备回信,然后快递员再把回信送回来。所以你的程序会在这里“暂停”,直到收到回信为止。
HttpResponse.BodyHandlers.ofString(): 这是你给快递员的一个特别指示:“对方的回信(HttpResponse)可能是用密码(字节流)写的,你收到后,请帮我翻译成我能看懂的文字(String)。”
HttpResponse<String> httpResponse: httpResponse 就是快递员最终送回给你的回信。<String> 表示这封信的内容已经被翻译成了文字。
二-B、代码在做什么:查看回信的过程
// 查看回信1: 看信封上的邮戳和状态
System.out.println("响应状态码:" + httpResponse.statusCode());
HTTP Response (响应): 这就是服务器(小顶课堂总部)给你的回信。
Status Code (状态码): 这就像回信信封上的一个盖章或标签,告诉你对方处理你的请求的结果。
200: “请求收到,你要的东西就在信里,一切顺利!” (OK)
404: “你请求的那个部门或资料我们这里没有,地址是不是写错了?” (Not Found)
代码含义: “看看回信上的状态章,确认一下对方是不是成功处理了我的请求。”
// 查看回信2: 读信里的具体内容
System.out.println("响应体:" + httpResponse.body());
Body (响应体): 这就是回信的正文内容。因为你之前请求的是网页,所以这个正文就是那个网站的全部HTML代码(可以理解为网页的“骨架”描述语言)。
代码含义: “好了,现在打开信封,大声把信里的内容读出来。” 你会在屏幕上看到一大堆 <html>…</html> 这样的代码。
总结
所以,你的Java代码完整地模拟了这个过程:
找了家快递公司 (HttpClient)。
写好了收信地址 (URI)。
准备了一封“索要资料”的信 (HttpRequest)。
把信寄出去,并耐心等待回信 (send)。
收到回信后,先看处理结果盖章 (statusCode)。
再读回信的正文内容 (body)。