傳統 EJB 客戶端調用需經歷固定流程:
InitialContext (需環境屬性)。lookup 獲取 Home 對象引用。PortableRemoteObject.narrow() 縮窄轉型為 EJBHome (Remote) 或強轉為 EJBLocalHome (Local)。create() 或 findByPrimaryKey() 獲取 Remote/Local 業務接口。步驟 1-3 固定易封裝,但步驟 4 因 EJBHome 接口未定義 create() (由具體 Home 子接口定義),導致編譯期無法直接調用,開發者常被迫硬編碼具體 Home 類名,破壞通用性。
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});
}
}
// 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。refreshContext() 方法重置靜態緩存。InitialContext 非線程安全,但僅作為查找工廠,實際返回的 Home/Remote Stub 通常是線程安全的。高併發下可考慮 ThreadLocal 緩存 Context。create(String arg),需擴展 getRemote(jndiName, Object... args) 並動態匹配參數類型。Spring 的 JndiObjectFactoryBean + LocalStatelessSessionProxyFactoryBean、EJB 3.0+ 的 @EJB 注入、CDI 的 @Inject,本質上皆是將「JNDI 查找 + 窄化轉型 + 代理生成」標準化、聲明化。理解 EJBAgent 手工實現原理,有助於排查注入失敗、ClassCastException、序列化問題等疑難雜症。