將 Hibernate 實體直接序列化為 JSON 或傳給 Flex/AMF 時,常面臨兩大痛點:
傳統方案引入 DTO/VO 層轉換,代碼冗餘且維護成本高。本文提出基於 Spring AOP 動態代理的 HibernateBeanSerializer,攔截 getter,對未初始化屬性返回 null,對已初始化關聯遞歸生成代理,優雅解決雙重問題。
getXxx()/isXxx() 方法:Hibernate.isPropertyInitialized() 為 false) → 返回 null,不拋異常。null,斷開遞歸。package cn.org.rapid_framework.util;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.hibernate.Hibernate;
import org.hibernate.collection.PersistentCollection;
import org.hibernate.proxy.HibernateProxy;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.util.StringUtils;
import java.lang.reflect.Modifier;
import java.util.*;
public class HibernateBeanSerializer<T> {
private T proxy;
public HibernateBeanSerializer(T object, String... excludesProperties) {
if (object == null) {
this.proxy = null;
} else {
ProxyFactory pf = new ProxyFactory();
pf.setTargetClass(object.getClass());
pf.setOptimize(true);
pf.setTarget(object);
pf.setProxyTargetClass(true); // 強制 CGLIB
pf.setOpaque(true);
pf.setExposeProxy(true);
pf.setPreFiltered(true);
HibernateBeanSerializerAdvice advice = new HibernateBeanSerializerAdvice();
advice.setExcludesProperties(excludesProperties);
pf.addAdvice(advice);
this.proxy = (T) pf.getProxy();
}
}
public T getProxy() { return proxy; }
private static class HibernateBeanSerializerAdvice implements MethodInterceptor {
private String[] excludesProperties = new String[0];
public void setExcludesProperties(String[] excludesProperties) {
this.excludesProperties = excludesProperties == null ? new String[0] : excludesProperties;
}
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
String propertyName = getPropertyName(mi.getMethod().getName());
Class<?> returnType = mi.getMethod().getReturnType();
if (propertyName == null) return mi.proceed(); // 非 getter
// 1. 未初始化 → null
if (!Hibernate.isPropertyInitialized(mi.getThis(), propertyName)) {
return null;
}
// 2. 排除列表 → null
if (isExclude(propertyName)) {
return null;
}
Object returnValue = mi.proceed();
return processReturnValue(returnType, returnValue);
}
private Object processReturnValue(Class<?> returnType, Object returnValue) {
if (returnValue == null) return null;
// final 類型 (String, Integer 等) 直接返回
if (Modifier.isFinal(returnType.getModifiers())) return returnValue;
// 未初始化的集合/代理 → null
if (!Hibernate.isInitialized(returnValue)) return null;
// HibernateProxy → 遞歸代理
if (returnValue instanceof HibernateProxy) {
return new HibernateBeanSerializer(returnValue).getProxy();
}
// PersistentCollection → 包裝集合並遞歸包裝元素
if (returnValue instanceof PersistentCollection) {
Collection<Object> proxyCollection;
if (returnType.isAssignableFrom(Set.class)) proxyCollection = new LinkedHashSet<>();
else if (returnType.isAssignableFrom(List.class)) proxyCollection = new ArrayList<>();
else if (returnType.isAssignableFrom(Map.class)) {
Map<Object, Object> proxyMap = new LinkedHashMap<>();
for (Map.Entry<, > entry : ((Map<, >) returnValue).entrySet()) {
proxyMap.put(entry.getKey(), new HibernateBeanSerializer(entry.getValue()).getProxy());
}
return proxyMap;
} else return returnValue;
for (Object o : (Collection<?>) returnValue) {
proxyCollection.add(new HibernateBeanSerializer(o).getProxy());
}
return proxyCollection;
}
return returnValue;
}
private boolean isExclude(String propertyName) {
for (String ex : excludesProperties) if (ex.equals(propertyName)) return true;
return false;
}
private String getPropertyName(String methodName) {
if (methodName.startsWith("get")) return StringUtils.uncapitalize(methodName.substring(3));
if (methodName.startsWith("is")) return StringUtils.uncapitalize(methodName.substring(2));
if (methodName.startsWith("set")) return StringUtils.uncapitalize(methodName.substring(3));
return null;
}
}
}
// 獲取原始實體 (含延遲關聯 resource)
Role role = roleDao.getById(1L);
// 生成序列化安全代理
Role proxyRole = new HibernateBeanSerializer<Role>(role).getProxy();
assertNotNull(role.getResource()); // 原始對象可訪問 (若已加載)
assertNull(proxyRole.getResource()); // 代理返回 null (未初始化)
Hibernate.initialize(role.getResource()); // 手動初始化
assertNotNull(proxyRole.getResource()); // 代理現可訪問
| 問題 | 傳統 DTO 方案 | 動態代理方案 |
|---|---|---|
| 延遲加載異常 | 需在 Service 層手動初始化或編寫 DTO 轉換 | 自動返回 null,零侵入 |
| 無限遞歸 | 需精心設計 DTO 斷開循環 | 配置 excludesProperties 或自動檢測已初始化狀態 |
| 代碼維護 | 每新增實體需寫 DTO 及轉換邏輯 | 通用序列化器,零樣板代碼 |
| 靈活性 | 靜態結構,難以動態調整 | 運行時可指定排除屬性,支援多場景 |
此方案已整合進
rapid-frameworkv2.0,Flex RemoteObject 預覽版將採用此機制。DWR 的HibernateConverter同樣採用類似思路在序列化時遇到未初始化屬性即停止轉換。