首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

Gson 循環引用序列化異常解決方案:transient 關鍵字與註解策略

     編輯:Java 綜合教學

Gson 循環引用序列化異常解決方案:transient 關鍵字與註解策略

在使用 Gson 將對象轉 JSON 時,java.lang.IllegalStateException: circular reference error 是高頻報錯,典型場景為雙向關聯實體 (如 User ↔ Order、Parent ↔ Child)。本文剖析異常堆疊,提供三種解決方案,並給出最佳實踐建議。

一、異常堆疊剖析


Caused by: java.lang.IllegalStateException: circular reference error
 Offending field: methodAccessor
 Offending object: preserveType: false, type: interface sun.reflect.MethodAccessor, obj: sun.reflect.DelegatingMethodAccessorImpl@5886f7bf
 at com.google.gson.CircularReferenceException.createDetailedException(CircularReferenceException.java:43)
 at com.google.gson.JsonSerializationVisitor.visitObjectField(JsonSerializationVisitor.java:115)
 ...
 at com.htsoft.oa.action.structuringdatamanage.TableargAction.get(TableargAction.java:142)

關鍵信息

  • Offending field: methodAccessor:觸發循環的字段名 (可能是反射生成的內部字段)。
  • TableargAction.get:142:業務代碼調用 gson.toJson(obj) 的行號。
  • Gson 默認不處理循環引用,遇到即拋異常 (Jackson 默認也拋異常,可配置)。

二、產生循環的典型代碼結構


// 實體雙向引用
public class Dept {
    private List<Emp> emps;  // 部門引用員工列表
    // getter/setter
}

public class Emp {
    private Dept dept;       // 員工引用部門 → 形成循環 Dept→Emp→Dept
    // getter/setter
}

三、三種解決方案對比

方案實現方式優點缺點適用場景
1. transient 關鍵字 private transient Dept dept; 簡單、無依賴、序列化/反序列化均忽略 字段永久不可序列化,反序列化後為 null,需手動回填 字段確實不需要 JSON 輸出 (如反向引用、臨時字段)
2. @Expose 註解 (需配置) @Expose(serialize = false) + new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create() 精確控制序列化/反序列化行為,可單獨控制 需所有類加註解,GsonBuilder 配置固定,侵入性強 需精細控制字段輸出的成熟項目
3. 自定義 TypeAdapter 實現 TypeAdapter<Dept> 手寫 JSON 邏輯 完全控制,可輸出 ID 而非完整對象,性能最優 代碼量大,維護成本高 高性能要求、複雜嵌套結構、需輸出精簡引用 (如只輸出 deptId)

四、方案一:transient 實戰 (最快修復)


public class Emp {
    private Long id;
    private String name;
    
    // 關鍵:transient 修飾反向引用
    private transient Dept dept; 
    
    // 為了反序列化後能用,提供輔助方法 (不參與序列化)
    public void setDeptRef(Dept dept) { this.dept = dept; }
    public Dept getDeptRef() { return dept; }
}

效果:gson.toJson(dept) 正常輸出,emps 中的每個 Emp 不再包含 dept 字段,循環斷開。

五、方案三:精簡引用 TypeAdapter 範例 (推薦生產)


public class DeptAdapter extends TypeAdapter<Dept> {
    @Override
    public void write(JsonWriter out, Dept dept) throws IOException {
        if (dept == null) { out.nullValue(); return; }
        out.beginObject();
        out.name("id").value(dept.getId());
        out.name("name").value(dept.getName());
        // 關鍵:只輸出員工 ID 列表,不遞歸輸出完整 Emp 對象
        out.name("empIds");
        out.beginArray();
        for (Emp emp : dept.getEmps()) out.value(emp.getId());
        out.endArray();
        out.endObject();
    }

    @Override
    public Dept read(JsonReader in) throws IOException {
        // 反序列化邏輯...
        return null; 
    }
}

// 註冊
Gson gson = new GsonBuilder()
    .registerTypeAdapter(Dept.class, new DeptAdapter())
    .create();

六、Jackson 對比 (若項目可遷移)


// Jackson 僅需一個註解
@JsonManagedReference  // 正向引用 (序列化)
public class Dept {
    @JsonManagedReference
    private List<Emp> emps;
}

@JsonBackReference   // 反向引用 (不序列化,反序列化時自動回填)
public class Emp {
    @JsonBackReference
    private Dept dept;
}

// 或全局配置
objectMapper.configure(SerializationFeature.FAIL_ON_SELF_REFERENCES, false);
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);

Jackson 對循環引用的內置支持遠優於 Gson,建議新項目優先選 Jackson。

七、避坑檢查清單

  1. ✅ 實體類設計時識別雙向關聯,標記反向引用為 transient 或規劃 TypeAdapter。
  2. ✅ 使用 GsonBuilder().setExclusionStrategies(...) 統一排除某類型字段 (如所有 MethodAccessor 類型)。
  3. ✅ 單元測試覆蓋:雙向關聯、自引用、多層嵌套循環。
  4. ✅ 異常日誌中定位 Offending field,精準修改對應字段。
  5. ❌ 直接捕獲異常並返回空字符串 — 掩蓋數據完整性問題。

八、一鍵排除所有反射相關字段 (應急通用方案)


Gson gson = new GsonBuilder()
    .setExclusionStrategies(new ExclusionStrategy() {
        @Override
        public boolean shouldSkipField(FieldAttributes f) {
            // 跳過所有 sun.reflect 內部類型字段
            return f.getDeclaringClass().getName().startsWith("sun.reflect.") 
                || f.getName().contains("methodAccessor");
        }
        @Override
        public boolean shouldSkipClass(Class clazz) {
            return clazz.getName().startsWith("sun.reflect.");
        }
    })
    .create();

循環引用本質是對象圖非樹狀結構,JSON 作為樹狀數據格式無法直接表示。通過 transient 斷開引用鏈、@Expose 精細控制、TypeAdapter 輸出精簡引用三種手段,配合異常日誌定位 Offending field,能快速修復線上序列化崩潰。長期看,建議實體層設計避免雙向強引用,或遷移至 Jackson 獲得原生循環引用支持。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved