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

EJB 3 開發指南(2):有狀態 SessionBean 實作購物車完整範例

     編輯:Java 相關知識

Stateful Session Bean 能在多次方法呼叫間保持會話狀態,是實作購物車、精靈對話、多步驟表單的經典選擇。本文完整示範:遠端介面定義、Bean 實作、客戶端狀態保持驗證與 Web 環境下 Session 綁定最佳實踐。

一、遠端業務介面

package service;

import java.util.List;
import javax.ejb.Remote;

@Remote
public interface ShoppingCart {
    void addCommodity(String value);
    List<String> getCommodity();
}

二、Stateful Bean 實作

package service;

import java.util.ArrayList;
import java.util.List;
import javax.ejb.Stateful;

@Stateful
public class ShoppingCartBean implements ShoppingCart {

    private List<String> shoppingCart = new ArrayList<String>();

    @Override
    public void addCommodity(String value) {
        shoppingCart.add(value);
    }

    @Override
    public List<String> getCommodity() {
        return shoppingCart;
    }
}

關鍵差異:@Stateful vs @Stateless

特性@Stateless@Stateful
狀態保持無 (每次呼叫可能不同實例)有 (同一客戶端專屬實例)
實例池有 (共用池)無 (專屬實例)
鈍化/活化無有 (閒置超時序列化至硬碟)
併發存取多執行緒安全 (容器保證)單執行緒存取 (同一時間僅一客戶端)
適用場景無狀態服務、工具類、外觀購物車、精靈對話、多步驟流程

三、獨立 Java 客戶端驗證狀態保持

package test;

import java.util.List;
import java.util.Properties;
import javax.naming.InitialContext;
import service.ShoppingCart;

public class Client {
    public static void main(String[] args) throws Exception {
        Properties prop = new Properties();
        prop.setProperty("java.naming.factory.initial", "org.jnp.interfaces.NamingContextFactory");
        prop.setProperty("java.naming.provider.url", "localhost:1099");
        InitialContext ctx = new InitialContext(prop);

        // 關鍵:使用同一個 proxy 物件呼叫多次
        ShoppingCart shoppingCart = (ShoppingCart) ctx.lookup("ShoppingCartBean/remote");

        shoppingCart.addCommodity("自行車");
        shoppingCart.addCommodity("筆記本電腦");
        shoppingCart.addCommodity("液晶電視");

        List<String> commodities = shoppingCart.getCommodity();
        for (String commodity : commodities) {
            System.out.println(commodity);
        }
    }
}

執行結果

自行車
筆記本電腦
液晶電視

三次 addCommodity 累積在同一個 Bean 實例的 shoppingCart 中,最終 getCommodity 返回全部三項。

四、JNDI 配置檔 (jndi.properties)

置於客戶端專案 src 目錄:

java.naming.factory.initial=org.jnp.interfaces.NamingContextFactory
java.naming.provider.url=localhost:1099

有此檔案時,客戶端程式碼可簡化為:

InitialContext ctx = new InitialContext();  // 自動讀取 classpath 下的 jndi.properties
ShoppingCart shoppingCart = (ShoppingCart) ctx.lookup("ShoppingCartBean/remote");

五、Web 環境最佳實踐:綁定 HttpSession

Servlet/JSP 中不可 將 Stateful Bean 存在 ServletContext 或靜態變數 (會導致所有用戶共用同一購物車)。正確做法:

@WebServlet("/cart")
public class CartServlet extends HttpServlet {
    @EJB ShoppingCartRemote cartRemote;  // 或 JNDI 查詢

    protected void doPost(HttpServletRequest req, HttpServletResponse resp) {
        HttpSession session = req.getSession(true);
        ShoppingCart cart = (ShoppingCart) session.getAttribute("shoppingCart");

        if (cart == null) {
            // 首次建立:從 EJB 容器取得新實例
            try {
                InitialContext ctx = new InitialContext();
                cart = (ShoppingCart) ctx.lookup("ShoppingCartBean/remote");
                session.setAttribute("shoppingCart", cart);
            } catch (NamingException e) { throw new ServletException(e); }
        }

        String item = req.getParameter("item");
        if (item != null) cart.addCommodity(item);

        // 導向顯示頁
        req.getRequestDispatcher("/cart.jsp").forward(req, resp);
    }
}

六、Stateful Bean 逾時與移除配置

@Stateful
@StatefulTimeout(unit = TimeUnit.MINUTES, value = 30)  // EJB 3.1+, 30 分鐘閒置後自動移除
@Remove(retainIfException = false)  // 標記移除方法
public class ShoppingCartBean implements ShoppingCart {
    // ...

    @Remove  // 呼叫此方法後容器銷毀 Bean
    public void checkout() {
        // 結帳邏輯
    }
}

七、常見錯誤與陷阱

錯誤現象原因解決
狀態不保持 (每次只有一項)每次呼叫都 ctx.lookup() 取得新實例同一客戶端會話重用同一 proxy 物件
NoSuchEJBExceptionBean 已被容器移除 (逾時/呼叫 @Remove)捕獲例外重新 lookup、調整 @StatefulTimeout
序列化失敗Bean 持有非序列化欄位 (Connection、Thread)欄位標記 transient、在 @PrePassivate 釋放
併發存取例外多執行緒同時呼叫同一 Stateful Bean確保單執行緒存取、或改用 Stateless + 外部狀態存儲

八、效能考量

  • 實例記憶體佔用:每個用戶一個實例,大量併發用戶時記憶體壓力大
  • 鈍化/活化開銷:閒置用戶序列化至硬碟,再次存取需反序列化
  • 替代方案:高併發場景考慮 Stateless Bean + Redis/資料庫儲存狀態,以 Session ID 關聯
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved