从零开始学Java:第31章 网络和 HTTP:让 Java 程序和外部服务通信

第31章 网络和 HTTP:让 Java 程序和外部服务通信

到目前为止,你写的程序主要在本机运行:读命令行、写文件、处理内存对象。真实应用经常要和外部系统通信。

比如:

  • 查询天气。
  • 调用短信服务。
  • 请求后端接口。
  • 下载文件。
  • 把订单提交给支付系统。
  • Android App 调用服务器接口。

这些都离不开网络和 HTTP。

这一章先不做复杂 Web 服务器,而是从客户端角度理解 HTTP:Java 程序如何发送请求,如何接收响应,如何处理状态码、JSON、超时和异常。

一、客户端和服务器

网络通信里常见两类角色:

客户端:发起请求的一方。
服务器:接收请求并返回响应的一方。

浏览器访问网站:

浏览器 -> 服务器
服务器 -> HTML/CSS/JS

Java 程序调用接口:

Java程序 -> API服务器
API服务器 -> JSON

Android App 调后端:

Android App -> 后端服务
后端服务 -> JSON数据

HTTP 是最常见的应用层协议。

二、HTTP 请求和响应

一次 HTTP 通信可以理解为:

请求 Request
响应 Response

请求包含:

  • URL。
  • 方法。
  • 请求头。
  • 请求体。

响应包含:

  • 状态码。
  • 响应头。
  • 响应体。

例如:

GET /books/001 HTTP/1.1
Host: api.example.com
Accept: application/json

响应:

HTTP/1.1 200 OK
Content-Type: application/json

{"isbn":"001","title":"Java入门"}

三、URL 的组成

https://api.example.com:443/books/001?detail=true

拆开:

部分含义
https协议
api.example.com主机名
443端口
/books/001路径
detail=true查询参数

常见端口:

  • HTTP 默认 80。
  • HTTPS 默认 443。

实际开发中,接口文档会告诉你 URL、方法、参数和响应格式。

四、HTTP 方法

常见方法:

方法常见含义
GET查询
POST新增或提交
PUT整体更新
PATCH部分更新
DELETE删除

例子:

GET /books
GET /books/001
POST /books
PUT /books/001
DELETE /books/001

这不是 Java 语法,而是接口设计约定。

五、状态码

常见状态码:

状态码含义
200成功
201创建成功
400请求参数错误
401未登录
403无权限
404资源不存在
409冲突,比如重复创建
500服务器内部错误

调用接口时不能只看有没有响应体。要先看状态码。

200:正常处理响应。
400/404:通常是请求方问题。
500:服务器问题。

六、Java HttpClient

Java 11 开始,标准库提供 HttpClient

发送 GET 请求:

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;

public class GetDemo {
    public static void main(String[] args) throws Exception {
        HttpClient client = HttpClient.newBuilder()
                .connectTimeout(Duration.ofSeconds(5))
                .build();

        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create("https://api.example.com/books/001"))
                .timeout(Duration.ofSeconds(10))
                .header("Accept", "application/json")
                .GET()
                .build();

        HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());

        System.out.println(response.statusCode());
        System.out.println(response.body());
    }
}

这里有三个对象:

  • HttpClient:负责发送请求。
  • HttpRequest:表示请求。
  • HttpResponse<String>:表示响应,body 是字符串。

七、处理状态码

不要直接使用 body。

int status = response.statusCode();
if (status >= 200 && status < 300) {
    System.out.println("成功:" + response.body());
} else if (status == 404) {
    System.out.println("资源不存在");
} else {
    System.out.println("请求失败,状态码:" + status + ",响应:" + response.body());
}

可以封装:

public static String requireSuccess(HttpResponse<String> response) {
    int status = response.statusCode();
    if (status >= 200 && status < 300) {
        return response.body();
    }
    throw new IllegalStateException("HTTP请求失败,状态码:" + status + ",响应:" + response.body());
}

这样业务代码不会到处重复判断。

八、发送 POST JSON

请求体是 JSON:

{"isbn":"001","title":"Java入门"}

Java 代码:

String json = "{\"isbn\":\"001\",\"title\":\"Java入门\"}";

HttpRequest request = HttpRequest.newBuilder()
        .uri(URI.create("https://api.example.com/books"))
        .timeout(Duration.ofSeconds(10))
        .header("Content-Type", "application/json")
        .header("Accept", "application/json")
        .POST(HttpRequest.BodyPublishers.ofString(json))
        .build();

HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());

Content-Type 表示你发出去的数据格式。

Accept 表示你希望服务器返回什么格式。

真实项目里 JSON 不应该手写字符串,应该用 Jackson:

ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(bookRequest);

九、把响应 JSON 转成对象

假设响应:

{"isbn":"001","title":"Java入门","author":"作者A"}

定义 DTO:

public class BookResponse {
    private String isbn;
    private String title;
    private String author;

    public String getIsbn() {
        return isbn;
    }

    public void setIsbn(String isbn) {
        this.isbn = isbn;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }
}

解析:

ObjectMapper mapper = new ObjectMapper();
BookResponse book = mapper.readValue(response.body(), BookResponse.class);

