65.9K
CodeProject 正在变化。 阅读更多。
Home

Java 中的异常处理框架

starIconstarIconstarIconstarIconstarIcon

5.00/5 (4投票s)

2017 年 5 月 1 日

CPOL

5分钟阅读

viewsIcon

19254

downloadIcon

353

引言

在 Java 中,通过添加 try-catch-finally 块来处理异常非常容易。对于简单的应用程序或测试目的来说,这已经足够了。但是,当您考虑开发企业级应用程序时,您会发现需要一些结构良好的代码来以更好、更有效的方式处理异常。在本文中,我将尝试解释如何以更好、更受管理的方式处理异常。

背景

在传统方法中,在开发企业级应用程序时可能会遇到许多问题。
起初很难识别问题。但随着应用程序规模的增加,您就会明白了。
以下是关键因素:

1. 可维护性: 您经常会看到,为不同类型或相同类型的异常编写了相同的处理程序代码。如果需要进行任何更改,将很难跟踪。因此,对于模块化应用程序来说,维护代码本身是一个巨大的挑战。

2. 可重用性: 如果目的相同,为什么不为不同的异常使用相同的代码片段呢?正如第 1 点所述,这也会降低相同目的代码的可重用性。

3. 可插拔和灵活性: 今天,处理程序的唯一工作就是记录异常。将来,可能需要发送电子邮件,或者需要将其发送到某些 JMS 队列,或者需要执行某些回滚任务,或者可能需要执行所有这些任务,或者可能是这些任务的组合。在常规方法中,几乎不可能按需修改代码,因为处理代码与异常源是紧密耦合的。

4. 可读性: 经常可以看到 catch 块本身包含一些复杂的代码,这不仅增加了方法的长度,还降低了代码的可读性。

5. 易于使用: 在遗留应用程序中,有时很难追踪异常处理。在传统方法中,您需要花费足够的时间来理解流程。

以上问题非常普遍,每个人都至少遇到过一次。

该框架背后的关键策略是将处理程序逻辑与异常源分离,使它们松耦合,并在需要时挂接处理程序。分离处理程序逻辑并不是什么大问题。我们可以创建一个处理程序类并将其逻辑放在其中。但主要问题是如何挂接它,以便它能提高可重用性、可维护性、灵活性并保持简单。下面我们就开始吧。

让我们来看下面的简单示例

   public void  checkLogin() {
        try  {
            
            //Statement 1 which may throw  new UserNotExistException("User not Exist.");

            //Statement 2 which may throw new InvalidCredentialException("Invalid Credentials.");
            
            //Statement 3 which may throw new DBException("Failed to retrieve data.");

            //Statement 4 which may throw new Exception("General Exception.");
            
        } catch (UserNotExistException uex) {
            //To do 
        } catch (InvalidCredentialException fex) {
            //To do 
        } catch (DBException dex) {
            //To do 
        } catch (Exception ex) {
            //To do 
        } 
    }

上面的例子是一个非常简单的用户验证示例。我们可以看到这个方法可能会抛出 4 种类型的异常(在实际应用中可能不同)。还有一些 catch 块来捕获这些异常。我们对这种情况非常熟悉。

根据要求,如果发生任何异常,应用程序可能需要执行以下处理程序操作(单个操作或组合操作)来处理每种类型的异常:(出于示例目的,我使用了 4 种处理程序操作)

  1. 记录异常
  2. 将错误详情发送到某个 JMS 队列
  3. 发送邮件
  4. 执行某些默认操作

现在,通过添加上述处理程序来重写 catch 块

    public boolean checkLogin() { 
        try  {
            
            //Statement 1 which may throw  new UserNotExistException("User not Exist.");

            //Statement 2 which may throw new InvalidCredentialException("Invalid Credentials.");
            
            //Statement 3 which may throw new DBException("Failed to retrieve data.");

            //Statement 4 which may throw new Exception("General Exception.");
            
        }  catch (UserNotExistException uex) {
            //To do 
            
            // want to log exception
            // want to do some default work 
            
        } catch (InvalidCredentialException fex) {
            //To do 
            
            // want to log exception
            // want to send mail 
            // want to do some default work
            
        } catch (DBException dex) {
            //To do 
            
            // want to log exception
            // want to send error details to some jms queue
            // want to send mail 
            // want to do some default work 
            
        } catch (Exception ex) {
            //To do 
            // want to log exception
            
        } 
        return <some_flag>;
    }

