Struts2 使用 JSON 插件回傳資料時,若 Action 中的 Map<String, Object> 放入包含雙向關聯或循環引用的領域物件(如 Hibernate 實體),org.apache.struts2.json.JSONWriter 遞迴遍歷物件圖時導致 java.lang.StackOverflowError。
java.lang.StackOverflowError
at sun.reflect.misc.ReflectUtil.checkPackageAccess(ReflectUtil.java:177)
at java.beans.Introspector.getBeanInfo(Introspector.java:154)
at org.apache.struts2.json.JSONWriter.bean(JSONWriter.java:177)
at org.apache.struts2.json.JSONWriter.process(JSONWriter.java:160)
at org.apache.struts2.json.JSONWriter.value(JSONWriter.java:126)
at org.apache.struts2.json.JSONWriter.add(JSONWriter.java:352)
at org.apache.struts2.json.JSONWriter.bean(JSONWriter.java:215)
... (無限遞迴)
public class LogAction extends ActionSupport {
private Map<String, Object> dataMap = new HashMap<>();
public String list() {
dataMap.clear();
PageParameter page = new PageParameter();
// ... 查詢邏輯 ...
List<UserSessionlog> sessionlogList = userService.findlog(...);
dataMap.put("page", page); // 包含集合引用
dataMap.put("sessionlogList", sessionlogList); // 列表元素可能反向引用 page
return "success";
}
}
JSONWriter 使用 JavaBeans Introspector 內省物件屬性| 方案 | 優點 | 缺點 |
|---|---|---|
| 移除循環引用物件(如範例移除 page) | 簡單直接 | 損失資料完整性 |
| 使用 DTO/VO 替代領域物件 | 解耦表現層與領域層,可控序列化內容 | 需額外轉換程式碼 |
| 配置 JSON 插件排除屬性 | 非侵入式 | Struts2 JSON 插件支援有限 |
| 改用 Jackson/Gson 並啟用循環檢測 | 功能強大,@JsonIgnore、@JsonManagedReference/@JsonBackReference 完整支援 | 需整合第三方庫 |
// 專用傳輸物件,無循環引用
public class LogPageDTO {
private List<UserSessionlogVO> logs;
private int totalCount, totalPage, currentPage;
// Getter/Setter
}
// Action 中轉換
public String list() {
LogPageDTO dto = new LogPageDTO();
dto.setLogs(convertToVOList(sessionlogList));
dto.setTotalCount(totalCount);
// ...
dataMap.put("dto", dto);
return "success";
}
若必須直接序列化領域物件,可嘗試在 struts.xml 配置 excludeProperties 排除導致循環的屬性,或實作自定義 JSONInterceptor 覆寫序列化邏輯。但長期來說,DTO 模式是架構正確的選擇。