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

Hibernate 通用序列化方案:動態代理攔截延遲載入,防序列化炸彈

     編輯:Java 相關知識

在 AJAX (Entity→JSON) 或 Flex RemoteObject (Entity→ActionScript) 場景下,Hibernate 實體序列化面臨兩大殺手:

  1. LazyInitializationException:Session 關閉後訪問延遲屬性拋異常。
  2. 遞歸序列化整個資料庫:關聯鏈(one-to-many 等)無限展開,導致效能崩潰或堆疊溢位。

傳統 DTO/VO 轉換雖能隔離,但樣板碼量大、維護成本高。本文提供基於 Spring AOP 的 HibernateBeanSerializer 通用方案:動態代理攔截 getter,延遲屬性返回 null,已載入屬性遞歸代理,優雅切斷序列化傳播鏈。

核心原理

  • 為目標 Entity 建立 JDK/CGLIB 動態代理。
  • 攔截 getXxx()/isXxx():
    • 若屬性未初始化(Hibernate.isPropertyInitialized())→ 返回 null,不拋異常。
    • 若屬性在排除清單 → 返回 null。
    • 否則正常調用,對返回值遞歸包裝代理(集合、Map、關聯 Entity)。
  • 代理物件可安全序列化,客戶端只見已載入資料,延遲屬性呈現為 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 省略...
    }
}

優勢總結

  • 零侵入:Entity 無需實作介面、繼承基類、標註註解。
  • 通用性:適用所有序列化框架(Jackson, Gson, Flex, XStream...)。
  • 精準控制:排除清單機制靈活裁剪輸出。
  • 效能:避免不必要的資料庫查詢與物件圖遍歷。

此方案已整合至 Rapid Framework v2.0,成為 Flex RemoteObject 序列化預設策略。告別 DTO 爆炸,擁抱領域模型直接序列化的輕量時代。

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