当您在模块化应用程序中工作时,您会体会到维护代码库的痛苦。现在来看下面的例子,它以一种有效的方式完成了与上面示例相同的事情。

    @HandleExceptions({
        @HandleException(exceptionType = UserNotExistException.class, handlers = {
             "logExceptionHandler","defaultExceptionHandler" 
        }) ,
        @HandleException(exceptionType = InvalidCredentialException.class, handlers = {
            "logExceptionHandler", "sendEmailExceptionHandler", "defaultExceptionHandler" 
        }) ,
        @HandleException(exceptionType = DBException.class, handlers = {
            "logExceptionHandler", "JMSExceptionHandler", "sendEmailExceptionHandler", "defaultExceptionHandler"  
        }) ,
        @HandleException(exceptionType = Exception.class, handlers = {
            "logExceptionHandler" 
        }) 
    })
    public void checkLogin() {
        //Statement 1 which may throw  new UserNotExistException("User not Exist.");

        //Statement 2 which may throw new InvalidCredentialException("Invalid Credentials.");
        
        //Statement 3 which may throw new DBException("Failed to retrieve data.");

        //Statement 4 which may throw new Exception("General Exception.");
    }

如果我们忽略注解部分(稍后介绍),任何人都可以轻松理解它比上面的例子更简单、更清晰。您可以看到方法体内部没有 catch 块。现在方法更清晰、更易读,并且只专注于其预期功能。
现在开始深入研究代码库以理解框架。我在 Spring 框架之上构建了这个框架。

该框架背后的关键思想是在异常抛出后、被捕获之前拦截它,并执行所需的必要操作。为了实现这一点,我在 Spring 应用程序中使用了 Spring AOP 的 @AfterThrowing 通知。(如果您不熟悉 AOP(面向切面编程),请访问 https://docs.springframework.org.cn/spring/docs/current/spring-framework-reference/html/aop.html)。

深入了解

现在是时候查看代码库了。在 com.sm.exceptionhandler.annotation 包中有三个用户定义的注解。

  • Handlers.java
  • HandleException.java
  • HandleExceptions.java

在该框架中,有两种方法可以将处理程序绑定到异常:一种是

Handlers.java:用于注解自定义异常类,以注册默认处理程序。

@Handlers({"fileExceptionHandler", "defaultExceptionHandler", "logExceptionHandler", "JMSExceptionHandler"})
public class UserNotExistException extends BaseException {

另一种是

HandleException.java:可能抛出异常的类必须用此注解进行注解。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface HandleException {
    static final String DEFAULT_HANDLER = "defaultExceptionHandler";
    public Class<?> exceptionType() default Exception.class;
    public String[] handlers() default {DEFAULT_HANDLER};
}

它有两个属性。要响应特定异常,用户需要将异常类型指定为 exceptionType,并指定要对此异常做出反应的处理程序列表。这里 handlers 是 Spring bean 的 ID。可以为特定异常指定多个处理程序。

如果可能出现多种类型的异常,并且您想单独处理它们,那么 exceptionType 参数将帮助您解决此问题。

您可以使用以下代码来处理多种类型的异常:

@HandleExceptions({
     @HandleException(exceptionType = FileNotExistException.class, handlers = {
         "defaultExceptionHandler", "logExceptionHandler",
         "JMSExceptionHandler"}),
     @HandleException(exceptionType = UserNotExistException.class, handlers = {
         "logExceptionHandler", "JMSExceptionHandler"})
})
public Integer testExceptionOne(int type) {
}

或者您可以编写最简单的代码,它将对每个异常执行操作。

@HandleException(handlers = {"fileExceptionHandler",
        "defaultExceptionHandler", "logExceptionHandler"})
public void testExceptionTwo() {
    throw new RuntimeException("Io Exception");
}

HandleException.java:包含 HandleException 的列表。

HandleExceptionAspect.java 是在运行时与应用程序进行织入的切面。使用 @AfterThrowing,切面只能在抛出给定类型的异常时执行通知,并且还可以在通知体中访问抛出的异常。

ExceptionHandlerFramework.java 包含调用和处理相应处理程序的主要逻辑。

其余的 Java 类非常直接,不言自明。

关注点

它非常简单易用。

请注意

附加的代码库是一个 NetBeans 项目,并在 Java 1.7 中进行了测试。

© . All rights reserved.