記錄一次看似詭異的 InvocationTargetException 排查過程,最終通過 Gson 繞過問題,但根因值得深思,作為網絡請求異常處理的反面教材。
業務流程:通過 icid 查詢客戶密鑰 → 攜帶密鑰請求商品列表 → 解析 JSON → 遍歷匹配商品獲取 giid。
// 1. 獲取密鑰
String key = Jsoup.connect(NMApi.url() + "/Stores/findKey")
.timeout(60000)
.data("icid", icid)
.post()
.text();
if (key == null || key.isEmpty()) {
return "查詢客戶密鑰失敗";
}
// 2. 獲取商品列表
String result = Jsoup.connect(NMApi.url() + "/wh/goods")
.timeout(10000)
.data("RequestName", "wGoodsInfo")
.data("icID", icid)
.data("cKey", key)
.data("cDepot", cdepot)
.data("iPage", "1")
.data("iPagePer", "111")
.data("cqgCode", "")
.data("cqcCode", "")
.data("iqwGiid", "")
.data("nqType", "0")
.get() // 注意:此處用 GET
.text();
System.out.println("查詢商品字符串: " + result);
// 3. Gson 解析
Map<String, Object> map = new Gson().fromJson(result, new TypeToken<Map<String, Object>>(){}.getType());
ArrayList<goodsRec> reclist = (ArrayList<goodsRec>) map.get("RecList");
// 4. 遍歷匹配
String giid = "";
for (int i = 0; i < reclist.size(); i++) {
if (reclist.get(i).getCgName().equals(cgoods) || reclist.get(i).getCgCode().equals(cgoods)) {
giid = reclist.get(i).getIwGiid();
break;
}
}
if (giid.isEmpty()) return "查無此物品";
reclist.get(4) 或循環內部)拋出 InvocationTargetException| 疑點 | 分析 |
|---|---|
| HTTP 方法不匹配 | 接口文檔要求 POST,代碼用 .get() 發送 GET,導致服務端返回錯誤頁面/空數據,JSON 解析產生非預期結構 | 泛型擦除導致類型轉換失敗 | map.get("RecList") 返回 List<LinkedTreeMap>(Gson 默認),強轉 ArrayList<goodsRec> 拋 ClassCastException,被反射調用包裝為 InvocationTargetException |
| 數據結構不穩定 | 服務端可能返回錯誤碼對象而非列表,導致 RecList 為 null 或非 List 類型 |
| Jsoup 連接復用問題 | 同一 Connection 對象多次調用 post()/get() 可能攜帶殘留數據 |
// 定義標準響應結構
class ApiResponse {
List<GoodsRec> RecList;
String errorCode;
String errorMsg;
}
class GoodsRec {
String cgName;
String cgCode;
String iwGiid;
// getter/setter
}
// 業務方法
public String findGiid(String icid, String cdepot, String cgoods) {
try {
// 1. 獲取密鑰 (POST)
String key = Jsoup.connect(apiUrl + "/Stores/findKey")
.timeout(60000)
.ignoreContentType(true) // 關鍵:防非 text/html 返回拋異常
.data("icid", icid)
.post()
.text();
if (key == null || key.isEmpty()) return "查詢客戶密鑰失敗";
// 2. 查詢商品 (按文檔用 POST)
String json = Jsoup.connect(apiUrl + "/wh/goods")
.timeout(10000)
.ignoreContentType(true)
.data("RequestName", "wGoodsInfo")
.data("icID", icid)
.data("cKey", key)
.data("cDepot", cdepot)
.data("iPage", "1")
.data("iPagePer", "100")
.post() // 修正為 POST
.text();
// 3. 安全解析
ApiResponse resp = new Gson().fromJson(json, ApiResponse.class);
if (resp.RecList == null || resp.RecList.isEmpty()) return "查無商品數據";
// 4. 匹配
for (GoodsRec rec : resp.RecList) {
if (cgoods.equals(rec.cgName) || cgoods.equals(rec.cgCode)) {
return rec.iwGiid;
}
}
return "查無此物品";
} catch (Exception e) {
log.error("查詢異常", e);
return "系統異常: " + e.getMessage();
}
}
ignoreContentType(true):Jsoup 默認只接受 text/html,API 返回 application/json 會直接拋異常。Map<String,Object> + 強轉,利用 Gson 直接映射到 POJO,編譯期發現類型錯誤。getCause() 才是真兇(如 ClassCastException、NullPointerException)。「繞過問題」不是解決問題。這次通過 Gson 直接映射對象掩蓋了類型轉換錯誤,真正根因極大概率是 **HTTP 方法錯誤導致返回非預期 JSON**,配合 **Jsoup 默認 content-type 校驗** 觸發異常鏈。養成「先看網絡層、再看解析層、最後看業務層」的排查順序,能避開 90% 的坑。