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

利用動態代理解決 Hibernate 序列化中的延遲加載與無限遞歸問題

     編輯:Java 相關知識

將 Hibernate 實體直接序列化為 JSON 或傳給 Flex/AMF 時,常面臨兩大痛點:

  1. LazyInitializationException:Session 關閉後訪問延遲屬性拋出異常。
  2. 無限遞歸序列化:雙向關聯導致整個對象圖被序列化,效能災難。

傳統方案引入 DTO/VO 層轉換,代碼冗餘且維護成本高。本文提出基於 Spring AOP 動態代理的 HibernateBeanSerializer,攔截 getter,對未初始化屬性返回 null,對已初始化關聯遞歸生成代理,優雅解決雙重問題。

核心思路

  • 為實體對象創建動態代理 (JDK/CGLIB)。
  • 攔截 getXxx()/isXxx() 方法:
    • 若屬性未初始化 (Hibernate.isPropertyInitialized() 為 false) → 返回 null,不拋異常。
    • 若屬性在排除列表 → 返回 null,斷開遞歸。
    • 否則執行原方法,對返回值進一步處理:
      • HibernateProxy → 遞歸包裝為代理。
      • PersistentCollection (List/Set/Map) → 包裝集合並遞歸包裝元素。
      • 其他 → 直接返回。

完整實現代碼

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-framework v2.0,Flex RemoteObject 預覽版將採用此機制。DWR 的 HibernateConverter 同樣採用類似思路在序列化時遇到未初始化屬性即停止轉換。

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