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

EJB 訪問代理模式最佳實踐:封裝 JNDI 查找與反射調用,實現統一獲取 Remote/Local 接口

     編輯:Java 相關知識

痛點:EJB 客戶端代碼的「四步曲」煩瑣與重複

傳統 EJB 客戶端調用需經歷固定流程:

  1. 創建 InitialContext (需環境屬性)。
  2. JNDI lookup 獲取 Home 對象引用。
  3. PortableRemoteObject.narrow() 縮窄轉型為 EJBHome (Remote) 或強轉為 EJBLocalHome (Local)。
  4. 調用 create() 或 findByPrimaryKey() 獲取 Remote/Local 業務接口。

步驟 1-3 固定易封裝,但步驟 4 因 EJBHome 接口未定義 create() (由具體 Home 子接口定義),導致編譯期無法直接調用,開發者常被迫硬編碼具體 Home 類名,破壞通用性。

解決方案:EJBAgent 代理類 + Java 反射動態調用

核心設計思路

  • 統一入口:getRemote(jndiName), getLocal(jndiName), getRemoteByKey(jndiName, pk), getLocalByKey(jndiName, pk)。
  • 靜態緩存 InitialContext:避免重複創建開銷 (LAN 環境創建 Context 耗時 ~1.3s,遠超 Home/Remote 創建)。
  • 反射調用 create()/findByPrimaryKey():繞過編譯期類型檢查,運行期動態派發。

關鍵代碼實現

public class EJBAgent {
    // 靜態緩存 Context,全局單例
    private static Context cachedContext;
    private final Properties envProps;

    // 客戶端構造器:需傳入環境屬性
    public EJBAgent(Properties props) { this.envProps = props; }
    // 服務端構造器:使用默認環境
    public EJBAgent() { this.envProps = null; }

    private synchronized Context getContext() throws NamingException {
        if (cachedContext == null) {
            cachedContext = (envProps != null) ? new InitialContext(envProps) : new InitialContext();
        }
        return cachedContext;
    }

    /** 獲取 Remote 接口 (Session/Entity Bean) */
    public EJBObject getRemote(String jndiName) throws Exception {
        Context ctx = getContext();
        Object homeRef = ctx.lookup(jndiName);
        EJBHome home = (EJBHome) PortableRemoteObject.narrow(homeRef, EJBHome.class);
        return (EJBObject) invokeCreate(home, null);
    }

    /** 獲取 Local 接口 */
    public EJBLocalObject getLocal(String jndiName) throws Exception {
        Context ctx = getContext();
        EJBLocalHome home = (EJBLocalHome) ctx.lookup(jndiName); // Local 無需 narrow
        return (EJBLocalObject) invokeCreate(home, null);
    }

    /** 通過主鍵獲取 Entity Bean Remote 接口 */
    public EJBObject getRemoteByKey(String jndiName, Object primaryKey) throws Exception {
        Context ctx = getContext();
        Object homeRef = ctx.lookup(jndiName);
        EJBHome home = (EJBHome) PortableRemoteObject.narrow(homeRef, EJBHome.class);
        return (EJBObject) invokeFinder(home, primaryKey);
    }

    /** 核心:反射動態調用 create() 無參方法 */
    private Object invokeCreate(EJBHome home, Object[] args) throws Exception {
        Class homeClass = home.getClass(); // 實際為具體 Home 接口代理類 (如 EJBExampleHome_Stub)
        Method createMethod = homeClass.getDeclaredMethod("create", (Class[]) null); // 無參
        return createMethod.invoke(home, (Object[]) null);
    }

    /** 核心:反射動態調用 findByPrimaryKey(key) */
    private Object invokeFinder(EJBHome home, Object primaryKey) throws Exception {
        Class homeClass = home.getClass();
        Method findMethod = homeClass.getDeclaredMethod("findByPrimaryKey", new Class[]{Object.class});
        return findMethod.invoke(home, new Object[]{primaryKey});
    }
}

實戰應用示例 (WebLogic 6.1)

// 1. 配置環境屬性 (通常作為單例全局配置)
Properties props = new Properties();
props.put(Context.INITIAL_CONTEXT_FACTORY, "weblogic.jndi.WLInitialContextFactory");
props.put(Context.PROVIDER_URL, "t3://localhost:7001");

// 2. 創建代理實例 (單例復用)
EJBAgent agent = new EJBAgent(props);

// 3. 極簡獲取 Remote 業務接口
EJBExampleRemote remote = (EJBExampleRemote) agent.getRemote("EJBExample");

// 4. 直接調用業務方法
String result = remote.someBusinessMethod();

// 5. 獲取 Entity Bean (按主鍵)
OrderRemote order = (OrderRemote) agent.getRemoteByKey("OrderEJB", new Integer(1001));

設計優勢總結

維度傳統硬編碼EJBAgent 代理模式
代碼重複度高 (每處調用複製 10+ 行)極低 (1 行調用)
類型安全編譯期強類型 (需硬編碼 Home 類)運行期反射 (需捕獲 NoSuchMethodException)
Context 管理每次創建/開發者自行緩存靜態單例緩存,顯著降低延遲
Local/Remote 統一分離代碼統一 API,構造器區分環境
Entity Bean 支持需手寫 findByPrimaryKey 邏輯內置 getRemoteByKey

注意事項與最佳實踐

  • 異常處理:反射調用可能拋出 NoSuchMethodException (方法名拼寫錯)、InvocationTargetException (業務異常)、IllegalAccessException。建議封裝為統一的 EJBAgentException。
  • Context 失效處理:集群/容器重啟可能導致緩存 Context 失效,建議提供 refreshContext() 方法重置靜態緩存。
  • 線程安全:InitialContext 非線程安全,但僅作為查找工廠,實際返回的 Home/Remote Stub 通常是線程安全的。高併發下可考慮 ThreadLocal 緩存 Context。
  • 參數化 create 方法:若 Session Bean 有參數 create(String arg),需擴展 getRemote(jndiName, Object... args) 並動態匹配參數類型。

現代框架中的演進

Spring 的 JndiObjectFactoryBean + LocalStatelessSessionProxyFactoryBean、EJB 3.0+ 的 @EJB 注入、CDI 的 @Inject,本質上皆是將「JNDI 查找 + 窄化轉型 + 代理生成」標準化、聲明化。理解 EJBAgent 手工實現原理,有助於排查注入失敗、ClassCastException、序列化問題等疑難雜症。

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