常規訪問方式
編寫過EJB(Enterprise Java Bean)訪問程序的朋友都知道,通過客戶端或服務端的程序訪問EJB(即獲得一個EJB Remote或Local對象)通常要經歷以下幾個步驟:
(1)創建一個初始化上下文(initial context);
(2)通過JNDI查找對應的EJB上下文對象;
(3)通過獲得的上下文對象獲得一個Home或者LocalHome對象(獲得Home對象通過PortableRemoteObject對象的narrow方法,獲得LocalHome對象通過強制類型轉換);
(4)最後通過Home或LocalHome的create方法創建一個Remote或Local對象,通過Remote或Local對象真正使用服務器的EJB提供的方法。
代理類設計
如果我們能夠把以上4個步驟封裝在一個類(本文叫EJBAgent)的方法(getRemote和getLocal)內就可以使得獲得一個Remote或Local對象只需要提供一個JNDI的名稱,避免了重復拷貝以上4個步驟的代碼。寫過EJB訪問代碼的程序員都知道,1~3步是很容易封裝起來的,以下是典型的代碼:
/*1.*/ InitialContext context = new InitialContext (propertIEs);
/*2.*/ Object home = context.lookup(JNDIName);
/*3.*/ EJBHome ejbHome =
(EJBHome) PortableRemoteObject.narrow (home, EJBHome.class);
第一步獲得一個Context,主要是獲得應用服務器的環境參數,我們可以把存放環境變量的pertiesertIEs對象作為參數,第二步是查找EJB上下文對象,可以把JNDIName作為參數,第三步獲得Home對象,我們可以統一獲得Home對象的父類:EJBHome,應用程序在具體根據實際使用的Home接口作類型轉換。
第四步通過create方法創建一個Remote或Local對象封裝就有一定困難了,因為我們在第三步獲得的是一個EJBHome的接口,而我們知道,EJBHome接口本身不提供create方法(對EntityBean有可能是findByPrimaryKey的方法),而是由具體的應用程序的Home接口定義的,所以如果我們直接在代碼體寫ejbHome.create(),會產生一個編譯錯誤,提示方法create沒找到。要使程序能夠執行create方法,一種辦法是把ejbHome強制類型轉換成應用程序定義的EJBHome類型,即(EJBHome)換成(EJBExampleHome),其中EJBExampleHome是應用程序定義的EJBHome接口,但這樣必須在代碼體中寫入應用程序定義的EJBHome類名稱,達不到通用處理的目的。但我們知道,通過PortableRemoteObject.narrow出來的EJBHome其實是應用程序的定義的Home接口,即雖然我們使用(EJBHome)作類型轉換,但ejbHome實際指向的對象是EJBExampleHome,其中就定義create方法,我們可以采用動態調用的方式調用create方法,從而避免在編譯時產生錯誤。Java.lang.reflect包提供了通過方法名稱動態調用方法的Method類。以下代碼是上述思想的實現:
/*3.*/ EJBHome ejbHome =
(EJBHome) PortableRemoteObject.narrow (home, EJBHome.class);
/*4.*/ Class ejbHomeClass = ejbHome.getClass();
/*5.*/ Method method = ejbHomeClass.getDeclaredMethod("create",null);
/*6.*/ Object remoteObject = method.invoke(ejbHome,null);
其中第4步是獲得ejbHome的Class,可能有部分朋友以為是獲得EJBHome的類,其實獲得的Class是應用程序定義的EJBHome接口:應用程序定義的類EJBExampleHome。有興趣的讀者可以通過getName()方法獲得實際Class名稱作判斷。第五步通過獲得的Class獲得一個定義的create方法。getDeclaredMethod有兩個參數,第一個是方法的名稱,是String類型,第二個是參數類型,是Class數組。由於create方法是沒有參數,所以getDeclaredMethod第二個參數是null。需要注意的是getDeclaredMethod是動態執行的,所以第一參數:方法名稱如果寫錯,編譯時是不會產生錯誤的,在實際執行時才會報錯,拋出“沒有該方法”的異常:NoSuchMethodException。第6步通過動態調用create方法創建Remote對象。與getDeclaredMethod方法對應,Method的invoke方法也有兩個參數,第一個參數是定義執行方法的對象,Object類型,第二個參數是調用方法的參數,是Object數組,因為create方法沒有參數,所以第二個參數設置為null。
例子介紹
以下以WebLogic6.1作應用服務器為例子說明EJBAgent的使用方法。我們建立一個叫EJBExample的SessionBean,其Home接口為EJBExampleHome,Remote對象為EJBExampleRemote,其JNDI名稱為EJBExample。通過以下代碼體獲得一個EJBExampleRemote的對象:
Properties properties = new PropertIEs();
propertIEs.put(Context.INITIAL_CONTEXT_FACTORY, "weblogic.jndi.WLInitialContextFactory");
propertIEs.put(Context.PROVIDER_URL,"t3://localhost:7001";);
EJBAgent agnet = new EJBAgent(propertIEs);
EJBExampleRemote remote = (EJBExampleRemote)agent.getRemote("EJBExample");
通過獲得的remote對象就可以操作服務器端EJBExample的方法。我們可以看到,整個過程只需要兩個參數:應用服務器環境properties和對應的EJB JNDI名稱,強制類型轉換也只需要在獲得Remote對象時才使用到。而且通常一個應用程序是連接到一個應用服務器上,所以properties參數通常只需要設置一次,這樣可以只產生一個EJBAgent實例供多個客戶端程序使用,既減少了代碼量,也提高了重用性。如果我們想獲得的是一個Local對象,則可以不必構造一個propertIEs對象作為參數,為獲得Local對象的程序通常是運行在應用服務器端的SessionBean,直接使用new InitialContext()則可以獲得應用服務器的環境參數。所以EJBAgent提供了有參和無參兩個構造方法,分別供ClIEnt端和Application Server端的應用程序使用。具體的實現請看EJBAgnet.Java。下面就EJBAgnet的部分關鍵代碼作說明:
(2)以靜態static的方式定義EJBAgent使用的context對象:避免EJB訪問程序多次重新獲得Context。在局域網的環境中,從clinet獲得一個Context的時間在1~3秒范圍內,筆者在局域網用WebLogic6.1作應用服務器作過時間比較,獲得一個Context平均花1.3秒,而獲得一個Home和Remote對象只分別花17和36毫秒,所以把獲得的Context用靜態方式緩存以供調用時使用大大減少了應用程序訪問EJB的時間;
(2)針對Entity Bean的訪問增加了getRemoteByKey和getLocalByKey的方法,可以通過JNDI Name和Primary Key的對象獲得一個EntityBean Remote或Local對象,同樣通過動態調用的方式實現。與create方法創建EJB的區別是findByPrimaryKey的方法需要有參數調用。