Java实现HTTP请求与JSON解析的完整代码示例
本文提供Java标准库HttpClient发起HTTP GET/POST请求并结合Jackson进行JSON数据反序列化与序列化的完整代码示例与工程实践指南。
要在Java中获取网络接口数据并提取内容,核心工作就是两件事:通过网络客户端向指定URL发出HTTP请求拿到响应字符串,随后使用解析器把这串JSON文本转换为程序可直接调用的Java对象。在现代Java工程中,最标准、最干净的做法是直接使用Java内置的java.net.http.HttpClient完成网络交互,并搭配工业级工具包Jackson完成JSON序列化与反序列化。本文将提供一套可直接运行的完整工程代码,并拆解关键执行细节与边界注意事项。
1. 依赖准备与基础环境
很多刚开始写网络请求的开发者经常遇到一个困惑:旧教程里常常使用已经过时的HttpURLConnection或依赖庞大的第三方Apache HttpClient。实际上,自Java 11起,JDK就标准化并内置了现代化的java.net.http.HttpClient,它原生支持异步、同步及HTTP/2协议,无需额外引入网络通信依赖。
在JSON处理方面,虽然也可以手动解析字符串,但工业界标准方案是使用类型安全的对象映射器。这里我们选择最稳健、生态支持最广泛的Jackson。如果你的项目使用Maven,只需在pom.xml中加入jackson-databind即可:
com.fasterxml.jackson.core
jackson-databind
2.17.0
2. 定义数据模型实体类(POJO)
在发起请求前,需要先定义一个接收数据的载体。假设我们调用的接口返回一个包含用户基本信息的JSON:
{
"id": 1001,
"name": "张伟",
"email": "zhangwei@example.com"
}
针对这段结构,我们建立对应的Java实体类。Jackson要求实体类必须具备无参构造方法以及标准的getter/setter,或者通过合理的注解辅助。这里建议添加@JsonIgnoreProperties(ignoreUnknown = true),这样如果接口未来返回了多余字段,解析时不会抛出异常中断运行。
package com.example.demo.model;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@JsonIgnoreProperties(ignoreUnknown = true)
public class UserProfile {
private Long id;
private String name;
private String email;
// 必须保留无参构造器供Jackson反射实例化
public UserProfile() {
}
public UserProfile(Long id, String name, String email) {
this.id = id;
this.name = name;
this.email = email;
}
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
@Override
public String toString() {
return "UserProfile{" +
"id=" + id +
", name='" + name + '\'' +
", email='" + email + '\'' +
'}';
}
}
3. 执行流程与实现步骤
使用内置HttpClient和Jackson完成一次交互,需要遵循清晰的时序步骤。为了避免在请求挂起时造成线程永久阻塞,配置超时时间是构建可靠客户端的第一要务。
4. 完整代码示例:GET与POST操作
以下为一个可以直接编译运行的完整类文件。程序内部包含了两个常见场景:通过GET方法获取远端JSON并反序列化为实体类,以及将本地实体类转换为JSON并通过POST提交给目标接口。
package com.example.demo;
import com.example.demo.model.UserProfile;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
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 HttpJsonDemo {
// 推荐保持全局单例复用,复用内部连接池
private static final HttpClient HTTP_CLIENT = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(5))
.build();
// ObjectMapper同样是线程安全的,推荐单例复用
private static final ObjectMapper MAPPER = new ObjectMapper();
public static void main(String[] args) {
String testUrl = "https://jsonplaceholder.typicode.com/users/1";
try {
System.out.println("=== 1. 执行 GET 请求并解析 JSON ===");
UserProfile user = executeGet(testUrl);
System.out.println("成功解析为实体: " + user);
System.out.println("\n=== 2. 执行 POST 请求并提交 JSON ===");
UserProfile newUser = new UserProfile(101L, "李雷", "lilei@example.com");
String postUrl = "https://jsonplaceholder.typicode.com/users";
String responseBody = executePost(postUrl, newUser);
System.out.println("服务端返回结果:\n" + responseBody);
} catch (IOException | InterruptedException e) {
System.err.println("请求处理失败: " + e.getMessage());
Thread.currentThread().interrupt(); // 保持中断标志位
}
}
/**
* 发送 GET 请求并解析单体对象
*/
public static UserProfile executeGet(String url) throws IOException, InterruptedException {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.timeout(Duration.ofSeconds(10))
.header("Accept", "application/json")
.GET()
.build();
HttpResponse response = HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) {
throw new RuntimeException("HTTP 响应失败,状态码: " + response.statusCode());
}
// 解析 JSON 字符串到目标实体
return MAPPER.readValue(response.body(), UserProfile.class);
}
/**
* 将实体序列化为 JSON,并执行 POST 提交
*/
public static String executePost(String url, UserProfile payload) throws IOException, InterruptedException {
// 将对象序列化为 JSON 字符串
String jsonString = MAPPER.writeValueAsString(payload);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.timeout(Duration.ofSeconds(10))
.header("Content-Type", "application/json; charset=UTF-8")
.header("Accept", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(jsonString))
.build();
HttpResponse response = HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() < 200 || response.statusCode() >= 300) {
throw new RuntimeException("HTTP POST 失败,状态码: " + response.statusCode());
}
return response.body();
}
}
5. 复杂结构:解析集合与泛型对象
在实际业务中,接口很少总是返回一个纯粹的单体对象,往往返回的是一个包含多个对象的数组列表,或者带有状态码、消息和业务数据包裹的泛型对象:
// 场景 A: 列表返回
[ {"id":1,"name":"Alice"}, {"id":2,"name":"Bob"} ]
// 场景 B: 包装体返回
{ "code": 0, "msg": "ok", "data": { "id": 1, "name": "Alice" } }
在Java中,由于泛型在编译后会发生类型擦除,如果直接调用MAPPER.readValue(json, List会导致编译不通过。正确的做法是借助Jackson提供的TypeReference保留运行时类型信息。
import com.fasterxml.jackson.core.type.TypeReference;
import java.util.List;
// 针对集合结构的解析代码
String jsonArrayString = "[{\"id\":1,\"name\":\"张伟\"},{\"id\":2,\"name\":\"李娜\"}]";
List userList = MAPPER.readValue(
jsonArrayString,
new TypeReference>() {}
);
for (UserProfile u : userList) {
System.out.println("用户ID: " + u.getId() + ", 名字: " + u.getName());
}
6. 关键注意事项与工程化经验
不要在每个业务方法中都通过 HttpClient.newHttpClient() 或 new ObjectMapper() 去创建实例。这两个对象的初始化成本较高,尤其是 HttpClient 内部维系着线程池和长连接(Keep-Alive)。把它们声明为静态常量或通过依赖注入注入为单例,可以大幅避免在高并发场景下文件描述符被耗尽的问题。
除了生命周期之外,在实际网络交互中还有几个常见问题需要防范:
- 连接与读取超时必须设置: 永远不要发起未配置超时的请求。默认情况下,如果不显式指定超时,一旦远端服务器出现网络黑洞或网关假死,调用线程将会永久挂起。
- 状态码非200的处理: 服务端返回404或500错误时,HTTP请求本身通常是成功通信的(不会抛出IOException),但返回的Body往往是报错页面的HTML或特定的错误JSON。必须先判断
response.statusCode(),不要把错误信息直接塞给readValue(),否则会抛出无意义的JSON解析异常。 - 命名格式差异处理: 如果接口服务端字段采用下划线命名(如
user_name),而Java实体类采用驼峰命名(如userName),可以在属性上配置@JsonProperty("user_name"),或直接对全局Mapper配置PropertyNamingStrategies.SNAKE_CASE。
7. 核心要点梳理与技术选型对照
在实现网络请求和数据反序列化时,不同技术路线的特性对比与全流程要点总结如下:
通过这套组合,开发者无需引入笨重的外部HTTP包,只需少量标准化代码,就能在Java项目中完成高可靠的HTTP请求发起与数据解析工作。










