DTO 是 Data Transfer Object,数据传输对象。

它和你的领域模型 Book 可以相同,也可以不同。接口返回什么,DTO 就按接口格式设计。

十、超时很重要

网络请求不能无限等。

连接超时:

HttpClient.newBuilder()
        .connectTimeout(Duration.ofSeconds(5))
        .build();

请求超时:

HttpRequest.newBuilder()
        .timeout(Duration.ofSeconds(10))

如果不设置超时,接口卡住时,你的程序可能长时间无响应。

命令行程序会卡住。

服务器程序会占住线程。

Android App 会影响用户体验。

十一、同步请求和异步请求

同步:

HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());

当前线程会等待响应。

异步:

client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
        .thenApply(HttpResponse::body)
        .thenAccept(System.out::println);

异步请求返回 CompletableFuture

初学阶段先掌握同步请求。异步请求和并发、线程池关系更复杂,后面可以继续深入。

十二、下载文件

把响应保存到文件:

HttpRequest request = HttpRequest.newBuilder()
        .uri(URI.create("https://example.com/file.zip"))
        .build();

HttpResponse<Path> response = client.send(
        request,
        HttpResponse.BodyHandlers.ofFile(Path.of("data", "file.zip"))
);

System.out.println(response.statusCode());
System.out.println(response.body());

BodyHandlers.ofFile 会把响应体写到文件。

如果文件很大,不要先读成字符串。

十三、一个简单 API 客户端类

把请求封装起来:

public class BookApiClient {
    private final HttpClient client;
    private final URI baseUri;
    private final ObjectMapper mapper;

    public BookApiClient(String baseUrl) {
        this.client = HttpClient.newBuilder()
                .connectTimeout(Duration.ofSeconds(5))
                .build();
        this.baseUri = URI.create(baseUrl);
        this.mapper = new ObjectMapper();
    }

    public BookResponse findBook(String isbn) {
        try {
            URI uri = baseUri.resolve("/books/" + isbn);
            HttpRequest request = HttpRequest.newBuilder()
                    .uri(uri)
                    .timeout(Duration.ofSeconds(10))
                    .header("Accept", "application/json")
                    .GET()
                    .build();

            HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
            String body = requireSuccess(response);
            return mapper.readValue(body, BookResponse.class);
        } catch (IOException e) {
            throw new IllegalStateException("调用图书接口失败", e);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new IllegalStateException("调用图书接口被中断", e);
        }
    }

    private String requireSuccess(HttpResponse<String> response) {
        int status = response.statusCode();
        if (status >= 200 && status < 300) {
            return response.body();
        }
        throw new IllegalStateException("HTTP状态码异常:" + status + ",响应:" + response.body());
    }
}

这段代码有几个要点:

  • 网络 IO 可能失败,要处理 IOException
  • 线程可能被中断,要恢复中断标记。
  • HTTP 状态码不是 2xx 时,不当成成功。
  • JSON 解析失败也会抛异常。

十四、不要把 HTTP 代码散在业务里

不推荐:

public void borrowBook(String isbn, String readerId) {
    HttpClient client = HttpClient.newHttpClient();
    // 这里调用远程图书接口
    // 然后继续借书
}

更好的分层:

BookApiClient:负责 HTTP
LibraryService:负责借书业务

业务层调用客户端:

BookResponse book = bookApiClient.findBook(isbn);

HTTP 细节集中在一个类里,方便测试和替换。

十五、常见错误

1. 只看 body,不看状态码

404、500 也可能有 body,但不是成功。

2. 不设置超时

网络请求可能一直卡住。

3. JSON 手写拼接

简单演示可以,真实项目用 Jackson。

4. InterruptedException 后不恢复中断

推荐:

catch (InterruptedException e) {
    Thread.currentThread().interrupt();
    throw new IllegalStateException("请求被中断", e);
}

5. 把 HTTP 调用散落到各个业务方法

应该封装成 API Client。

十六、练习

  1. HttpClient 发送一个 GET 请求,打印状态码和 body。
  2. requireSuccess 方法,非 2xx 抛异常。
  3. 构造一个 POST JSON 请求。
  4. 用 Jackson 把对象转 JSON。
  5. 写一个 BookApiClient,封装 findBook(isbn)
  6. 给请求设置连接超时和请求超时。
  7. 思考:HTTP 接口失败时,业务层应该重试、提示用户,还是直接失败?

十七、本章小结

你现在应该理解:

  • HTTP 是请求响应模型。
  • 请求包含 URL、方法、header、body。
  • 响应包含状态码、header、body。
  • GET 常用于查询,POST 常用于提交。
  • 状态码必须检查。
  • Java 11 的 HttpClient 可以发送 HTTP 请求。
  • JSON 应使用 Jackson 等库解析。
  • 网络请求必须考虑超时、IO 异常和中断。
  • HTTP 代码应该封装在 API Client 中,不要散落在业务层。

下一章讲数据库和 JDBC。HTTP 是程序和远程服务通信,JDBC 是程序和数据库通信。它们都会遇到外部资源、异常、连接管理和数据格式问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

车载Android刘工

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值