Stateful Session Bean 維護用戶會話狀態,購物車是教科書級範例。本範例結合 JDK 5.0 Generics,展示型別安全的 `HashMap
package org.jboss.tutorial.stateful.bean;
import javax.ejb.Remote;
import javax.ejb.Remove;
import java.util.HashMap;
@Remote
public interface ShoppingCart {
void buy(String product, int quantity);
HashMap getCartContents();
@Remove void checkout(); // 結帳後 Bean 實例移除
}
package org.jboss.tutorial.stateful.bean;
import javax.ejb.Remove;
import javax.ejb.Stateful;
import java.io.Serializable;
import java.util.HashMap;
@Stateful
public class ShoppingCartBean implements ShoppingCart, Serializable {
// Generics: 型別安全的購物車內容 <商品名, 數量>
private HashMap cart = new HashMap();
public void buy(String product, int quantity) {
if (cart.containsKey(product)) {
int current = cart.get(product);
cart.put(product, current + quantity);
} else {
cart.put(product, quantity);
}
}
public HashMap getCartContents() {
return cart;
}
@Remove
public void checkout() {
System.out.println("To be implemented: 持久化訂單、清空購物車、發送確認郵件...");
// 實際專案:呼叫 EntityManager.persist(order)、整合付款閘道等
}
}
package org.jboss.tutorial.stateful.client;
import org.jboss.tutorial.stateful.bean.ShoppingCart;
import javax.ejb.EJBException;
import javax.naming.InitialContext;
import java.rmi.NoSuchObjectException;
import java.util.HashMap;
public class Client {
public static void main(String[] args) throws Exception {
InitialContext ctx = new InitialContext();
ShoppingCart cart = (ShoppingCart) ctx.lookup(ShoppingCart.class.getName());
System.out.println("Buying 1 memory stick");
cart.buy("Memory stick", 1);
System.out.println("Buying another memory stick");
cart.buy("Memory stick", 1);
System.out.println("Buying a laptop");
cart.buy("Laptop", 1);
System.out.println("Print cart:");
HashMap fullCart = cart.getCartContents();
for (String product : fullCart.keySet()) {
System.out.println(fullCart.get(product) + " " + product);
}
System.out.println("Checkout");
cart.checkout(); // 觸發 @Remove,Bean 實例銷毀
System.out.println("驗證 @Remove 後調用應拋出 NoSuchObjectException");
try {
cart.getCartContents(); // 此時 Bean 已不存在
} catch (EJBException e) {
if (e.getCausedByException() instanceof NoSuchObjectException) {
System.out.println("成功捕獲 NoSuchObjectException: Bean 已被移除");
} else {
throw e;
}
}
}
}
| 階段 | 容器行為 | 開發者關注點 |
|---|---|---|
| 建立 | 客戶端 `home.create()` → 容器實例化 Bean、注入依賴、呼叫 `@PostConstruct` | 初始化資源、建立空集合 |
| 業務方法 | 客戶端調用 → 容器攔截 (交易、安全) → 委派 Bean 實例 | 執行緒安全 (Stateful Bean 單執行緒存取保證) |
| 被動化 | 容器依 LRU/時間將閒置 Bean 序列化至磁碟 (`ejbPassivate`) | 實作 `Serializable`、清理非序列化資源 (JDBC 連線等) |
| 活化 | 客戶端再次調用 → 容器反序列化還原 (`ejbActivate`) | 重建暫時性資源 |
| 移除 | 客戶端調用 `@Remove` 方法 → 容器呼叫 `@PreDestroy` → 銷毀實例 | 釋放資源、完成持久化 |
// 介面回傳具體參數化型別
HashMap getCartContents();
// 客戶端獲得完整型別資訊,無需強制轉型
for (String product : fullCart.keySet()) { // 型別安全迭代
Integer qty = fullCart.get(product); // 直接獲得 Integer
}
優勢:編譯期型別檢查、消除 ClassCastException、API 文檔化、IDE 智慧提示支援。
log4j.appender.R=org.apache.log4j.RollingFileAppender
log4j.appender.R.File=record.log
log4j.appender.R.layout=org.apache.log4j.PatternLayout
log4j.appender.R.layout.ConversionPattern=%p %d{hh:mm:ss} %t %c{1} -%m%n
log4j.appender.R.MaxBackupIndex=1
log4j.appender.R.MaxFileSize=100KB
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] (%F:%L) -%m%n
log4j.rootLogger=stdout,R
# JBoss 啟動
cd jboss_home/bin
run.bat -c all
# 專案目錄
ant build
ant deploy
ant run-client
| 特性 | Stateful | Stateless |
|---|---|---|
| 狀態保持 | 是 (會話專屬) | 否 (方法間無狀態) |
| 擴展性 | 較難 (需叢集複制/黏性 Session) | 極佳 (任意實例服務任意請求) |
| 資源佔用 | 較高 (保持實例) | 低 (池化複用) |
| 適用場景 | 購物車、精靈式流程、遊戲狀態 | CRUD 服務、工具方法、無狀態業務 |
| 現代替代 | Redis Session / JWT / 資料庫 | Spring @Service / REST Controller |