在會話bean綜述中,描述了無狀態和有狀態bean的區別在於客戶端和服務器之間交互形式不同。對於無狀態會話bean,交互的開始和結束都在同一個方法中。有時客戶端需要發出多個服務請求(需要調用多個方法),而每個請求需要訪問或者考慮前面的請求結果。有狀態會話bean的出現就是為了處理這種情況,通過向客戶端提供一個專用的服務(某一個可以保留前面狀態的bean),當客戶端獲得bean的引用時啟動該服務,並且只有當客戶端選擇結束會話時才結束。回到飯店吃飯的例子就是當你點餐的時候一個服務員在為你服務,直到你離開飯店這個服務員才會被釋放,吃飯過程中全部是同一個人在為你服務。
說到有狀態會話bean,不得不提的一個例子就是購物車的例子,這是一個典型的有狀態會話bean的例子。客戶端獲取購物車的引用,啟動會話。在用戶會話期間,客戶端在購物車中添加或刪除項目,其中購物車維護特定於客戶端的狀態。然後,當會話結束時,客戶端完成購買,購物車才會被刪除。
這與在普通的Java應用程序代碼中使用一個非委托的java對象沒什麼不同。我們創建了一個類的實例,調用對象上的操作(set方法)改變實例中的屬性值,然後當不再需要對象時釋放它。有狀態會話bean與上面提到的普通Java對象的區別是服務器管理實際的對象實例,而客戶端與該實例通過bean的業務接口進行間接地交互。注意這裡客戶端知道的僅僅是一個接口,而不是像普通Java應用程序中那樣拿到這個實例的真正引用。
有狀態會話bean提供了無狀態會話bean可用功能的一個超集。無狀態會話bean所包含的功能,如遠程接口功能,同樣也適用於有狀態會話bean。
1.定義有狀態會話bean
以典型的購物車的例子作為代表定義有狀態會話bean,類似於無狀態會話bean,有狀態會話bean是由一個或多個有單一的bean類實現的業務接口組成。購物車bean的一個本地業務接口示例如下代碼所示
public interface ShoppingCart { public void addItem(String id, int quantity); public void removeItem(String id, int quantity); public MapgetItems(); public void checkout(int paymentId); public void cancel(); }
下面代碼顯示了實現ShoppingCart接口的bean類。通過@Stateful注解標記這個bean類,來告訴服務器該類是一個有狀態會話bean。
@Stateful public class ShoppingCartBean implements ShoppingCart { private HashMapitems = new HashMap (); public void addItem(String item, int quantity) { Integer orderQuantity = items.get(item); if (orderQuantity == null) { orderQuantity = 0; } orderQuantity += quantity; items.put(item, orderQuantity); } public void removeItem(String item, int quantity) { Integer orderQuantity = items.get(item); if (orderQuantity == null) { return; } orderQuantity -= quantity; if (orderQuantity > 0) { items.put(item, orderQuantity); } else { items.remove(item); } } public Map getItems() { return items; } @Remove public void checkout(int paymentId) { // store items to database // ... } @Remove public void cancel() { } }
寫完接口與實現之後來比較一下無狀態會話bean與有狀態會話bean,主要存在下面兩方面的不同。
第一個區別是該bean類具有類似於普通Java類中屬性的狀態字段(這裡指item),可以通過bean的業務方法對其進行修改。因為使用該bean的客戶端可以有效的訪問一個私有會話bean實例,並對其進行更改,注意這裡的訪問不是指客戶端直接拿到某個bean的實例,而是通過業務接口對其進行操作。
第二個區別是擁有使用@Remove注解標記的方法。客戶端將使用這些方法來結束與bean的會話。調用了一個這樣的方法之後,服務器將銷毀bean實例。如果後續嘗試繼續調用業務方法,那麼客戶端引用將會拋出異常(這與無狀態會話bean不同,無狀態會話bean更自由一些,拿來就用用完拉倒。這也是業界更傾向於不使用有狀態會話bean的主要原因之一)。每個有狀態會話bean必須至少定義一個使用@Remove注解標記的方法,即使該方法除了結束會話之外不做其他事情。在代碼中如果用戶完成了商店交易,就調用checkout()方法;相反,如果用戶決定不繼續交易就調用cancel()方法。兩種情況都將會刪除會話bean
2.生命周期回調
和無狀態會話bean一樣,有狀態會話bean也支持生命周期的回調,以便於bean的初始化和清理。它還支持兩個額外的回調,以允許bean有效地處理bean實例的鈍化(passivation)和激活(activation)進程。鈍化是由服務器序列化bean實例的進程,使得可以對它進行脫機存儲以釋放資源或者復制到集群中的另一個服務器上。激活處理則反序列化一個鈍化的會話bean實例,使之在服務器上再次變得活動。因為有狀態會話bean保留代表客戶端的狀態,並且直至調用bean的一個remove方法之後才會刪除,所以,服務器不能銷毀一個bean實例以釋放資源。鈍化允許在服務器保留會話狀態的同時回收資源。
在鈍化之前,服務器將調用Prepassivate回調。Bean使用此回調為bean的序列化做准備,通常關閉任何只想其他服務器資源的活動連接。PrePassivate方法由@prePassivate標記注解所標識。在已經激活bean之後,服務器將調用PostActivate回調。鎖著序列化實例的恢復,bean必須重新獲取bean的業務方法可能依賴的任何其他資源的連接。PostActivate方法由@PostActivate標記注解所標識。代碼中顯示了會話bean充分利用回調聲明周期來維護一個JDBC連接。注意,只有JDBC連接是顯式地管理。作為資源連接工廠,服務器在鈍化和激活期間會自動保存和恢復數據源。
@Stateful // WARMING: // Resource declaration is covered later in the chapter. // use of mappedName is vendor specific. In this case, it is used // to specify the JNDI location of the datasource to use. @Resource(name="jdbc/ds", type=DataSource.class, mappedName="jdbc/sfsbLifecycleExample") public class OrderBrowserBean implements OrderBrowser { DataSource ds; Connection conn; @PostConstruct public void init() { // acquire the data source try { ds = (DataSource) new InitialContext().lookup("java:comp/env/jdbc/ds"); } catch (Exception e) { throw new EJBException(e); } acquireConnection(); } @PrePassivate public void passivate() { releaseConnection(); } @PostActivate public void activate() { acquireConnection(); } @PreDestroy public void shutdown() { releaseConnection(); } private void acquireConnection() { try { conn = ds.getConnection(); } catch (SQLException e) { throw new EJBException(e); } } private void releaseConnection() { try { conn.close(); } catch (SQLException e) { } conn = null; } public CollectionlistOrders() { // ... return new ArrayList (); } }