Java 中的异常处理框架





5.00/5 (4投票s)
引言
在 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 种处理程序操作)
- 记录异常
- 将错误详情发送到某个 JMS 队列
- 发送邮件
- 执行某些默认操作
现在,通过添加上述处理程序来重写 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 中进行了测试。