首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

JBoss EJB 3.0 Stateful Session Bean 實戰:Generics 型別安全購物車

     編輯:Java 相關知識

前言:狀態型 Bean 的經典應用場景

Stateful Session Bean 維護用戶會話狀態,購物車是教科書級範例。本範例結合 JDK 5.0 Generics,展示型別安全的 `HashMap` 實作,並驗證 `@Remove` 生命週期管理。

完整程式碼結構

ShoppingCart.java — 遠端業務介面


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 實例移除
}

ShoppingCartBean.java — 狀態型 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)、整合付款閘道等
    }
}

Client.java — 驗證生命週期與異常處理


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;
            }
        }
    }
}

關鍵技術點深度解析

1. @Stateful 生命週期與狀態管理

階段容器行為開發者關注點
建立客戶端 `home.create()` → 容器實例化 Bean、注入依賴、呼叫 `@PostConstruct`初始化資源、建立空集合
業務方法客戶端調用 → 容器攔截 (交易、安全) → 委派 Bean 實例執行緒安全 (Stateful Bean 單執行緒存取保證)
被動化容器依 LRU/時間將閒置 Bean 序列化至磁碟 (`ejbPassivate`)實作 `Serializable`、清理非序列化資源 (JDBC 連線等)
活化客戶端再次調用 → 容器反序列化還原 (`ejbActivate`)重建暫時性資源
移除客戶端調用 `@Remove` 方法 → 容器呼叫 `@PreDestroy` → 銷毀實例釋放資源、完成持久化

2. @Remove 機制與異常處理

  • 標記 `@Remove` 的方法**正常完成**後,容器移除 Bean 實例
  • 若方法拋出**應用例外** (Application Exception),**不移除** (預設 `retainIfException=false` 可覆蓋)
  • 若方法拋出**系統例外** (System Exception/RuntimeException),容器回滾交易並移除 Bean
  • 移除後,客戶端持有的 Stub 失效,再次調用拋出 `NoSuchObjectException` (包裝在 `EJBException` 中)

3. Generics 在 EJB 介面中的應用


// 介面回傳具體參數化型別
HashMap getCartContents();

// 客戶端獲得完整型別資訊,無需強制轉型
for (String product : fullCart.keySet()) {  // 型別安全迭代
    Integer qty = fullCart.get(product);    // 直接獲得 Integer
}

優勢:編譯期型別檢查、消除 ClassCastException、API 文檔化、IDE 智慧提示支援。

4. 序列化需求

  • Stateful Bean **必須實作 `Serializable`** (支援被動化/活化/叢集複制)
  • 所有欄位型別需可序列化:`HashMap` 符合
  • 非序列化資源 (DataSource、EntityManager、Socket) 需 `transient` + `ejbActivate` 重建

Log4j 配置 (同 Entity Bean 範例)


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

建置部署執行 (Ant)


# JBoss 啟動
cd jboss_home/bin
run.bat -c all

# 專案目錄
ant build
ant deploy
ant run-client

狀態型 vs 無狀態選擇指南

特性StatefulStateless
狀態保持是 (會話專屬)否 (方法間無狀態)
擴展性較難 (需叢集複制/黏性 Session)極佳 (任意實例服務任意請求)
資源佔用較高 (保持實例)低 (池化複用)
適用場景購物車、精靈式流程、遊戲狀態CRUD 服務、工具方法、無狀態業務
現代替代Redis Session / JWT / 資料庫Spring @Service / REST Controller

常見坑點

  • 忘記 implements Serializable:被動化失敗、叢集複制拋出 NotSerializableException
  • 非執行緒安全假設:雖容器保證單執行緒存取,但若 Bean 內部啟動背景執行緒需自行同步
  • @Remove 方法拋出檢查型例外:Bean 不會被移除,可能導致資源洩漏
  • 大量併發下實例耗盡:配置 `max-pool-size`、`cache-config` (JBoss `jboss.xml`)
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved