一、工具类功能
给业务代码用的 HTTP 客户端。对接第三方开放平台、调用内部 REST 服务时发 GET/POST 请求,拿回来的直接是业务对象(可以是单个对象、List/Map 这类泛型结构,也可以就是响应原文),不用自己解析响应报文。
面向 JSON/文本接口:响应体带 8 MiB 上限,请求超时管到响应体传输,非 2xx 会把状态码、最终地址、响应头与完整错误体一并交给你,不用于下载大文件。
二、所需环境
JDK 17或以上 + Spring Boot 3.x或以上
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
三、方法概览(按需调用)
| 序号 | 方法 | 入参 | 返回 | 用途 |
|---|---|---|---|---|
| 1 | get(url, Class) | 地址 + 目标类型 | R | GET 简参版,取单个对象 |
| 2 | get(url, TypeReference) | 地址 + 泛型类型 | R | GET 简参版,取 List/Map 等泛型结构 |
| 3 | get(url, queryParams, headers, timeout, Class) | 地址 + 查询参数 + 请求头 + 本次超时+ 目标类型 | R | GET 全参版,取单个对象 |
| 4 | get(url, queryParams, headers, timeout, TypeReference) | 地址 + 查询参数 + 请求头 + 本次超时+ 泛型类型 | R | GET 全参版,取 List/Map 等泛型结构 |
| 5 | post(url, body, Class) | 地址 + 请求体 + 目标类型 | R | POST 简参版,取单个对象 |
| 6 | post(url, body, TypeReference) | 地址 + 请求体 + 泛型类型 | R | POST 简参版,取 List/Map 等泛型结构 |
| 7 | post(url, body, queryParams, headers, timeout, Class) | 地址 + 请求体 + 查询参数 + 请求头 + 本次超时+ 目标类型 | R | POST 全参版,取单个对象 |
| 8 | post(url, body, queryParams, headers, timeout, TypeReference) | 地址 + 请求体 + 查询参数 + 请求头 + 本次超时+ 泛型类型 | R | POST 全参版,取 List/Map 等泛型结构 |
| 9 | toQueryString(Map<String,String>) | 键值对 | String | 表单请求体/查询串编码 |
四、调用示例(直观体验)
调用示例直接复制进 IDE 右键 Run 就能看到调用工具类各方法的输出结果。
java
17:52:48.525 [main] INFO com.example.order.api.HttpUtilsDemo -- 「HttpUtils GET方法1-简参版 get(url, Class)」原生返回 -> 这段不是 JSON
17:52:48.608 [main] INFO com.example.order.api.HttpUtilsDemo -- 「HttpUtils GET方法1-简参版 get(url, Class)」返回对象 -> User[name=张三, age=18]
17:52:48.620 [main] INFO com.example.order.api.HttpUtilsDemo -- 「HttpUtils GET方法2-简参版 get(url, TypeReference)」返回集合列表 -> [User[name=张三, age=18], User[name=李四, age=20]]
17:52:48.625 [main] INFO com.example.order.api.HttpUtilsDemo -- 「HttpUtils GET方法3-全参版 get(url, queryParams, headers, timeout, Class)」原生返回 -> {"name":"张三","age":18}
17:52:48.627 [main] INFO com.example.order.api.HttpUtilsDemo -- 「HttpUtils GET方法3-全参版 get(url, queryParams, headers, timeout, Class)」返回对象 -> User[name=张三, age=18]
17:52:48.629 [main] INFO com.example.order.api.HttpUtilsDemo -- 「HttpUtils GET方法4-全参版 get(url, queryParams, headers, timeout, TypeReference)」返回集合列表 -> [User[name=张三, age=18], User[name=李四, age=20]]
17:52:48.661 [main] INFO com.example.order.api.HttpUtilsDemo -- 「HttpUtils POST方法1-简参版 post(url, body, Class)」原生返回 -> {"orderId":"SO20260930001","created":true}
17:52:48.686 [main] INFO com.example.order.api.HttpUtilsDemo -- 「HttpUtils POST方法1-简参版 post(url, body, Class)」返回对象 -> CreateResult[orderId=SO20260930001, created=true]
17:52:48.696 [main] INFO com.example.order.api.HttpUtilsDemo -- 「HttpUtils POST方法2-简参版 post(url, body, TypeReference)」返回集合列表 -> [CreateResult[orderId=SO20260930002, created=true], CreateResult[orderId=SO20260930003, created=true]]
17:52:48.697 [main] INFO com.example.order.api.HttpUtilsDemo -- 「HttpUtils POST方法3-全参版 post(url, body, queryParams, headers, timeout, Class)」原生返回 -> {"name":"张三"}
17:52:48.718 [main] INFO com.example.order.api.HttpUtilsDemo -- 「HttpUtils POST方法3-全参版 post(url, body, queryParams, headers, timeout, Class)」返回对象 -> FormEcho[name=张三]
17:52:48.723 [main] INFO com.example.order.api.HttpUtilsDemo -- 「HttpUtils POST方法4-全参版 post(url, body, queryParams, headers, timeout, TypeReference)」返回集合列表 -> [CreateResult[orderId=SO20260930002, created=true], CreateResult[orderId=SO20260930003, created=true]]
17:52:48.723 [main] INFO com.example.order.api.HttpUtilsDemo -- 「HttpUtils toQueryString方法 toQueryString(Map<String,String>)」返回编码串 -> name=zhangsan&addr=xx%E8%A1%97%E9%81%93
17:52:48.747 [main] ERROR com.example.order.api.HttpUtilsDemo -- 「异常信息获取演示」非 2xx -> 状态码=404, 业务码=A0001, msg=用户不存在, 最终地址=http://127.0.0.1:61701/user/404
17:52:48.750 [main] ERROR com.example.order.api.HttpUtilsDemo -- 「异常信息获取演示」被限流 -> 状态码=429, 建议等待=30秒, 错误体={"code":"A0002","msg":"请降低调用频率"}
17:52:48.750 [main] ERROR com.example.order.api.HttpUtilsDemo -- 「异常信息获取演示」调用方式不对 -> 只支持 http/https 协议,当前为: ftp://127.0.0.1/x
17:52:48.755 [main] ERROR com.example.order.api.HttpUtilsDemo -- 「异常信息获取演示」响应体解析失败 -> 响应体解析失败,url=http://127.0.0.1:61701/text,状态码=200,响应摘要=这段不是 JSON
进程已结束,退出代码为 0
java
package com.example.order.api;
import com.example.common.utils.HttpUtils;
import com.fasterxml.jackson.core.type.TypeReference;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpServer;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* 调用示例:整个文件复制进 IDE,右键 Run 就能看效果。
*
* <p>本地用 JDK 自带的 {@link HttpServer} 起了几个假接口,所以不依赖外网、不需要 Spring 容器;
* 真实项目里这些调用写在 {@code @Service} 的方法里,写法完全一样。
* 日志用 Lombok 的 {@code @Slf4j} + SLF4J 占位符,没装 Lombok 就把 {@code log} 换成项目自己的 Logger。
*/
@Slf4j
public class HttpUtilsDemo {
/** 示例用的 DTO,实际换成你自己的 */
public record User(String name, Integer age) {
}
public record OrderDto(String sku, Integer count) {
}
public record CreateResult(String orderId, Boolean created) {
}
public record ApiError(String code, String msg) {
}
public record FormEcho(String name) {
}
public static void main(String[] args) throws IOException {
HttpServer server = startLocalApi();
String base = "http://127.0.0.1:" + server.getAddress().getPort();
try {
// ========== 一、GET方法调用示例 ==========
String text = HttpUtils.get(base + "/text", String.class);
log.info("「HttpUtils GET方法1-简参版 get(url, Class)」原生返回 -> {}", text);
User user = HttpUtils.get(base + "/user/1", User.class);
log.info("「HttpUtils GET方法1-简参版 get(url, Class)」返回对象 -> {}", user);
List<User> userList = HttpUtils.get(base + "/user/list", new TypeReference<>() {});
log.info("「HttpUtils GET方法2-简参版 get(url, TypeReference)」返回集合列表 -> {}", userList);
String raw = HttpUtils.get(base + "/user/1", Map.of("from", "app"),
Map.of("X-Trace-Id", "demo-1"), Duration.ofSeconds(5), String.class);
log.info("「HttpUtils GET方法3-全参版 get(url, queryParams, headers, timeout, Class)」原生返回 -> {}", raw);
User fullUser = HttpUtils.get(base + "/user/1", Map.of("from", "app"),
Map.of("X-Trace-Id", "demo-2"), Duration.ofSeconds(5), User.class);
log.info("「HttpUtils GET方法3-全参版 get(url, queryParams, headers, timeout, Class)」返回对象 -> {}", fullUser);
List<User> fullUserList = HttpUtils.get(base + "/user/list", Map.of("size", "2"),
Map.of("X-Trace-Id", "demo-3"), Duration.ofSeconds(5), new TypeReference<>() {});
log.info("「HttpUtils GET方法4-全参版 get(url, queryParams, headers, timeout, TypeReference)」返回集合列表 -> {}", fullUserList);
// ========== 二、POST方法调用示例 ==========
String createdRaw = HttpUtils.post(base + "/order", new OrderDto("A1", 2), String.class);
log.info("「HttpUtils POST方法1-简参版 post(url, body, Class)」原生返回 -> {}", createdRaw);
CreateResult createResult = HttpUtils.post(base + "/order", new OrderDto("A1", 2), CreateResult.class);
log.info("「HttpUtils POST方法1-简参版 post(url, body, Class)」返回对象 -> {}", createResult);
List<CreateResult> createResultList = HttpUtils.post(base + "/order/batch", List.of(new OrderDto("C3", 1),
new OrderDto("D4", 2)), new TypeReference<>() {});
log.info("「HttpUtils POST方法2-简参版 post(url, body, TypeReference)」返回集合列表 -> {}", createResultList);
String echoedRaw = HttpUtils.post(base + "/form", HttpUtils.toQueryString(Map.of("name", "张三")), null,
Map.of("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8"), null, String.class);
log.info("「HttpUtils POST方法3-全参版 post(url, body, queryParams, headers, timeout, Class)」原生返回 -> {}", echoedRaw);
FormEcho echoed = HttpUtils.post(base + "/form", HttpUtils.toQueryString(Map.of("name", "张三")), null,
Map.of("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8"), null, FormEcho.class);
log.info("「HttpUtils POST方法3-全参版 post(url, body, queryParams, headers, timeout, Class)」返回对象 -> {}", echoed);
List<CreateResult> createResList = HttpUtils.post(base + "/order/batch", List.of(new OrderDto("E5", 3)),
Map.of("channel", "app"), Map.of("X-Trace-Id", "demo-4"), Duration.ofSeconds(5), new TypeReference<>() {});
log.info("「HttpUtils POST方法4-全参版 post(url, body, queryParams, headers, timeout, TypeReference)」返回集合列表 -> {}", createResList);
// ========== 三、toQueryString方法调用示例 ==========
// 参数顺序按 Map 的迭代顺序拼,签名场景用 LinkedHashMap 保证顺序稳定
Map<String, String> queryParams = new LinkedHashMap<>();
queryParams.put("name", "zhangsan");
queryParams.put("addr", "xx街道");
String queryString = HttpUtils.toQueryString(queryParams);
log.info("「HttpUtils toQueryString方法 toQueryString(Map<String,String>)」返回编码串 -> {}", queryString);
// ========== 四、catch异常信息 ==========
// 1. 状态码非 2xx:错误体一步解成业务对象,按对方错误码分支
try {
HttpUtils.get(base + "/user/404", User.class);
} catch (HttpUtils.HttpStatusException e) {
ApiError error = e.bodyAs(ApiError.class);
log.error("「异常信息获取演示」非 2xx -> 状态码={}, 业务码={}, msg={}, 最终地址={}", e.statusCode(), error.code(), error.msg(), e.url());
}
// 2. 429 被限流:响应头里的 Retry-After 直接可读,不用自己翻报文
try {
HttpUtils.get(base + "/limited", User.class);
} catch (HttpUtils.HttpStatusException e) {
log.error("「异常信息获取演示」被限流 -> 状态码={}, 建议等待={}秒, 错误体={}", e.statusCode(),
e.header("Retry-After").orElse("未声明"), e.responseBody());
}
// 3. 参数不合法:url 协议不对、受限请求头、超时非正、请求体无法序列化
try {
HttpUtils.get("ftp://127.0.0.1/x", User.class);
} catch (IllegalArgumentException e) {
log.error("「异常信息获取演示」调用方式不对 -> {}", e.getMessage());
}
// 4. 响应体不是合法 JSON:IllegalStateException,消息里带响应摘要
try {
HttpUtils.get(base + "/text", User.class);
} catch (IllegalStateException e) {
log.error("「异常信息获取演示」响应体解析失败 -> {}", e.getMessage());
}
} finally {
server.stop(0);
}
}
/** 用 JDK 自带的 HttpServer 起本地假接口,端口交给系统分配 */
private static HttpServer startLocalApi() throws IOException {
HttpServer server = HttpServer.create(new InetSocketAddress("127.0.0.1", 0), 0);
server.createContext("/user/1", ex -> json(ex, 200, "{\"name\":\"张三\",\"age\":18}"));
server.createContext("/user/list", ex -> json(ex, 200,
"[{\"name\":\"张三\",\"age\":18},{\"name\":\"李四\",\"age\":20}]"));
server.createContext("/order", ex -> json(ex, 200,
"{\"orderId\":\"SO20260930001\",\"created\":true}"));
server.createContext("/order/batch", ex -> json(ex, 200,
"[{\"orderId\":\"SO20260930002\",\"created\":true},{\"orderId\":\"SO20260930003\",\"created\":true}]"));
server.createContext("/user/404", ex -> json(ex, 404, "{\"code\":\"A0001\",\"msg\":\"用户不存在\"}"));
server.createContext("/limited", ex -> {
ex.getResponseHeaders().add("Retry-After", "30");
json(ex, 429, "{\"code\":\"A0002\",\"msg\":\"请降低调用频率\"}");
});
server.createContext("/text", ex -> plain(ex, 200, "这段不是 JSON"));
server.createContext("/form", ex -> {
String form = new String(ex.getRequestBody().readAllBytes(), StandardCharsets.UTF_8);
String name = URLDecoder.decode(form.substring(form.indexOf('=') + 1), StandardCharsets.UTF_8);
json(ex, 200, "{\"name\":\"" + name + "\"}");
});
server.start();
return server;
}
private static void json(HttpExchange ex, int status, String body) throws IOException {
respond(ex, status, "application/json; charset=UTF-8", body);
}
private static void plain(HttpExchange ex, int status, String body) throws IOException {
respond(ex, status, "text/plain; charset=UTF-8", body);
}
private static void respond(HttpExchange ex, int status, String contentType, String body) throws IOException {
byte[] bytes = body.getBytes(StandardCharsets.UTF_8);
ex.getResponseHeaders().add("Content-Type", contentType);
ex.sendResponseHeaders(status, bytes.length);
try (OutputStream out = ex.getResponseBody()) {
out.write(bytes);
}
ex.close();
}
}
五、工具类源码(可直接复制)
📦 点击可保存并下载 「Java 企业级工具类全家桶-68-HTTP请求工具类」 的完整源码,拿来即用。
觉得对您有帮助,麻烦 点点关注啦 ,您的关注是我创作的最大动力~ 🎯