Java 企业级工具类全家桶-68-HTTP请求工具类

一、工具类功能

给业务代码用的 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请求工具类」 的完整源码,拿来即用。


觉得对您有帮助,麻烦 点点关注啦 ,您的关注是我创作的最大动力~ 🎯

相关推荐
code_slave(码畜)5 小时前
微服务架构落地:基础服务 —— 报表服务(中篇:元数据、查询引擎、缓存与权限落地)
spring boot·spring cloud·缓存·微服务·架构
ZealSinger5 小时前
Boot4挂起函数丢traceId怎么修
spring boot·kotlin·协程·可观测性
code_slave(码畜)6 小时前
微服务架构落地:基础服务 —— 报表服务(AI 集成篇:AI 增强报表能力)
人工智能·spring boot·spring cloud·微服务·架构
code_slave(码畜)7 小时前
微服务架构落地:公共中间件层总览——不承载业务,只承载稳定性
spring boot·spring cloud·微服务·中间件·架构
谢亮_vipxieliang8 小时前
Spring Boot 自动配置原理:从 @SpringBootApplication 到自定义 Starter
java·spring boot·后端
paopaokaka_luck12 小时前
非遗文物数字化小程序(AI非遗问答、ONNX图像识别、协同过滤推荐、ECharts数据分析、非遗知识浏览与互动、文创商城订单闭环、文化活动报名签到、社区交流)
javascript·spring boot·mysql·数据分析·echarts·mybatis
EatFan14 小时前
Spring Boot 4 迁移避坑清单:Jackson 3、starter 拆分与最低 JDK 口径核对(含若依/芋道/CRMEB 升级对照)
java·数据库·spring boot·spring boot 4·java 21·jakarta ee 11·jackson 3
ly768914 小时前
Spring Boot 集成 Redis 企业级实践:连接池、序列化与缓存穿透雪崩的工程化防御
spring boot·redis·缓存·缓存穿透·布隆过滤器·lettuce 连接池
Wx-bishekaifayuan15 小时前
springboot陨石鉴收系统96265-计算机课程设计、毕业设计
java·vue.js·spring boot·后端·python·spring·课程设计