EJB (Enterprise JavaBeans) 作為 J2EE 核心組件,其異常處理機制直接關係生產系統的可維護性、問題定位速度與用戶體驗。本文基於 EJB 規範,系統闡述異常分類、容器處理邏輯、常見反模式、LoggableEJBException 模式、Web 層整合策略,構建企業級異常處理體系。
| 類別 | 定義 | 典型示例 | 容器處理 | 事務 | 客戶端收到 |
|---|---|---|---|---|---|
| JVM 異常 | JVM 拋出,不可恢復 | OutOfMemoryError | 無法處理,需重啟服務器 | 標記回滾 | 無 (進程崩潰) |
| 應用程序異常 | 受查異常,聲明於 Remote 接口,非 RuntimeException 子類 | CreateException, FinderException, 業務自定義異常 | 不包裝,原樣拋給客戶端 | 默認不回滾 (除非調用 setRollbackOnly) | 原異常類型 |
| 系統異常 | 非受查異常或無法恢復的受查異常 | NullPointerException, SQLException, NamingException | 包裝為 RemoteException/EJBException 拋給客戶端 | 強制回滾並清理 | RemoteException 或子類 |
catch (Ne) { throw new EJBException("msg"); } | 丟失堆棧、無法定位行號、根因消失 |
| **記錄到控制台** | e.printStackTrace(); | 生產環境無控制台、I/O 同步阻塞、日誌丟失 |
| **重複記錄** | 實體 Bean、會話 Bean、Web 層各記一次 | 日誌膨脹、無唯一 ID 關聯、排查困難 |
| **捕獲 Exception 大雜燴** | catch (Exception e) | 掩蓋具體異常類型、違反精準處理原則 |
isLogged),防止重複記錄EJBException,自動觸發容器回滾與包裝
public class LoggableEJBException extends EJBException {
private boolean logged = false;
private final String uniqueId;
public LoggableEJBException(Throwable cause) {
super(cause);
this.uniqueId = ExceptionIdGenerator.generate(); // 如: 20240115-143022-host-001
}
public String getUniqueId() { return uniqueId; }
public boolean isLogged() { return logged; }
public void setLogged(boolean logged) { this.logged = logged; }
}
public class ExceptionIdGenerator {
private static final AtomicLong SEQ = new AtomicLong();
public static String generate() {
return new SimpleDateFormat("yyyyMMdd-HHmmss").format(new Date())
+ "-" + InetAddress.getLocalHost().getHostName()
+ "-" + String.format("%03d", SEQ.incrementAndGet() % 1000);
}
}
try {
OrderHome home = EJBHomeFactory.getOrderHome();
Order order = home.findByPrimaryKey(id);
} catch (NamingException | SQLException e) {
throw new LoggableEJBException(e); // 包裝原始異常
} catch (RemoteException re) {
Throwable detail = re.getCause(); // RemoteException 的 detail 域存真實異常
if (detail instanceof Exception) {
throw new LoggableEJBException((Exception) detail);
}
throw new LoggableEJBException(re);
}
public class ExceptionLogUtil {
// 創建帶唯一ID的異常
public static LoggableEJBException createLoggableEJBException(Throwable t) {
if (t instanceof LoggableEJBException) return (LoggableEJBException) t;
if (t instanceof RemoteException) {
Throwable d = ((RemoteException) t).getCause();
return new LoggableEJBException(d instanceof Exception ? (Exception)d : t);
}
return new LoggableEJBException(t instanceof Exception ? (Exception)t : new Exception(t));
}
// 記錄異常 (僅記錄一次)
public static void logIfNeeded(LoggableEJBException le, String loggerName) {
if (le.isLogged()) return;
String trace = StackTraceUtil.getStackTrace(le);
Category.getInstance(loggerName).error(le.getUniqueId() + ": " + trace);
le.setLogged(true);
}
}
public class StackTraceUtil {
public static String getStackTrace(Throwable t) {
StringWriter sw = new StringWriter();
t.printStackTrace(new PrintWriter(sw));
return sw.toString();
}
}
前提:Web 層 (Servlet/Struts/Spring MVC) 是 EJB 唯一客戶端。
BaseAction/Interceptor/ControllerAdvice 一處攔截
public class BaseAction extends Action {
public ActionForward execute(ActionMapping m, ActionForm f, HttpServletRequest req, HttpServletResponse res) {
try {
return doExecute(m, f, req, res);
} catch (LoggableEJBException le) {
ExceptionLogUtil.logIfNeeded(le, getClass().getName());
req.setAttribute("errorId", le.getUniqueId());
return mapping.findForward("systemError");
} catch (Exception e) {
LoggableEJBException le = ExceptionLogUtil.createLoggableEJBException(e);
ExceptionLogUtil.logIfNeeded(le, getClass().getName());
req.setAttribute("errorId", le.getUniqueId());
return mapping.findForward("systemError");
}
}
protected abstract ActionForward doExecute(...);
}
@ControllerAdvice
public class GlobalExceptionHandler {
private static final Logger LOG = LoggerFactory.getLogger(GlobalExceptionHandler.class);
@ExceptionHandler(LoggableEJBException.class)
public ModelAndView handle(LoggableEJBException le, HttpServletRequest req) {
ExceptionLogUtil.logIfNeeded(le, GlobalExceptionHandler.class.getName());
return new ModelAndView("error", "errorId", le.getUniqueId());
}
}
ejbCreate 中 FinderException 查找關聯實體失敗 | **系統異常** | 客戶端視角:創建訂單失敗,內部關聯查找屬實現細節 |
| 會話 Bean 業務邏輯中 ObjectNotFoundException (用戶資料不存在) | **應用程序異常** | 業務語義明確,客戶端可引導用戶完善資料 |
| 會話 Bean 調用另一會話 Bean CreateException | **系統異常** | 基礎設施故障,非業務預期 |
| 實體 Bean 方法中 SQLException | **系統異常** | 數據層故障,不可恢復 |
catch (SQLException e) 而非 catch (Exception e)-g:vars,lines,source構建健壯 EJB 異常體系的三板斧:
LoggableEJBException 保留根因+唯一 ID+防重記錄標記配合 Log4J/Logback 異步寫入、ELK 集中分析、錯誤 ID 關聯工單系統,即可實現「用戶反饋錯誤 ID → 運維秒級定位代碼行 → 修復上線」的閉環。這才是企業級系統應有的異常處理成熟度。