在 AJAX (Entity→JSON) 或 Flex RemoteObject (Entity→ActionScript) 場景下,Hibernate 實體序列化面臨兩大殺手:
傳統 DTO/VO 轉換雖能隔離,但樣板碼量大、維護成本高。本文提供基於 Spring AOP 的 HibernateBeanSerializer 通用方案:動態代理攔截 getter,延遲屬性返回 null,已載入屬性遞歸代理,優雅切斷序列化傳播鏈。
getXxx()/isXxx():Hibernate.isPropertyInitialized())→ 返回 null,不拋異常。null。// 原始實體
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()); // 代理:現可見真實資料
public class HibernateBeanSerializer<T> {
private T proxy;
public HibernateBeanSerializer(T object, String... excludesProperties) {
if (object == null) { this.proxy = null; return; }
ProxyFactory pf = new ProxyFactory();
pf.setTargetClass(object.getClass());
pf.setTarget(object);
pf.setProxyTargetClass(true); // CGLIB 代理類而非介面
pf.addAdvice(new HibernateBeanSerializerAdvice(excludesProperties));
this.proxy = (T) pf.getProxy();
}
public T getProxy() { return proxy; }
private static class HibernateBeanSerializerAdvice implements MethodInterceptor {
private String[] excludes;
public Object invoke(MethodInvocation mi) throws Throwable {
String prop = getPropertyName(mi.getMethod().getName());
if (prop == null) return mi.proceed();
// 1. 未初始化 → null
if (!Hibernate.isPropertyInitialized(mi.getThis(), prop)) return null;
// 2. 排除清單 → null
if (isExcluded(prop)) return null;
Object val = mi.proceed();
return processReturnValue(mi.getMethod().getReturnType(), val);
}
private Object processReturnValue(Class> type, Object val) {
if (val == null || Modifier.isFinal(type.getModifiers())) return val;
if (!Hibernate.isInitialized(val)) return null;
// 遞歸包裝關聯物件
if (val instanceof HibernateProxy)
return new HibernateBeanSerializer(val).getProxy();
if (val instanceof PersistentCollection) {
// 集合/Map 每元素遞歸代理
Collection proxyColl = createCollection(type);
for (Object o : (Collection) val)
proxyColl.add(new HibernateBeanSerializer(o).getProxy());
return proxyColl;
}
return val;
}
// getPropertyName, isExcluded, createCollection 省略...
}
}
此方案已整合至 Rapid Framework v2.0,成為 Flex RemoteObject 序列化預設策略。告別 DTO 爆炸,擁抱領域模型直接序列化的輕量時代。