會話Bean(Session Bean)
什么是Session Bean
Session Bean可被視為客戶端程序在服務(wù)器上的部分邏輯延伸,每個Session Bean對象對應(yīng)于特定的客戶端,不能在多個客戶端間共享。換句話說,Session Bean用于表示運(yùn)行于服務(wù)器端的部分業(yè)務(wù)過程,作為客戶端的代理,管理業(yè)務(wù)過程或任務(wù),如客戶對賬戶的借貸操作、匯率的計算,等等這些涉及邏輯、算法和工作流的種種任務(wù)。這些過程都是特定的客戶行為,EJB根據(jù)這些過程在運(yùn)行時創(chuàng)建過程實例、執(zhí)行計算或者清除實例。
Session Bean的生存時間
相對于表示業(yè)務(wù)實體的Entity Bean,Session Bean的生存時間要短,大致等于于一個客戶端會話的延續(xù)時間。例如,客戶通過瀏覽器,訪問與Session Bean表示的相關(guān)業(yè)務(wù)過程,或通過一個Java應(yīng)用程序或者是Applet訪問的相關(guān)業(yè)務(wù)過程的持續(xù)時間,或其它 Enterprise Bean訪問此業(yè)務(wù)過程的持續(xù)時間。
Session Bean是非持久的,其狀態(tài)不被保存到持久存儲機(jī)制(如數(shù)據(jù)庫、文件系統(tǒng))中,盡管Session Bean本身可以執(zhí)行對數(shù)據(jù)庫的操作,但它并不是一種持久對象的表示。
Enterprise Bean類必須實現(xiàn)的接口中,定義了Enterprise Bean類的一些容器用于管理實例的回調(diào)方法。這些回調(diào)方法被容器用于與組件進(jìn)行交互,當(dāng)容器執(zhí)行與此Bean相關(guān)的一些重要操作時,通過調(diào)用這些方法通知組件。例如,用于容器在初始化或清除組件實例時,將調(diào)用這些方法中對應(yīng)的管理回調(diào)方法。由于這些方法是由容器使用的,所以,組件中不應(yīng)去調(diào)用這些方法 (有狀態(tài)會話bean的remove方法除外)。
會話狀態(tài)
Session Bean表示客戶端與應(yīng)用之間的會話。會話由客戶端與組件之間的交互組成,一般表現(xiàn)為一系列的方法調(diào)用。
根據(jù)Session Bean有保持會話狀態(tài)的方式,可分為有狀態(tài)的和無狀態(tài)的Session Bean。
無狀態(tài)的Session Bean
無狀態(tài)的Session Bean,會話狀態(tài)只會在單個方法調(diào)用中被保持,一旦方法調(diào)用結(jié)束,組件將丟棄方法調(diào)用過程中保持的狀態(tài),不保持跨越方法調(diào)用的會話狀態(tài)。
由于無狀態(tài)Session Bean不提供跨越方法調(diào)用的狀態(tài)保持,因此,對于任何客戶端調(diào)用,相同類別的Session Bean實例之間沒有區(qū)別。例如,某個特定客戶端對某個特定的無狀態(tài)Session Bean實例的方法進(jìn)行了調(diào)用后,此方法調(diào)用中的狀態(tài)在調(diào)用完成后即被清除,對于后續(xù)的其他客戶端方法調(diào)用而言,前一個是沒有影響的。因此,無狀態(tài)Session Bean通常被用于一些不需要保存跨越方法調(diào)用的會話狀態(tài)的計算,如匯率的換算等較為簡單的操作。
由于具有這種會話狀態(tài)無關(guān)的特性,無狀態(tài)Session Bean通常可以在服務(wù)器啟動時在實例池中創(chuàng)建并保持一些實例,為不同的客戶端調(diào)用從實例池中分配已有的實例,免去頻繁的創(chuàng)建、初始化和清除實例的動作。
有狀態(tài)的Session Bean
對于有狀態(tài)的Session Bean,在整個會話期間,特定組件實例將保持與某個特定客戶端之間跨越方法調(diào)用的會話狀態(tài)。不同于無狀態(tài)的Session Bean,有狀態(tài)Session Bean的實例只能對應(yīng)于一個特定的客戶端會話,不可在不同的客戶端會話間共享,當(dāng)新的會話開始,有狀態(tài)的Session Bean實例即被創(chuàng)建和初始化,當(dāng)會話結(jié)束,實例即被清除。
因此,一般情況下,有狀態(tài)的Session Bean所能提供的功能性,要強(qiáng)于無狀態(tài)的Session Bean。
容器與會話Bean
容器是為EJB組件提供運(yùn)行時環(huán)境的系統(tǒng)。同一容器中可以部署多個EJB組件。容器提供客戶端通過JNDI對已部署組件進(jìn)行依賴注入(EJB3.0)或訪問Home接口(EJB2.1)的能力,即客戶端可請求容器注入會話Bean遠(yuǎn)程接口或通過JNDI查找指定EJB組件的Home接口。
客戶端不能直接訪問EJB組件的實例,只能通過JNDI查找指定組件的Remote接口,通過Remote接口取得對組件接口的引用。客戶端對組件接口的方法調(diào)用,通過組件接口傳播到容器中對應(yīng)的EJB組件實例。
下圖表示EJB組件及其接口與容器、客戶端間的關(guān)系:
EJB組件及其接口與容器、客戶端間的關(guān)系
會話Bean的會話狀態(tài)
容器為會話Bean提供運(yùn)行時環(huán)境,并對會話Bean實例進(jìn)行管理,因此,會話Bean實例的狀態(tài)也由容器進(jìn)行維護(hù)。根據(jù)狀態(tài)管理模式,可以將會話Bean劃分為有狀態(tài)(Stateful)會話Bean和無狀態(tài)(Stateless)會話Bean,有狀態(tài)會話Bean的狀態(tài)由容器負(fù)責(zé)維護(hù),而無狀態(tài)會話Bean則不需要容器進(jìn)行狀態(tài)管理。
有狀態(tài)Session Bean實例的鈍化與激活
對于當(dāng)前容器中存在的有狀態(tài)會話Bean實例,為提供更有效的管理,容器需要將某些空閑的實例狀態(tài)從內(nèi)存臨時轉(zhuǎn)移到存儲機(jī)制中。這種轉(zhuǎn)移稱為實例的鈍化(Passivation),相反地,將實例狀態(tài)從存儲機(jī)制恢復(fù),則稱為激活。(Activation)。
當(dāng)實例被包含于某個事務(wù)中時,容器不能對實例進(jìn)行鈍化操作。
會話狀態(tài)
有狀態(tài)會話Bean的會話狀態(tài)指會話Bean實例中的域值,連同實例中的域通過Java對象引用指向的對象的傳遞閉包(transitive closure)。
注意 |
|
對象的傳遞閉包按照Java編程語言中的串行化協(xié)議定義,即是通過串行化對象實例,對實例的域進(jìn)行保存。參考Sun的對象串行化文檔。 |
某些情況下,會話對象的會話狀態(tài)可能會包含打開的資源,如網(wǎng)絡(luò)端口和數(shù)據(jù)庫連接等。當(dāng)會話對象被鈍化時,容器不能鈍化這些資源,因此,需要開發(fā)者在容器對實例發(fā)出鈍化事件通知時(容器調(diào)用實例中注解為PrePassivate的方法(EJB3.0)或ejbPassivate方法),對上述資源進(jìn)行關(guān)閉;在容器對實例發(fā)出激活事件通知時(容器調(diào)用實例中注解為PostActivate的方法或ejbActivate方法),重新打開上述資源。
因此在編寫有狀態(tài)的Session Bean時,開發(fā)者必須注意,保證實例的非transient域是下列類型之一,使容器可以在鈍化實例時,可以完整保存對象的會話狀態(tài):
· 可串行化的對象;
· null值;
· EJB的業(yè)務(wù)接口引用;
· EJB的遠(yuǎn)程接口引用,即使stub class為非序列化亦可
· EJB的遠(yuǎn)程Home接口引用,即使stub class為非序列化亦可;
· Entity Bean的本地接口引用,即使其為非序列化亦可;
· EJB的本地Home接口引用,即使其為非序列化亦可;
· 對SessionContext對象的引用,即使其為非序列化亦可;
· 環(huán)境命名上下文(指“java:comp/env”JNDI上下文)及其任何子上下文;
· UserTransaction的引用;
· Resource manager連接工廠的引用;
· 對容器管理的EntityManager對象的引用,即使其為非序列化亦可;
· 通過依賴注入或JNDI查找獲得的EntityManagerFactory引用,即使其為非序列化亦可;
· 對javax.ejb.Timer對象的引用
· 不可直接串行化的對象,但是通過在串行化對象期間,將對象的引用更改為對象的業(yè)務(wù)接口,Home接口和組件接口的引用、對象對SessionContext對象的引用、對象對“java:comp/env”JNDI上下文及其子上下文的引用、對象對UserTransaction接口的引用、對象對EntityManager與EntityManagerFactory的引用進(jìn)行串行化,可將對象視為可串行化的對象。
事務(wù)操作與狀態(tài)域
會話Bean的會話狀態(tài)是非事務(wù)性的。如對象在參與的事務(wù)中改變了狀態(tài),之后事務(wù)回滾,對象是不能自動回到參加事務(wù)前的狀態(tài)的。因此,在不能保證會話Bean的會話狀態(tài)與事務(wù)狀態(tài)不一致的情況下,可以通過實現(xiàn)SessionSynchronization接口,在afterCompletion方法發(fā)出的通知中,根據(jù)事務(wù)提交的情況,決定是否重新設(shè)置對象的會話狀態(tài)。
組件模型單元
對于會話Bean,其組件模型包含以下幾部分:
· Home接口,定義客戶端創(chuàng)建、清除EJB實例的方法;(注:在EJB3.0組件模型中并不需要此接口)
· 組件接口(component interface),定義客戶端可訪問的組件的業(yè)務(wù)方法,在EJB3.0組件模型中稱為業(yè)務(wù)接口(business interface);
· 組件類,提供對定義在組件接口中的方法的實現(xiàn);
· 部署描述,包含此EJB的部署信息,如事務(wù)屬性等,在EJB3.0中可使用注解來描述部署信息。
下面分別對開發(fā)這些單元時,涉及的普遍過程、規(guī)則及注意事項進(jìn)行描述。
Home接口
EJB2.1規(guī)范通過Home接口定義客戶端創(chuàng)建、清除EJB對象的方法。Home接口有兩種類型,本地Home接口和遠(yuǎn)程Home接口,分別提供給本地和遠(yuǎn)程客戶端使用。在EJB3.0中,可通過依賴注入,JNDI查找,可選的生命周期回調(diào)方法注解來實現(xiàn)類似的功能。
遠(yuǎn)程Home接口
遠(yuǎn)程Home接口使遠(yuǎn)程客戶端可以:
· 創(chuàng)建新的Session對象;
· 清除一個Session對象;
· 取得此Session Bean的javax.ejb.EJBMetaData接口。javax.ejb.EJBMetaData接口用于表示此Session Bean的信息,降低客戶端與服務(wù)器的綁定程度和用于支持客戶端腳本;
· 取得遠(yuǎn)程Home接口的句柄(Handler),此句柄可串行化(serialization)到持久存儲中,然后,可在其他虛擬機(jī)中,從持久存儲中,對此串行化對象進(jìn)行解串行化(deserialization),取得對此遠(yuǎn)程Home接口句柄的引用。
編寫遠(yuǎn)程Home接口的規(guī)則
編寫EJB的遠(yuǎn)程Home接口時,開發(fā)人員必須遵循如下規(guī)則:
· 遠(yuǎn)程Home接口必須擴(kuò)展(extend)javax.ejb.EJBHome接口;
· 定義在接口中的方法的參數(shù)和返回值必須是合法的RMI類型,并且必須顯式聲明拋出java.rmi.RemoteException異常;
· 在繼承關(guān)系上,遠(yuǎn)程接口可以擴(kuò)展已有的接口,但繼承的方法必須依從上一規(guī)則;
· 遠(yuǎn)程接口方法必須定義一個或多個create方法。無狀態(tài)的Session Bean必須提供一個無參數(shù)的create方法,并且只能命名為“create()”的形式;每個create方法必須匹配一個定義在組件類中的ejbCreate方法,此組件類中的ejbCreate方法必須具有相同個數(shù)和類型的參數(shù)。無狀態(tài)Session Bean的create方法在組件類中的匹配方法只能命名為“ejbCreate()”的形式。;
· 每個create方法名稱開頭必須是“create” 。
· create方法的返回值類型必須是Session Bean的遠(yuǎn)程接口;
· create方法必須聲明拋出javax.ejb.CreateException異常;
代碼范例
Home接口通過定義一個或多個create方法,提供一種或多種創(chuàng)建session對象的方式。create方法的參數(shù)一般用于初始化被創(chuàng)建session對象的狀態(tài)。
create方法的返回值類型為Bean的遠(yuǎn)程接口類型。
下面是一個范例Session Bean的Home接口的代碼,定義了兩個create方法:
public interface CartHome extends javax.ejb.EJBHome
{
Cart create(String customerName, String account)
throws RemoteException, BadAccountException,
CreateException;
Cart createLargeCart(String customerName, String account)
throws RemoteException, BadAccountException,
CreateException;
}
客戶端可通過如下代碼,對已部署在應(yīng)用服務(wù)器上的Session Bean的Home接口進(jìn)行查找,并使用CartHome接口中定義的create方法創(chuàng)建session對象:
Context initialContext = new InitialContext();
CartHome cartHome = (CartHome)
javax.rmi.PortableRemoteObject.narrow(
initialContext.lookup(“java:comp/env/ejb/cart”),
CartHome.class);
Cart shoppingCart = home.create("Duke DeEarl","123");
本地Home接口
本地Home接口使本地客戶端可以:
· 創(chuàng)建新的Session對象;
· 清除一個Session對象;
編寫本地Home接口的規(guī)則
如果EJB需要提供本地客戶端的訪問,必須提供EJB的本地Home接口,開發(fā)人員必須遵循如下規(guī)則進(jìn)行編寫:
· 本地Home接口必須擴(kuò)展(extend)javax.ejb.EJBLocalHome接口;
· 定義在接口中的方法不能拋出java.rmi.RemoteException異常;
· 在繼承關(guān)系上,本地接口可以擴(kuò)展已有的接口;
· 本地接口方法必須定義一個或多個create方法。無狀態(tài)的Session Bean必須提供一個無參數(shù)的create方法,并且只能命名為“create()”的形式;每個create方法必須匹配一個定義在組件類中的ejbCreate方法,此組件類中的ejbCreate方法必須具有相同個數(shù)和類型的參數(shù)。無狀態(tài)Session Bean的create方法在組件類中的匹配方法只能命名為“ejbCreate()”的形式。;
· 每個create方法名稱開頭必須是“create” 。
· create方法的返回值類型必須是Session Bean的本地接口;
· create方法必須聲明拋出javax.ejb.CreateException異常;
代碼范例
客戶端可通過類似如下代碼,對已部署在應(yīng)用服務(wù)器上的Session Bean的本地Home接口進(jìn)行查找:
Context initialContext = new InitialContext();
CartHome cartHome = (CartHome)
initialContext.lookup(“java:comp/env/ejb/cart”);
業(yè)務(wù)(組件)接口
會話Bean和實體Bean的客戶端不能直接訪問EJB組件類的實例,客戶端程序通過業(yè)務(wù)接口訪問EJB組件,開發(fā)者在業(yè)務(wù)接口中定義可供客戶端訪問的業(yè)務(wù)方法。業(yè)務(wù)接口分為兩種類型,本地接口和遠(yuǎn)程接口。
遠(yuǎn)程接口
如果EJB需要被遠(yuǎn)程客戶端訪問,必須提供EJB的遠(yuǎn)程接口,遠(yuǎn)程接口為組件提供以下支持:
· 定義EJB對象的業(yè)務(wù)邏輯方法,遠(yuǎn)程接口把對業(yè)務(wù)方法的調(diào)用傳播到Session Bean實例;
· 提供允許客戶端取得遠(yuǎn)程Home接口實例引用的方法;
· 提供使客戶端取得Session對象句柄的方法;
· 提供比較兩個EJB實例是否相等的方法;
· 移除Session Bean實例的方法;
必須遵守的的規(guī)則
開發(fā)人員必須遵循如下規(guī)則編寫遠(yuǎn)程接口:
· 遠(yuǎn)程接口必須使用@Remote注解標(biāo)記(EJB3.0),或擴(kuò)展(extend)javax.ejb.EJBObject接口(EJB2.1);
· 若使用EJB2.1規(guī)范,定義在接口中的方法的參數(shù)和返回值必須是合法的RMI類型,并且必須顯式聲明拋出java.rmi.RemoteException異常;
· 在繼承關(guān)系上,遠(yuǎn)程接口可以擴(kuò)展已有的接口,但繼承的方法必須依從上一規(guī)則;
· 每個在遠(yuǎn)程接口中定義的方法,必須在組件類中有一個匹配的方法,組件類中的匹配方法必須與接口中定義的方法具有相同的名字、相同的參數(shù)個數(shù)、參數(shù)類型和相同的返回值,而且所有在遠(yuǎn)程接口中定義的拋出的異常也必須在匹配方法中定義;
· 遠(yuǎn)程接口方法不可暴露本地接口類型、本地Home接口類型,而且,對于容器管理持久性Entity Bean,也不可暴露作為Entity Bean中方法參數(shù)和返回值的受管理的集合類(collection)。
代碼范例
下面是一個EJB2.1的會話Bean的遠(yuǎn)程接口的代碼,其中定義了三個業(yè)務(wù)方法:
public interface Cart extends EJBObject
{
public void addBook(String title)
throws RemoteException;
public void removeBook(String title)
throws BookException, RemoteException;
public Vector getContents()
throws RemoteException;
}
在EJB3.0中,可使用以下形式達(dá)到同樣效果:
@Remote
public interface Cart extends EJBObject
{
public void addBook(String title)
throws RemoteException;
public void removeBook(String title)
throws BookException, RemoteException;
public Vector getContents()
throws RemoteException;
}
本地接口
在EJB2.1規(guī)范中,如果EJB需要被本地客戶端訪問,必須提供EJB的本地接口,本地接口為組件提供以下支持:
· 定義EJB對象的業(yè)務(wù)邏輯方法,本地接口把對業(yè)務(wù)方法的調(diào)用傳播到Session Bean實例;
· 提供允許客戶端取得本地Home接口實例引用的方法;
· 提供比較兩個EJB實例是否相等的方法;
· 移除Session Bean實例的方法;
在EJB3.0規(guī)范中,容器為遠(yuǎn)程接口提供了位置透明性,本地客戶端亦可通過遠(yuǎn)程接口訪問業(yè)務(wù)方法。但使用本地接口可以為EJB提供細(xì)粒度的業(yè)務(wù)方法,獲得更好的可重用、與可維護(hù)性與運(yùn)行效率。EJB3.0中的本地接口可以使用@Local注解來標(biāo)記。
必須遵守的規(guī)則
開發(fā)人員必須遵循如下規(guī)則編寫本地接口:
· 本地接口必須使用@Local注解進(jìn)行標(biāo)記,或擴(kuò)展(extend)javax.ejb.EJBLocalObject接口;
· 本地接口中定義的方法不能聲明拋出java.rmi.RemoteException異常;
· 在繼承關(guān)系上,本地接口可以擴(kuò)展已有的接口;
· 每個在本地接口中定義的方法,必須在組件類中有一個匹配的方法,組件類中的匹配方法必須與接口中定義的方法具有相同的名字、相同的參數(shù)個數(shù)、參數(shù)類型和相同的返回值,而且所有在遠(yuǎn)程接口中定義的拋出的異常也必須在匹配方法中定義;
組件類
對于一個Session Bean組件,開發(fā)者在業(yè)務(wù)(組件)接口中定義的的業(yè)務(wù)方法和在Home接口中定義的create方法,需要在組件類中對這些方法提供實現(xiàn)。EJB2.1中的組件類必須實現(xiàn)javax.ejb.SessionBean接口,容器通過調(diào)用從javax.ejb.SessionBean中繼承的管理方法,提供組件訪問容器提供的服務(wù)的能力,并通過一些狀態(tài)管理回調(diào)方法,向組件實例發(fā)送其生存周期中的關(guān)鍵事件的信息。在EJB3.0中的組件類則不必實現(xiàn)javax.ejb.SessionBean接口,而是通過依賴注入、JNDI查找、生存周期回調(diào)方法注解達(dá)到同樣的功能。
在組件類中使用注解
EJB3.0的組件類不再強(qiáng)制實現(xiàn)javax.ejb.SessionBean接口,而是通過注解實現(xiàn)同樣的功能,從而避免了嚴(yán)格的隱式命名約定。在組件中常用的注解有以下幾個:
@Resource注解
在組件類中可以使用@Resource注解通知容器對上下文資源進(jìn)行依賴注入,特別地,可以注入SessionContext實例:
@Stateless public class EmployeeServiceBean
implements EmployeeService{
@Resource SessionContext ctx;
...
}
@PostCreate注解
在組件類中可以使用@PostCreate注解標(biāo)記初始化方法按照EJB3.0規(guī)范,容器將會在完成對會話Bean依賴注入之后,會話Bean的第一次方法被調(diào)用之前回調(diào)該方法。可以在該方法中對依賴注入后的會話Bean狀態(tài)進(jìn)行初試化。
@Remove與@PreDestroy注解
在會話Bean的業(yè)務(wù)接口中使用@Remove注解標(biāo)記客戶端請求清除會話Bean的方法。該方法由客戶端調(diào)用,若成功完成,容器將清除會話Bean。此注解適用于有狀態(tài)會話Bean,無狀態(tài)會話Bean的生存周期完全由容器管理,毋須在客戶端顯式刪除。
在會話Bean組件類中可使用@PreDestroy注解標(biāo)記容器清除會話Bean前的回調(diào)方法。容器將在會話Bean被清除前調(diào)用該方法。一般,實例可以在此方法中對實例占用的資源進(jìn)行釋放。
@PrePassivate與@PostActivate注解
在實例將被容器鈍化(Passivate)時,容器會調(diào)用被標(biāo)記為PrePassivate的方法。同樣,當(dāng)實例將被再次激活(Activate)時,容器會調(diào)用被標(biāo)記為PostActivate的方法。在實例鈍化時,容器將會自動保存實例的狀態(tài)信息;在激活實例時,容器也將恢復(fù)被保存的實例狀態(tài)信息。一般的Session Bean都會忽略此事件,但是,對于使用某些不可串行化的資源,如與特定數(shù)據(jù)庫的連接等,作為實例狀態(tài)一部分的Session Bean,通常需要在ejbPassivate方法中釋放資源,在ejbActivate方法中重新獲取資源。
javax.ejb.SessionBean接口
在EJB2.1規(guī)范中,javax.ejb.SessionBean是容器與組件實例之間的協(xié)議。通過實現(xiàn)此接口,組件提供容器訪問組件的能力,實際上,容器不需要組件提供任何服務(wù),容器通過javax.ejb.SessionBean中定義的接口方法訪問組件實例,主要是為了給組件提供訪問容器提供的服務(wù)的能力,并向組件實例發(fā)送通知其生存周期中重要事件的發(fā)生信息。
javax.ejb.SessionBean中定義了以下方法。
setSessionContext
容器通過調(diào)用setSessionContext方法,將由容器維護(hù)的Bean實例的上下文(context)與Bean實例進(jìn)行關(guān)聯(lián)。一般,Session Bean實例的上下文被作為實例會話狀態(tài)的一部分,被實例所保存。
ejbRemove
在實例將被容器清除時,容器會調(diào)用此方法。一般,實例可以在此方法中對實例占用的資源進(jìn)行釋放。
ejbPassivate與ejbActivate
在實例將被容器鈍化(Passivate)時,容器會調(diào)用ejbPassivate方法。同樣,當(dāng)實例將被再次激活(Activate)時,容器會調(diào)用ejbActivate方法。對于使用某些不可串行化的資源,如與特定數(shù)據(jù)庫的連接等,作為實例狀態(tài)一部分的會話Bean,通常需要在ejbPassivate方法中釋放資源,在ejbActivate方法中重新獲取資源。
SessionContext接口
對于會話Bean實例,容器將提供一個SessionContext對象,使實例可以訪問由容器維護(hù)的實例的上下文環(huán)境。在SessionContext接口中,定義了如下方法:¹
· getEJBObject方法,返回Session Bean的遠(yuǎn)程接口;
· getEJBHome方法,返回Session Bean的遠(yuǎn)程Home接口;
· getEJBLocalObject,返回Session Bean的本地接口;
· getEJBLocalHome,返回Sesson Bean的本地Home接口;
· getCallerPrincipal方法,返回標(biāo)識調(diào)用此實例的調(diào)用者的java.security.Principal對象;
· isCallerInRole方法,檢查此Session Bean的調(diào)用者是否是某個特定的角色;
· setRollbackOnly方法,當(dāng)前事務(wù)將被永久標(biāo)記為回滾,不會被提交。只有容器管理事務(wù)的Session Bean可被允許使用此方法;
· getRollbackOnly方法,檢查當(dāng)前事務(wù)是否已被標(biāo)記為回滾。例如,EJB實例可以通過此方法,判斷是否繼續(xù)在當(dāng)前事務(wù)邊界內(nèi)進(jìn)行計算。只有容器管理事務(wù)的Session Bean可被允許使用此方法;
· getUserTransaction方法,返回javax.transaction.UserTransaction接口。EJB實例可通過此接口對事務(wù)邊界進(jìn)行劃分,并取得事務(wù)的狀態(tài)。只有容器管理事務(wù)的Session Bean可被允許使用此方法;
可選的SessionSynchronization接口
會話Bean的組件類可以選擇是否實現(xiàn)javax.ejb.SessionSynchronization接口。此接口對會話Bean提供事務(wù)的同步通知。
· afterBegin通知,標(biāo)志會話Bean實例一個新事務(wù)的開始。當(dāng)包含在事務(wù)中第一個的業(yè)務(wù)方法被調(diào)用之前,容器將調(diào)用此方法,后續(xù)的業(yè)務(wù)方法調(diào)用將會存在于此事務(wù)上下文中;
· afterCompletion通知,標(biāo)志一個會話Bean事務(wù)的提交操作結(jié)束,并通知實例提交操作是成功還是回滾;
· beforeCompletion通知,通知會話Bean實例事務(wù)將進(jìn)行提交操作;
EJB3.0規(guī)范規(guī)定,如果會話Bean直接或間接地實現(xiàn)了SessionSynchronization接口,容器必須回調(diào)afterBegin, beforeCompletion與afterCompletion方法。若會話Bean沒有實現(xiàn)SessionSynchronization接口,則容器不會調(diào)用這些方法。
串行化的會話Bean方法調(diào)用
容器將對每個會話Bean實例的方法調(diào)用進(jìn)行串行化。Apusic應(yīng)用服務(wù)器中的EJB容器支持會話Bean的多個實例并發(fā)執(zhí)行;但是,每個會話Bean實例只會看到依次進(jìn)行的方法調(diào)用,因此開發(fā)Session Bean時,不需要將其以可重入(reentrant)的方式進(jìn)行編寫。
客戶端不能對有狀態(tài)的會話Bean對象進(jìn)行并發(fā)調(diào)用。當(dāng)某個客戶端對某特定的會話Bean實例的業(yè)務(wù)方法調(diào)用正在執(zhí)行中,從相同或不同的客戶端發(fā)出了另一個客戶端調(diào)用,容器將對第二個客戶端調(diào)用拋出java.rmi.RemoteException或javax.ejb.EJBException,這取決于第二個客戶端調(diào)用是通過遠(yuǎn)程或本地組件接口進(jìn)行調(diào)用。
業(yè)務(wù)方法必須遵守的規(guī)則
會話Bean的組件類中可以定義零到多個業(yè)務(wù)方法,其方法簽名和命名必須遵守如下規(guī)則:
· 方法名是任意的,但不能使用“ejb”開頭,以免與EJB組件架構(gòu)中的容器管理回調(diào)方法發(fā)生沖突,如ejbPassivate方法;
· 業(yè)務(wù)方法必須被聲明為public方法;
· 業(yè)務(wù)方法不能被聲明為final或static;
· 如業(yè)務(wù)方法是對應(yīng)于會話Bean遠(yuǎn)程接口中定義的業(yè)務(wù)方法,則方法參數(shù)和返回值必須是合法的RMI/IIOP類型;
· 可以拋出任意應(yīng)用級異常;
注意 |
|
EJB1.0允許業(yè)務(wù)方法拋出java.rmi.RemoteException,以指出非應(yīng)用級的異常。在EJB1.1和EJB2.1兼容的Enterprise Bean開發(fā)中,這種方式不建議使用(deprecated),EJB2.1兼容的Enterprise Bean開發(fā)中,不應(yīng)從業(yè)務(wù)方法拋出java.rmi.RemoteException。 |
代碼實例
以下是一個會話Bean組件類的范例代碼,其對應(yīng)的組件接口和Home接口,請參考本章中關(guān)于組件接口和Home接口中的范例部分:
import java.util.*;
import javax.ejb.*;
public class CartBean implements SessionBean {
String customerName;
String customerId;
Vector contents;
public void ejbCreate(String person) throws CreateException {
if (person == null) {
throw new CreateException("Null person not allowed.");
}
else {
customerName = person;
}
customerId = "0";
contents = new Vector();
}
public void ejbCreate(String person, String id) throws CreateException {
if (person == null) {
throw new CreateException("Null person not allowed.");
}
else {
customerName = person;
}
IdVerifier idChecker = new IdVerifier();
if (idChecker.validate(id)) {
customerId = id;
}
else {
throw new CreateException("Invalid id: " + id);
}
contents = new Vector();
}
public void addBook(String title) {
contents.addElement(title);
}
public void removeBook(String title) throws BookException {
boolean result = contents.removeElement(title);
if (result == false) {
throw new BookException(title + " not in cart.");
}
}
public Vector getContents() {
return contents;
}
public CartBean() {}
public void ejbRemove() {}
public void ejbActivate() {}
public void ejbPassivate() {}
public void setSessionContext(SessionContext sc) {}
}
生存周期
有狀態(tài)Session Bean的生存周期
有狀態(tài)Session Bean的生存周期
上圖表示有狀態(tài)Session Bean的生存周期。
有狀態(tài)會話Bean實例的生命周期開始于用戶端通過依賴注入或JNDI查找獲得一個有狀態(tài)會話Bean實例的引用,或者用戶端調(diào)用了會話Bean的Home接口中的create<METHOD>方法。這將通知容器調(diào)用會話Bean類的newInstance方法創(chuàng)建一個新的會話Bean實例。然后容器將注入SessionContext實例(若可用),并執(zhí)行其它由元數(shù)據(jù)注解標(biāo)記的或在部署描述符中設(shè)定的依賴注入。然后容器將調(diào)用會話Bean的PostConstruct生存周期回調(diào)方法(若已定義)。如果會話Bean遵照EJB2.1規(guī)范,則容器調(diào)用實例中匹配的ejbCreate<METHOD>或Init方法。最后,容器返回會話Bean的對象引用。現(xiàn)在實例進(jìn)入就緒狀態(tài),客戶端可以調(diào)用其業(yè)務(wù)方法。
根據(jù)會話bean元數(shù)據(jù)注解中的事務(wù)屬性或部署描述文件中的設(shè)定,以及客戶端調(diào)用所關(guān)聯(lián)的事務(wù)上下文,業(yè)務(wù)方法將會在一個特定的事務(wù)上下文環(huán)境或一個未指定的業(yè)務(wù)上下文環(huán)境中執(zhí)行(在圖中分別以“事務(wù)方法”與“非事務(wù)方法”表示)。
容器的緩沖算法決定會話Bean實例是否需要移出內(nèi)存(例如采取最近最久未使用算法)。容器先回調(diào)Bean實例的PrePassivate生存周期回調(diào)方法(若已定義)。然后,容器把實例的狀態(tài)信息保存到二級存儲設(shè)備中,會話Bean進(jìn)入鈍化狀態(tài)。在事務(wù)之中的會話Bean不能被鈍化。
當(dāng)會話Bean處于鈍化狀態(tài),容器可能會在會話Bean超時后清除該實例,超時信息在部署描述文件中設(shè)置。此時所有對于該實例的引用都將失效。如果客戶端試圖通過業(yè)務(wù)接口調(diào)用任一方法,容器將拋出javax.ejb.NoSuchEJBException異常。如果使用EJB2.1規(guī)范,則容器分別為遠(yuǎn)程客戶端拋出java.rmi.NoSuchObjectException異常,為本地客戶端拋出javax.ejb.NoSuchObjectLocalException異常。
如果客戶端調(diào)用了被標(biāo)記為Remove的方法,或者home接口或組件接口中的remove方法,容器將在該方法成功完成后回調(diào)被標(biāo)記為PreDestroy的生存周期回調(diào)方法(若已定義)。這將終結(jié)會話Bean實例的生存周期。此時所有對于該實例的引用都將失效。如果客戶端試圖通過業(yè)務(wù)接口調(diào)用任一方法,容器將拋出javax.ejb.NoSuchEJBException異常。如果使用EJB2.1規(guī)范,則容器分別為遠(yuǎn)程客戶端拋出java.rmi.NoSuchObjectException異常,為本地客戶端拋出javax.ejb.NoSuchObjectLocalException異常。要注意即使客戶端沒有顯式調(diào)用Remove方法,容器也會在EJB對象實例超時后主動調(diào)用其PreDestroy方法并清除該實例。
如果客戶端調(diào)用了鈍化狀態(tài)的會話Bean的方法,容器將激活該實例。此時容器將先從二級存儲設(shè)備中恢復(fù)實例的狀態(tài)信息,并調(diào)用實例的PostActivte方法(若已定義)。
如果Remove方法成功完成,或Remove方法中拋出了應(yīng)用異常而retainIfException為假,或拋出了系統(tǒng)異常,SessionSynchronization方法將不會被調(diào)用。如果拋出了應(yīng)用異常而retainIfException為真,則該會話bean實例既不會被清除也不會被丟棄,SessionSynchronization方法(若已定義)將會在事務(wù)結(jié)束時被調(diào)用。
客戶端代碼對有狀態(tài)Session Bean生存周期的控制只能創(chuàng)建(使用依賴注入、JNDI查找或create<METHOD>方法)和清除(使用Remove方法)。其他方法由EJB容器進(jìn)行調(diào)用。
無狀態(tài)Session Bean的生存周期
因無狀態(tài)Session Bean不會進(jìn)行被鈍化操作,因此,其生存周期只有兩個階段,不存在和就緒狀態(tài)。下圖表示無狀態(tài)Session Bean的生存周期:
無狀態(tài)Session Bean的生存周期