Session Bean 生命週期由容器管理,EJB 3 提供四個標準回呼註解 (@PostConstruct、@PreDestroy、@PostActivate、@PrePassivate) 配合 @Resource 注入 SessionContext,讓開發者在關鍵時間點執行資源初始化、清理、鈍化/活化邏輯。本文完整示範各註解觸發時機與實作範例。
一、四大生命週期回呼註解
| 註解 | 觸發時機 | 適用 Bean 類型 | 典型用途 |
@PostConstruct | 建構子執行後、依賴注入完成後 | Stateless、Stateful、Singleton | 資源初始化:資料庫連線、快取載入、執行緒池建立 |
@PreDestroy | 容器銷毀 Bean 實例前 | Stateless、Stateful、Singleton | 資源釋放:關閉連線、儲存狀態、關閉執行緒池 |
@PrePassivate | Stateful Bean 被鈍化 (序列化至硬碟) 前 | Stateful | 釋放非序列化資源:JDBC Connection、Socket、開啟檔案 |
@PostActivate | Stateful Bean 被活化 (從硬碟還原) 後 | Stateful | 重建資源:重新取得連線、恢復暫存狀態 |
二、完整程式碼範例:購物車 Stateful Bean
package service;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import javax.ejb.PostActivate;
import javax.ejb.PrePassivate;
import javax.ejb.SessionContext;
import javax.ejb.Stateful;
@Stateful
public class ShoppingCartBean implements ShoppingCart {
private List<String> shoppingCart = new ArrayList<String>();
@Resource
private SessionContext sessionContext;
public ShoppingCartBean() {
System.out.println("constructor: " + sessionContext); // SessionContext 尚未注入
}
@PostConstruct
public void init() {
System.out.println("PostConstruct: " + sessionContext.getInvokedBusinessInterface());
// 初始化資源:如載入用戶偏好設定
}
@PreDestroy
public void destroy() {
System.out.println("PreDestroy");
// 釋放資源
}
@PrePassivate
public void passivate() {
System.out.println("PrePassivate");
// shoppingCart 可序列化,無需特殊處理
// 若持有非序列化資源 (Connection),此處必須釋放
}
@PostActivate
public void activate() {
System.out.println("PostActivate");
// 重建非序列化資源
}
@Override
public void addCommodity(String value) {
shoppingCart.add(value);
}
@Override
public List<String> getCommodity() {
return shoppingCart;
}
}
三、SessionContext 實用方法
| 方法 | 用途 |
getBusinessObject(Class) | 取得 Bean 的業務介面代理 (用於自我呼叫) |
getCallerPrincipal() | 取得呼叫者身分 (安全檢查) |
isCallerInRole(String) | 檢查呼叫者角色 |
setRollbackOnly() | 標記交易回滾 (CMT) |
getRollbackOnly() | 檢查交易是否已標記回滾 |
getTimerService() | 取得 TimerService (排程) |
getInvokedBusinessInterface() | 取得正在執行的業務介面 Class |
四、生命週期流程圖
Stateless Bean
實例化 → @PostConstruct → 服務就緒 → (處理請求) → @PreDestroy → 銷毀
Stateful Bean
實例化 → @PostConstruct → 服務就緒
│ │
│ 閒置超過 timeout │ 客戶端移除/容器回收
▼ ▼
@PrePassivate (鈍化) @PreDestroy (銷毀)
│ │
│ 客戶端再次呼叫 │
▼ │
@PostActivate (活化) ────────────┘
│
▼
繼續服務
五、關鍵注意事項
- 建構子中不可使用注入資源:
@Resource 注入發生在建構子之後,@PostConstruct 之前
- Stateless Bean 無鈍化/活化:
@PrePassivate/@PostActivate 僅對 Stateful 生效
- 例外處理:回呼方法拋出運行時異常會導致交易回滾 (CMT) 或實例銷毀
- 併發安全:Stateful Bean 同一時間僅服務一個客戶端,無併發問題;Singleton 需自行處理併發 (
@Lock)
- 序列化要求:Stateful Bean 所有非 transient 欄位必須可序列化,否則鈍化失敗
六、實戰模式:資源管理範本
@Stateful
public class ResourceManagedBean implements ResourceManaged {
@Resource private DataSource dataSource;
private Connection conn; // 非序列化,不可鈍化
@PostConstruct
public void init() {
try { conn = dataSource.getConnection(); }
catch (SQLException e) { throw new EJBException(e); }
}
@PrePassivate
public void passivate() {
try { if (conn != null && !conn.isClosed()) conn.close(); }
catch (SQLException e) { /* log */ }
conn = null;
}
@PostActivate
public void activate() {
try { conn = dataSource.getConnection(); }
catch (SQLException e) { throw new EJBException(e); }
}
@PreDestroy
public void destroy() {
try { if (conn != null && !conn.isClosed()) conn.close(); }
catch (SQLException e) { /* log */ }
}
// 業務方法使用 conn...
}
七、Singleton Bean (EJB 3.1+) 补充
@Startup:應用啟動時立即初始化 (觸發 @PostConstruct)
@DependsOn:指定初始化順序依賴
@Lock(LockType.READ/WRITE):方法級併發控制
- 同樣支援
@PostConstruct/@PreDestroy,無鈍化/活化