资讯动态

Java发送POST JSON请求的四种主流写法与避坑指南

发布时间:2026/10/15 17:00:46 来源:尧图企业网站定制
Java 开发里发个HTTP请求这件事看起来简单实际坑多得离谱。尤其对接第三方接口最常见的就是POST一个JSON包体过去等对方回包。我这些年做的各种系统里几乎每个都逃不过这个环节上报业务数据、调用支付回调、同步会员信息、调用算法服务打分……全是POST JSON。这个需求本身不难难点在于发送这一下里藏着编码、超时、连接复用、异常处理、日志脱敏这些细节。这篇文章我把几种主流写法、背后的原理和踩过的坑都整理出来从原生JDK到第三方库看这一篇基本够用。适合谁看刚开始接触接口对接的初中级Java开发或者项目里想统一HTTP请求工具、却不知道选哪种方案的开发者。我尽量讲清楚为什么这么做而不是只丢一段代码让你复制。1. 方案选型为什么有这么多发送POST JSON的方式用Java发HTTP请求方式多到让人选择困难老牌的HttpURLConnection、Apache HttpClient、OkHttp还有JDK 11开始自带的java.net.http.HttpClient。其实这不叫混乱而是技术演进的正常结果不同时代的项目、不同场景的需求催生了不同定位的客户端。先说选型的底层逻辑。HttpURLConnection是JDK 1.1就有的玩意儿优点是无依赖任何一个Java环境都能用但API设计非常古老写起来啰嗦而且它的连接复用机制、超时控制、响应流处理都需要开发者自己非常小心否则连接池泄漏、连接不释放的问题就会找上门。Apache HttpClient是老牌功能型选手依赖比较多API历来偏厚重但它的连接池管理、重试策略、HTTP协议支持非常完善很多传统企业项目和Spring老版本环境下都是标配。OkHttp是后起之秀设计上走的是简洁路线API直观性能好还内置了对HTTP/2、WebSocket的支持现在不少开源SDK内部用的都是它。JDK 11的HttpClient则是官方想终结这个第三方库林立局面的尝试零依赖、API设计现代支持异步发送对只用标准JDK的新项目来说很友好。我的建议是这样的老项目维护、需要精细控制连接池的用Apache HttpClient新项目且允许引入第三方库的优先OkHttp不想引任何依赖、又跑在JDK 11的直接用原生HttpClient。别盲目追新也别固守老一套。下面我分别把三种主流方案的完整代码和关键细节展开最后再给你一张对比表方便你直接做决定。1.1 三种主流HTTP客户端的定位差异为了更直观理解我把它们摊开对比客户端依赖API风格核心优势推荐场景HttpURLConnectionJDK自带原生、繁琐零依赖、兼容性最好简单脚本、无第三方库的严格环境Apache HttpClient需引入功能丰富连接池、重试、协议支持完备企业级项目、Spring老版本体系OkHttp需引入简洁流畅高性能、HTTP/2、API友好新项目、SDK封装、Android/Java通用JDK 11 HttpClientJDK 11现代流畅零依赖、支持同步/异步新项目且不想引入第三方库这四种我都实际用过。如果你的项目已经用Spring Boot很多人说直接用RestTemplate不就行了这里我多说一句RestTemplate底层默认使用的就是JDK的HttpURLConnection或可以替换成其他Client实现它对连接池、超时的控制能力其实完全取决于你给它配了哪个底层Client。真正对性能有要求的接口调用建议还是直接用OkHttp或Apache HttpClient而不是在RestTemplate上绕一层。1.2 选型时真正该关注的三个指标选HTTP客户端别只看谁代码短要盯三个点一是连接池复用能力。每次请求都new一个连接在高并发下TCP握手开销会直接拖垮接口。OkHttp和Apache HttpClient都内置连接池而HttpURLConnection虽然也支持keep-alive但需要你主动规范地使用否则容易出现连接泄漏。二是超时控制粒度。很多坑都出在超时了但没设置对层级。连接超时connectTimeout、读取超时readTimeout、写超时writeTimeout是三个不同概念有的库还区分总超时。我见过不少同学只设置了连接超时结果接口卡住时线程在那儿干等半天。三是异常处理与重试的灵活性。第三方接口总会抖动发请求的代码必须能区分网络没通服务器返回5xx业务返回错误码这三种情况。有些库的自动重试机制可能会在POST这种非幂等请求上闯祸——重发一个下单请求导致用户被扣两次款。这一点我在第5章详细讲。2. 原生HttpURLConnection不引入任何依赖的写法先把这个最原汁原味的写法讲透。不是因为它最好而是它的每一步都暴露了HTTP协议的真实工作过程理解它之后你再去看任何封装库都会觉得豁然开朗。2.1 最精简的POST JSON代码假设你要发送这样一个JSON{username:hello,score:99}目标地址是https://api.example.com/report下面是完整的原生写法import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URL; import java.nio.charset.StandardCharsets; import java.io.BufferedReader; import java.io.InputStreamReader; String json {\username\:\hello\,\score\:99}; URL url new URL(https://api.example.com/report); HttpURLConnection conn (HttpURLConnection) url.openConnection(); // 关键点1设置请求方法 conn.setRequestMethod(POST); // 关键点2声明请求体是JSON格式且使用UTF-8编码 conn.setRequestProperty(Content-Type, application/json; charsetUTF-8); // 关键点3允许输出请求体这一步容易漏 conn.setDoOutput(true); // 超时设置连接超时5秒读取超时10秒 conn.setConnectTimeout(5000); conn.setReadTimeout(10000); // 尝试获取输出流并写入JSON try (OutputStream os conn.getOutputStream()) { byte[] input json.getBytes(StandardCharsets.UTF_8); os.write(input, 0, input.length); os.flush(); } // 读取响应码和响应体 int responseCode conn.getResponseCode(); StringBuilder response new StringBuilder(); try (BufferedReader br new BufferedReader( new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8))) { String line; while ((line br.readLine()) ! null) { response.append(line); } } conn.disconnect();这段代码虽然短但每一步都有讲究。setDoOutput(true)是个大坑点在HttpURLConnection里一旦设置了doOutputtrue请求方法就会自动变成POST所以如果你想把代码写得严谨可以显式设置setRequestMethod(POST)避免依赖这个隐式行为。另外conn.disconnect()别误以为会立即断开TCP连接在keep-alive机制下它更多是标记连接可以归还给底层的连接管理器但这个方法在HttpURLConnection里并不像很多同学想象的那么关键真正关键的是用完流就关闭这决定了连接能否被复用。2.2 编码与Content-Type的正确姿势这里必须多说一句Content-Type和字符编码是两个维度别混为一谈。Content-Type: application/json声明的是我这个请求体是JSON格式charsetUTF-8声明的是JSON里的字符以UTF-8编码。服务端解析时如果只看到application/json而没看到charset很多框架会默认按UTF-8处理但遇到一些老旧的或约定不规范的接口就可能出现中文乱码。我踩过最典型的坑发生在对接一个C系服务端写的接口时对方文档写着接收application/json我老老实实只设了这个头发送的JSON里恰好有中文用户两个字对方收到后乱码。排查半天发现是服务端用了默认的ISO-8859-1去解码最终解决方案就是客户端显式写Content-Type: application/json; charsetUTF-8同时把json.getBytes(StandardCharsets.UTF_8)写清楚。记住两端编码必须一致这个一致既包括HTTP头声明的charset也包括字节流实际使用的编码。2.3 原生写法的高频翻车点原生写法翻车点非常集中我列几个最常见的忘记设置setDoOutput(true)请求发出去了但服务端收不到包体或者收到空包体排查半天发现在这。用println写JSONos.write((json \n).getBytes())这种写法多出来的换行符可能让严格校验JSON格式的服务端直接返回400。把readTimeout设成了0参数为0表示无限等待一旦对端不返回线程就永远挂住。生产环境务必设置合理的读取超时。用getInputStream()读错误响应服务端返回4xx/5xx时getInputStream()会抛FileNotFoundException导致很多初学者误以为接口崩了。正确做法是响应码400时用conn.getErrorStream()读取错误体。最后一个坑值得展开。我在项目里见过不少同学这样写br new BufferedReader(new InputStreamReader(conn.getInputStream()))然后一旦接口返回500程序直接抛异常连错误日志都看不到。修正思路是写一个统一的响应读取方法先判断响应码再决定读getInputStream()还是getErrorStream()把错误详情打印出来。这个习惯养成了后面排查联调问题会省很多时间。3. 第三方库与原生HttpClient三种主流通用写法如果你觉得原生写法太啰嗦那接下来这部分应该是你的菜。我用三种方式实现同一个需求发送POST JSON并获取响应每种都给出可直接复制的核心代码同时讲清楚各自的注意事项。3.1 OkHttp简洁且高性能的新时代选择目前我最推荐新项目使用OkHttp它的API设计非常自然构建一个Request指定URL、请求体、头部然后交给OkHttpClient执行。代码如下import okhttp3.MediaType; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.Response; import java.util.concurrent.TimeUnit; // 单例模式构建Client避免每次请求都创建 OkHttpClient client new OkHttpClient.Builder() .connectTimeout(5, TimeUnit.SECONDS) .readTimeout(10, TimeUnit.SECONDS) .writeTimeout(10, TimeUnit.SECONDS) .retryOnConnectionFailure(true) .build(); String json {\username\:\hello\,\score\:99}; MediaType JSON MediaType.parse(application/json; charsetutf-8); RequestBody requestBody RequestBody.create(JSON, json); Request request new Request.Builder() .url(https://api.example.com/report) .post(requestBody) .build(); try (Response response client.newCall(request).execute()) { if (!response.isSuccessful()) { System.err.println(HTTP error code: response.code()); } String responseBody response.body() null ? : response.body().string(); System.out.println(responseBody); }OkHttp需要注意的细节有四个。第一OkHttpClient建议全局复用因为它是连接池和线程池的持有者每次new会重复创建线程池浪费资源。第二response.body().string()只能调用一次之后流就被消费完了再调就是空字符串或报错要想保存响应内容必须赋给变量。第三OkHttp 4.x的RequestBody.create签名变了旧式RequestBody.create(JSON, json)在新版本中已经调整为json.toRequestBody(JSON)这种扩展函数式写法如果你用的Kotlin或者较新版本Java注意别照抄旧代码。第四retryOnConnectionFailure默认是开启的但它重试的是连接失败场景HTTP 500这种服务器错误并不会自动重试这个行为要心里有数。3.2 Apache HttpClient老牌稳定但API略沉重Apache HttpClient的版本演进也坑过不少人现在主流是5.x4.x在老项目里依旧大量存在。我用5.x版本写一遍老项目用的4.x写法其实差别不大核心思路相同。import org.apache.hc.client5.http.classic.methods.HttpPost; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; import org.apache.hc.client5.http.impl.classic.HttpClients; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.io.entity.EntityUtils; import org.apache.hc.core5.http.io.entity.StringEntity; CloseableHttpClient client HttpClients.createDefault(); HttpPost post new HttpPost(https://api.example.com/report); post.setHeader(Content-Type, application/json; charsetUTF-8); String json {\username\:\hello\,\score\:99}; StringEntity entity new StringEntity(json, ContentType.APPLICATION_JSON); post.setEntity(entity); try (CloseableHttpResponse response client.execute(post)) { int statusCode response.getCode(); String respBody EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8); System.out.println(Status: statusCode); System.out.println(Body: respBody); } client.close();Apache HttpClient的核心要点一个是连接池管理另一个是响应实体释放。上面代码用了try-with-resourcesCloseableHttpResponse会被自动关闭但response.getEntity()的流有时候不会跟着Response一起关稳妥做法是调用EntityUtils.toString()它会帮我们把整个实体读出来并释放底层流。如果不做这一步在高并发场景下很容易把连接池占满后续请求全部排队超时。另外HttpClients.createDefault()创建的客户端内部用的是PoolingHttpClientConnectionManager默认最大连接数还要看具体版本生产环境建议显式配置PoolingHttpClientConnectionManager cm new PoolingHttpClientConnectionManager(); cm.setMaxTotal(200); cm.setDefaultMaxPerRoute(50); CloseableHttpClient client HttpClients.custom().setConnectionManager(cm).build();这个配置能极大提升接口调用吞吐量。很多人用Apache HttpClient说性能不行八成是没配连接池、或者每次请求都new一个Client压根没把连接复用的能力用起来。3.3 JDK 11原生HttpClient零依赖的现代方案如果你的项目已经跑在JDK 11以上又不想因一个HTTP请求引入一堆第三方依赖那java.net.http.HttpClient是最佳选择。它的API设计吸取了不少OkHttp的精华支持同步发送和异步发送代码也是这几个方案里最干净的import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.time.Duration; HttpClient client HttpClient.newBuilder() .connectTimeout(Duration.ofSeconds(5)) .build(); String json {\username\:\hello\,\score\:99}; HttpRequest request HttpRequest.newBuilder() .uri(URI.create(https://api.example.com/report)) .timeout(Duration.ofSeconds(10)) .header(Content-Type, application/json) .POST(HttpRequest.BodyPublishers.ofString(json, StandardCharsets.UTF_8)) .build(); // 同步发送 HttpResponseString response client.send(request, HttpResponse.BodyHandlers.ofString()); System.out.println(response.statusCode()); System.out.println(response.body()); // 异步发送 client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) .thenAccept(resp - System.out.println(resp.body()));这里要特别注意BodyPublishers.ofString的重载选择。ofString(String)不显式指定编码时底层按UTF-8处理这在大多数场景没问题但为了和Content-Type里的charset保持严格一致我还是推荐写成ofString(json, StandardCharsets.UTF_8)。另外这个原生HttpClient默认不会自动重试也不会额外做连接池调优它的设计哲学更偏向可控所以生产环境用的时候超时、重试这些策略要自己包一层。3.4 三种实现方式对比小结实现方式依赖代码量连接池异步支持推荐指数个人向HttpURLConnection无较多弱无2星除严格环境外不推荐Apache HttpClient有中等强有4星老项目首选OkHttp有少强有Call5星新项目首选JDK 11 HttpClient无JDK自带少内置有4星JDK 11零依赖首选4. JSON序列化与反序列化别在请求体上栽跟头发POST JSON请求绕不开一个前置步骤把Java对象变成JSON字符串再把响应JSON变回Java对象。这个环节的坑往往比HTTP请求本身还多。我见过太多接口联调对不上的问题最后定位下来全是序列化细节没处理好。4.1 请求体序列化的三个细节先看一个最常见的反例。假设我们有个ReportData类public class ReportData { private String username; private Integer score; // getter/setter 略 }如果直接用对象序列化得到的是{username:hello,score:99}看着很正常。但一旦score为null不同JSON库的默认行为就分叉了fastjson2默认输出{username:hello}也就是null字段默认不输出。Jackson默认输出{username:hello,score:null}null字段是会被带上的。Gson默认输出{username:hello,score:null}同样带上null字段。这个差异的杀伤力在于如果对方接口的校验逻辑是包体里出现了score字段、但值为null就报参数错误那么Jackson和Gson的默认行为就会导致请求失败反过来如果对方强制要求某些字段必须出现在JSON里即使是null也要出现那fastjson2的默认行为就不够了需要自己配置序列化策略。我个人处理原则是业务字段能保证非空的就明确赋值字段确实可能为空的提前想好对方接口到底认不认null而不是赌默认行为。第二个细节是日期格式。Java 8的LocalDateTime序列化时不同库默认输出完全不同。fastjson2默认输出类似2025-06-01 12:00:00Jackson输出的是数组或时间戳Gson也各有不同。而第三方接口的日期格式通常只有两三种规定yyyy-MM-dd HH:mm:ss、yyyy-MM-ddTHH:mm:ss.SSSZ或毫秒时间戳。生产环境最稳的做法是在DTO的日期字段上显式加JsonFormatJackson或JSONField(formatyyyy-MM-dd HH:mm:ss)fastjson2让格式完全受控不要依赖全局默认配置。第三个细节是类型不一致。如果你把一个Integer类型的字段序列化出去但对方接口文档要求的其实是字符串那服务端接到的就是{score:99}而不是{score:99}一部分严格的接口会直接报类型解析失败。这类问题看日志时往往很难发现因为在服务端的报错信息里它只会说字段类型不匹配而不会告诉你JSON的原始字符串长什么样。4.2 响应解析的容错处理响应解析同样要稳。常见做法是写一个统一的响应处理逻辑原则是先保留原始响应字符串再解析解析失败时能把原始响应打出来方便排查。不要一上来就JSON.parseObject(respBody, Xxx.class)一旦报错你连服务端返回了个啥都不知道。String respBody response.body().string(); System.out.println(raw response: respBody); // 生产环境建议用日志 try { ApiResult result JSON.parseObject(respBody, ApiResult.class); // 业务处理 } catch (Exception e) { // 解析失败保留原始响应 log.error(response parse error, raw{}, respBody, e); }还有一个容易忽略的点接口返回的JSON可能带BOM头。某些服务端框架生成的响应在最前面会有一个看不见的\uFEFF字符直接parseObject会报无法识别的字符而且报错信息非常误导人。碰到这种情况解析前先respBody respBody.replace(\uFEFF, )或者用if (respBody.startsWith(\uFEFF))做个判断。4.3 使用哪种JSON库先说结论新项目我推荐用fastjson2或者Jackson二选一别混用。Gson虽然简单但在复杂泛型解析、多态场景下不如前两者灵活。fastjson2是原fastjson的升级版修复了大量老版本的漏洞性能也很强Jackson则是Spring Boot默认集成的库如果项目已经用了Spring Boot直接用Jackson零额外依赖。选择的标准只有一个团队里最熟悉哪个、项目基础设施依赖哪个就用哪个。JSON库这种东西不值得为性能差一点而换来换去混着用才会出问题。5. 从能跑到能用封装、超时、重试与日志把POST JSON发出去只是第一步。真实生产环境的接口调用需求远比发出去复杂我在这一节把工程化必须考虑的四件事讲清楚。5.1 统一封装HTTP请求工具类的边界一个合格的HTTP工具类不应该只是把发送POST JSON这个方法抽出来而应该在更高维度上做个抽象。我推荐的最小封装层级是这样底层是HTTP客户端OkHttp或Apache HttpClient的单例。中间层是请求模板统一处理超时、Content-Type、序列化、日志、重试。最上层是业务调用方只关心传个DTO进去拿到DTO出来。public class HttpJsonUtil { private static final OkHttpClient CLIENT new OkHttpClient.Builder() .connectTimeout(5, TimeUnit.SECONDS) .readTimeout(15, TimeUnit.SECONDS) .build(); public static T T postJson(String url, Object request, ClassT responseType) { String requestJson JSON.toJSONString(request); RequestBody body RequestBody.create( MediaType.parse(application/json; charsetutf-8), requestJson); Request req new Request.Builder().url(url).post(body).build(); long start System.currentTimeMillis(); try (Response response CLIENT.newCall(req).execute()) { String respBody response.body() null ? : response.body().string(); long cost System.currentTimeMillis() - start; log(url, requestJson, response.code(), respBody, cost); return JSON.parseObject(respBody, responseType); } } private static void log(String url, String requestJson, int code, String respBody, long cost) { // 统一日志格式注意脱敏 System.out.printf(HTTP POST url%s cost%dms code%d req%s resp%s%n, url, cost, code, maskSensitive(requestJson), maskSensitive(respBody)); } }这个封装的边界是只处理通用逻辑不处理具体业务。业务错误码的判断、数据转换都放到上层去做。封装得太重反而难用比如把所有接口的响应都强行转成统一Result对象会导致你对接那些不规范的第三方接口时处处要适配得不偿失。5.2 重试策略不是所有请求都能重试POST请求的重试是个高风险动作因为它不像GET那样天然幂等。你给用户下单接口发一个POST如果第一次超时了但其实服务端已经处理成功重试就会导致重复下单。我的处理原则是三条只对连接失败、超时这类网络层异常做重试对4xx/5xx业务异常不做无脑重试。重试必须配合幂等键请求头里带一个客户端生成的requestId或idempotency-key服务端至少可以据此判断是否已经处理过。重试次数和间隔要克制一般最多重试2-3次间隔用指数退避比如1秒、2秒、4秒而不是立刻重试三次。代码实现上如果是OkHttp我在它的基础上包一层循环即可int maxAttempts 3; int attempt 0; IOException lastException null; while (attempt maxAttempts) { try (Response response CLIENT.newCall(request).execute()) { // 2xx、3xx直接返回4xx、5xx不重试 if (response.isSuccessful()) return response; // 503可以重试一次400这种别重试 if (response.code() ! 503) return response; } catch (IOException e) { lastException e; } attempt; Thread.sleep((long) Math.pow(2, attempt) * 1000); }注意503 Service Unavailable是一种常见的可以被重试的响应但即使如此重试逻辑也要放在业务层面按接口性质去决定工具层不要自动对所有POST做重试。5.3 日志与脱敏联调排障的第一依赖接口联调时日志是唯一的线索。我一般要求发请求的工具类必须打出这几个信息请求URL、请求JSON体、响应状态码、响应体、耗时。这五样缺一个出问题时排查效率就大打折扣。但日志里也藏着信息安全问题。请求体里如果带手机号、身份证、密码、token这些字段直接全量打印到日志里日志一旦被拖库就是安全事故。我常用的做法是做一个maskSensitive方法把JSON字符串里敏感字段的值替换成***private static String maskSensitive(String json) { if (json null) return ; String[] sensitiveKeys {password, token, mobile, idCard}; String masked json; for (String key : sensitiveKeys) { masked masked.replaceAll( (\ key \\\s*:\\s*\)[^\]*(\), $1***$2); } return masked; }这个方法简单粗暴但够用。生产环境建议把日志级别做成可配置的联调时开DEBUG正常运行开INFO避免日志量过大。5.4 超时设置的工程化建议超时不是随便填的。要根据接口耗时规律去设置我的经验值是连接超时3-5秒。连不上基本就是网络或服务地址问题没必要等太久。读取超时取决于业务。常规接口给10-15秒批量处理类接口可以给30秒到60秒但别超过60秒超过就该怀疑接口设计有问题。写超时一般5-10秒。只在服务端接收很慢的场景才会触发写超时。还有一个隐含的超时容易被忽略DNS解析超时。默认的DNS解析时间是操作系统决定的如果对方域名解析卡住整个请求可能卡几分钟。OkHttp里可以通过dns()方法指定一个带超时的Dns实现不过大部分场景不需要这么细但你要知道有这一层。6. 高频问题排查与避坑清单最后把这些年遇到的高频问题整理成一张速查表。每种问题我都给过真实教训部分问题在之前的章节里提过这里做收拢方便你以后直接对照排查。问题现象可能原因排查思路与解法服务端收到空包体没设setDoOutput(true)请求体没写入流原生写法重点检查doOutput第三方库检查Request.Body是否为空中文乱码客户端编码与服务端解码不一致统一UTF-8Content-Type带charsetUTF-8且字节流按UTF-8取报错响应不是JSON格式服务端返回了HTML错误页返回的JSON带BOM头先打印原始响应respBody.replace(\uFEFF,)连接池耗尽请求排队超时响应流没关闭连接未归还用try-with-resourcesEntityUtils.toString释放实体流偶发超时换一台机器又好了DNS解析慢连接池里有过期连接设置合理的DNS超时连接池做空闲连接校验签名校验失败拼接签名串时用了带格式的JSON和服务端规范不一致Content-Type多了charset对齐签名规范签名时用固定序列化格式不要用默认toString服务端收到null字段报错不同JSON库对null字段的序列化行为不同明确配置IncludeNull或不输出null参考4.1节接口返回401/403token过期、时间偏移检查客户端与服务器时间一致性token刷新逻辑别放request里读取响应时抛FileNotFoundException原生写法读错误响应错用getInputStream()4xx/5xx用getErrorStream()读取响应报文过大内存暴涨用body().string()一把梭读进内存大响应改用流式读取或限制最大读取长度这十类问题覆盖了我这几年接口对接实战里九成的踩坑场景。看起来每个问题都不复杂但它们组合起来的时候排查链路会非常长。我个人的经验是遇到问题先把原始请求和原始响应打印出来永远先确认发出的东西对不对再去看回来的东西怎么解析。绝大对数联调问题最后都能定位到发出去的JSON不是我以为的那个JSON或者服务端返回的不是我以为的那个JSON这两句话上。另外再补充一个很多开发者忽略的细节本地环境、测试环境、生产环境的HTTP Client配置应当一致。我遇到过本地接口一切正常一到服务器上就疯狂超时的场景最后发现是服务器上用的老JDK版本、底层TLS协议不匹配而本地用的新JDK自动就升级了协议。所以部署到新环境第一件事是先用最简单的curl测通接口再上代码这能帮你把代码问题和环境问题快速剥离开。7. 我在实际项目里的最终方案讲了这么多分享一下我现在写新代码时的默认选择算是个人的标准答案。如果JDK版本允许我优先用OkHttp fastjson2的组合。OkHttp管HTTP通信fastjson2管JSON序列化两者都很轻、文档全、坑都有前人踩过。旧项目里如果是Spring Boot体系我不会刻意引入OkHttp直接用RestTemplate 底层替换为HttpComponentsClientHttpRequestFactory也就是Apache HttpClient再把连接池配好也能跑得很稳。JDK 11的零依赖场景我会用原生HttpClient但会在外面包一层超时和重试。关于工具类的封装我最后强调一点工具类只做HTTP通信和JSON转换绝不做业务状态判断。业务代码要能从调用成功和业务成功两个维度分开看待——HTTP 200不代表业务处理成功很多接口会用200包着{code:500,msg:失败}这种结构所以工具类返回原始解析后的对象让业务层去判断code字段这个边界一旦守住代码会清爽很多。这套方案稳定运行在我负责的多个项目里。HTTP请求不是什么高深技术但把细节抠到极致能帮你在接口联调时节省一大半的排查时间。希望这篇能帮你少踩几个坑。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