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

EJB 異常處理最佳實踐:分類策略、日誌規範、避免重複記錄與 Web 層整合

     編輯:Java 相關知識

EJB (Enterprise JavaBeans) 作為 J2EE 核心組件,其異常處理機制直接關係生產系統的可維護性、問題定位速度與用戶體驗。本文基於 EJB 規範,系統闡述異常分類、容器處理邏輯、常見反模式、LoggableEJBException 模式、Web 層整合策略,構建企業級異常處理體系。

一、EJB 異常三大類別與容器行為

類別定義典型示例容器處理事務客戶端收到
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) | 掩蓋具體異常類型、違反精準處理原則 |

三、最佳實踐核心模式:LoggableEJBException

設計目標

  • 保留原始異常堆棧 (包裝而非替換)
  • 攜帶唯一錯誤 ID (時間戳 + 主機名 + 序列)
  • 標記是否已記錄 (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);
    }
}

實體/會話 Bean 中統一處理模板


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

四、異常記錄工具類:StackTraceUtil + Log4J


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 層整合:集中式異常處理

前提:Web 層 (Servlet/Struts/Spring MVC) 是 EJB 唯一客戶端。

優勢

  • 記錄點集中:BaseAction/Interceptor/ControllerAdvice 一處攔截
  • 避免 EJB 層每個方法都寫記錄代碼
  • 統一錯誤頁面、用戶友好提示、錯誤 ID 反饋給用戶

Struts 1.x 示例


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(...);
}

Spring MVC 現代寫法


@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());
    }
}

六、應用程序異常 vs 系統異常的判斷灰度區

| 場景 | 建議分類 | 理由 | |------|----------|------| | 實體 Bean ejbCreate 中 FinderException 查找關聯實體失敗 | **系統異常** | 客戶端視角:創建訂單失敗,內部關聯查找屬實現細節 | | 會話 Bean 業務邏輯中 ObjectNotFoundException (用戶資料不存在) | **應用程序異常** | 業務語義明確,客戶端可引導用戶完善資料 | | 會話 Bean 調用另一會話 Bean CreateException | **系統異常** | 基礎設施故障,非業務預期 | | 實體 Bean 方法中 SQLException | **系統異常** | 數據層故障,不可恢復 |

七、代碼重構輔助異常定位

  • 大方法拆小:單一職責,堆棧行號精準
  • 精準捕獲:catch (SQLException e) 而非 catch (Exception e)
  • 編譯器優化可能內聯方法,保留調試信息:-g:vars,lines,source

八、總結

構建健壯 EJB 異常體系的三板斧:

  1. 分類正確:應用異常走業務流,系統異常走容器回滾+包裝
  2. 包裝保真:LoggableEJBException 保留根因+唯一 ID+防重記錄標記
  3. 記錄集中:Web 層統一攔截記錄,EJB 層只拋不記

配合 Log4J/Logback 異步寫入、ELK 集中分析、錯誤 ID 關聯工單系統,即可實現「用戶反饋錯誤 ID → 運維秒級定位代碼行 → 修復上線」的閉環。這才是企業級系統應有的異常處理成熟度。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved