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

从零开始学Java:第31章 网络和 HTTP:让 Java 程序和外部服务通信
第31章 网络和 HTTP让 Java 程序和外部服务通信到目前为止你写的程序主要在本机运行读命令行、写文件、处理内存对象。真实应用经常要和外部系统通信。比如查询天气。调用短信服务。请求后端接口。下载文件。把订单提交给支付系统。Android App 调用服务器接口。这些都离不开网络和 HTTP。这一章先不做复杂 Web 服务器而是从客户端角度理解 HTTPJava 程序如何发送请求如何接收响应如何处理状态码、JSON、超时和异常。一、客户端和服务器网络通信里常见两类角色客户端发起请求的一方。 服务器接收请求并返回响应的一方。浏览器访问网站浏览器 - 服务器 服务器 - HTML/CSS/JSJava 程序调用接口Java程序 - API服务器 API服务器 - JSONAndroid 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?detailtrue拆开部分含义https协议api.example.com主机名443端口/books/001路径detailtrue查询参数常见端口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 HttpClientJava 11 开始标准库提供HttpClient。发送 GET 请求importjava.net.URI;importjava.net.http.HttpClient;importjava.net.http.HttpRequest;importjava.net.http.HttpResponse;importjava.time.Duration;publicclassGetDemo{publicstaticvoidmain(String[]args)throwsException{HttpClientclientHttpClient.newBuilder().connectTimeout(Duration.ofSeconds(5)).build();HttpRequestrequestHttpRequest.newBuilder().uri(URI.create(https://api.example.com/books/001)).timeout(Duration.ofSeconds(10)).header(Accept,application/json).GET().build();HttpResponseStringresponseclient.send(request,HttpResponse.BodyHandlers.ofString());System.out.println(response.statusCode());System.out.println(response.body());}}这里有三个对象HttpClient负责发送请求。HttpRequest表示请求。HttpResponseString表示响应body 是字符串。七、处理状态码不要直接使用 body。intstatusresponse.statusCode();if(status200status300){System.out.println(成功response.body());}elseif(status404){System.out.println(资源不存在);}else{System.out.println(请求失败状态码status响应response.body());}可以封装publicstaticStringrequireSuccess(HttpResponseStringresponse){intstatusresponse.statusCode();if(status200status300){returnresponse.body();}thrownewIllegalStateException(HTTP请求失败状态码status响应response.body());}这样业务代码不会到处重复判断。八、发送 POST JSON请求体是 JSON{isbn:001,title:Java入门}Java 代码Stringjson{\isbn\:\001\,\title\:\Java入门\};HttpRequestrequestHttpRequest.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();HttpResponseStringresponseclient.send(request,HttpResponse.BodyHandlers.ofString());Content-Type表示你发出去的数据格式。Accept表示你希望服务器返回什么格式。真实项目里 JSON 不应该手写字符串应该用 JacksonObjectMappermappernewObjectMapper();Stringjsonmapper.writeValueAsString(bookRequest);九、把响应 JSON 转成对象假设响应{isbn:001,title:Java入门,author:作者A}定义 DTOpublicclassBookResponse{privateStringisbn;privateStringtitle;privateStringauthor;publicStringgetIsbn(){returnisbn;}publicvoidsetIsbn(Stringisbn){this.isbnisbn;}publicStringgetTitle(){returntitle;}publicvoidsetTitle(Stringtitle){this.titletitle;}publicStringgetAuthor(){returnauthor;}publicvoidsetAuthor(Stringauthor){this.authorauthor;}}解析ObjectMappermappernewObjectMapper();BookResponsebookmapper.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 会影响用户体验。十一、同步请求和异步请求同步HttpResponseStringresponseclient.send(request,HttpResponse.BodyHandlers.ofString());当前线程会等待响应。异步client.sendAsync(request,HttpResponse.BodyHandlers.ofString()).thenApply(HttpResponse::body).thenAccept(System.out::println);异步请求返回CompletableFuture。初学阶段先掌握同步请求。异步请求和并发、线程池关系更复杂后面可以继续深入。十二、下载文件把响应保存到文件HttpRequestrequestHttpRequest.newBuilder().uri(URI.create(https://example.com/file.zip)).build();HttpResponsePathresponseclient.send(request,HttpResponse.BodyHandlers.ofFile(Path.of(data,file.zip)));System.out.println(response.statusCode());System.out.println(response.body());BodyHandlers.ofFile会把响应体写到文件。如果文件很大不要先读成字符串。十三、一个简单 API 客户端类把请求封装起来publicclassBookApiClient{privatefinalHttpClientclient;privatefinalURIbaseUri;privatefinalObjectMappermapper;publicBookApiClient(StringbaseUrl){this.clientHttpClient.newBuilder().connectTimeout(Duration.ofSeconds(5)).build();this.baseUriURI.create(baseUrl);this.mappernewObjectMapper();}publicBookResponsefindBook(Stringisbn){try{URIuribaseUri.resolve(/books/isbn);HttpRequestrequestHttpRequest.newBuilder().uri(uri).timeout(Duration.ofSeconds(10)).header(Accept,application/json).GET().build();HttpResponseStringresponseclient.send(request,HttpResponse.BodyHandlers.ofString());StringbodyrequireSuccess(response);returnmapper.readValue(body,BookResponse.class);}catch(IOExceptione){thrownewIllegalStateException(调用图书接口失败,e);}catch(InterruptedExceptione){Thread.currentThread().interrupt();thrownewIllegalStateException(调用图书接口被中断,e);}}privateStringrequireSuccess(HttpResponseStringresponse){intstatusresponse.statusCode();if(status200status300){returnresponse.body();}thrownewIllegalStateException(HTTP状态码异常status响应response.body());}}这段代码有几个要点网络 IO 可能失败要处理IOException。线程可能被中断要恢复中断标记。HTTP 状态码不是 2xx 时不当成成功。JSON 解析失败也会抛异常。十四、不要把 HTTP 代码散在业务里不推荐publicvoidborrowBook(Stringisbn,StringreaderId){HttpClientclientHttpClient.newHttpClient();// 这里调用远程图书接口// 然后继续借书}更好的分层BookApiClient负责 HTTP LibraryService负责借书业务业务层调用客户端BookResponsebookbookApiClient.findBook(isbn);HTTP 细节集中在一个类里方便测试和替换。十五、常见错误1. 只看 body不看状态码404、500 也可能有 body但不是成功。2. 不设置超时网络请求可能一直卡住。3. JSON 手写拼接简单演示可以真实项目用 Jackson。4. InterruptedException 后不恢复中断推荐catch(InterruptedExceptione){Thread.currentThread().interrupt();thrownewIllegalStateException(请求被中断,e);}5. 把 HTTP 调用散落到各个业务方法应该封装成 API Client。十六、练习用HttpClient发送一个 GET 请求打印状态码和 body。写requireSuccess方法非 2xx 抛异常。构造一个 POST JSON 请求。用 Jackson 把对象转 JSON。写一个BookApiClient封装findBook(isbn)。给请求设置连接超时和请求超时。思考HTTP 接口失败时业务层应该重试、提示用户还是直接失败十七、本章小结你现在应该理解HTTP 是请求响应模型。请求包含 URL、方法、header、body。响应包含状态码、header、body。GET 常用于查询POST 常用于提交。状态码必须检查。Java 11 的HttpClient可以发送 HTTP 请求。JSON 应使用 Jackson 等库解析。网络请求必须考虑超时、IO 异常和中断。HTTP 代码应该封装在 API Client 中不要散落在业务层。下一章讲数据库和 JDBC。HTTP 是程序和远程服务通信JDBC 是程序和数据库通信。它们都会遇到外部资源、异常、连接管理和数据格式问题。