
核心定义
@AfterThrowing
是 Spring AOP 中专门用于处理异常场景的**通知(Advice)**类型。它的核心作用是:
仅在目标方法(连接点)的执行过程中抛出异常 时,执行一段特定的逻辑。如果目标方法成功执行并正常返回,则该通知不会被执行。
你可以把它想象成 Java try...catch
语句块里的 catch
部分。当 try
块中的代码出现问题时,catch
块就会被激活。@AfterThrowing
的行为与此非常相似。
@AfterThrowing
的关键特性
- 执行时机 :只在目标方法抛出异常后执行。
- 访问异常信息 :可以! 这是
@AfterThrowing
的核心功能。你可以获取到目标方法抛出的那个异常对象,从而进行详细的记录或处理。 - 能否"处理"或"吞掉"异常 :不能! 这是一个非常关键且容易误解的点。
@AfterThrowing
通知执行完毕后,它不会"吞掉"或"消化"这个异常 。该异常会继续向上层调用栈抛出 ,最终由调用该方法的代码块来处理(比如被一个try...catch
捕获,或者导致程序终止)。它的主要职责是**"观察"和"记录"异常,而不是"解决"异常**。如果你想捕获异常并返回一个默认值来"修复"流程,必须使用@Around
(环绕通知)。
@AfterThrowing
能做什么?(主要应用场景)
它的应用场景非常专注于"出错了怎么办"。
-
异常日志记录 (Exception Logging)
- 这是最普遍、最重要的用途。当系统发生异常时,自动记录下详细的错误信息,包括哪个方法出错了、传入的参数是什么、抛出的是什么异常、异常的堆栈信息等。这对于事后排查问题至关重要。
- 示例 :"方法
deleteUser
执行失败!参数:[0]
。异常类型:IllegalArgumentException
,异常信息:用户ID无效
。"
-
监控与告警 (Monitoring & Alerting)
- 当捕获到特定类型的严重异常时(如
SQLException
,OutOfMemoryError
),可以触发告警机制。 - 示例:调用监控系统 API(如 Prometheus),或者发送邮件、短信、钉钉/Slack 消息给开发或运维人员,通知他们系统出现了严重故障。
- 当捕获到特定类型的严重异常时(如
-
事务回滚策略(特定场景)
- 虽然 Spring 的
@Transactional
默认在遇到RuntimeException
时会自动回滚,但有时你可能想在遇到某些特定的已检查异常 (Checked Exception) 时也触发回滚。可以在@AfterThrowing
中手动标记事务为"仅回滚"(rollback-only)。但这通常不是首选方案。
- 虽然 Spring 的
-
失败统计 (Failure Metrics)
- 记录某个或某类方法的失败次数,用于系统健康度分析。
如何获取异常信息?
要获取异常对象,你需要在 @AfterThrowing
注解中使用 throwing
属性。
throwing
属性的值是一个字符串,它指定了通知方法中哪个参数用来接收抛出的异常对象。- 这个参数名必须与通知方法签名中的一个参数名完全匹配。
语法:
java
@AfterThrowing(pointcut = "yourPointcut()", throwing = "ex")
public void myAdviceMethod(JoinPoint joinPoint, Throwable ex) {
// 'ex' 参数就会接收到目标方法抛出的异常对象
// 参数类型可以是 Throwable, Exception, 或更具体的异常类型
}
- 参数类型 :
- 使用
Throwable
或Exception
可以捕获所有类型的异常,通用性最强。 - 使用更具体的异常类型,如
IllegalArgumentException
,可以让该通知只在目标方法抛出这类特定异常或其子类异常时才被触发。
- 使用
代码示例
我们用一个例子来演示如何捕获并记录异常信息。
1. 业务服务类 (目标对象)
java
package com.example.service;
import org.springframework.stereotype.Service;
@Service
public class OrderService {
// 成功的方法
public void createOrder(String orderId) {
System.out.println("--- 核心业务逻辑:订单 " + orderId + " 创建成功 ---");
}
// 会抛出异常的方法
public void cancelOrder(String orderId) {
System.out.println("--- 核心业务逻辑:正在尝试取消订单 " + orderId + " ---");
if (orderId == null || orderId.trim().isEmpty()) {
throw new IllegalArgumentException("订单ID不能为空!");
}
// ... 模拟其他失败场景
throw new RuntimeException("数据库连接失败,无法取消订单!");
}
}
2. 切面类 (Aspect) 中定义 @AfterThrowing
通知
java
package com.example.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import java.util.Arrays;
@Aspect
@Component
public class ExceptionHandlingAspect {
// 拦截 OrderService 中的所有公共方法
@Pointcut("execution(public * com.example.service.OrderService.*(..))")
public void orderServicePointcut() {}
/**
* 定义异常通知
* 1. 使用 @AfterThrowing 注解
* 2. 指定切点 "orderServicePointcut()"
* 3. 使用 throwing = "exception" 来指定接收异常的参数名
*/
@AfterThrowing(pointcut = "orderServicePointcut()", throwing = "exception")
public void logException(JoinPoint joinPoint, Throwable exception) {
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
System.err.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
System.err.printf("[AOP 异常通知]: 方法 [%s] 执行时发生异常!%n", methodName);
System.err.printf("[AOP 异常通知]: 输入参数为: %s%n", Arrays.toString(args));
System.err.printf("[AOP 异常通知]: 异常类型为: %s%n", exception.getClass().getName());
System.err.printf("[AOP 异常通知]: 异常消息为: %s%n", exception.getMessage());
// 在实际项目中,这里会使用日志框架(如 SLF4J)记录异常堆栈
// log.error("Exception in method {}({}) with cause = '{}'", methodName, args, exception.getMessage(), exception);
System.err.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n");
}
}
3. 运行代码并观察输出
调用会抛出异常的方法 orderService.cancelOrder("ORDER123")
:
java
try {
orderService.cancelOrder("ORDER123");
} catch (Exception e) {
System.out.println(">>> 调用方捕获到最终异常: " + e.getMessage());
}
控制台输出 (注意 System.err
可能会让输出颜色不同或顺序稍有变化):
--- 核心业务逻辑:正在尝试取消订单 ORDER123 ---
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
[AOP 异常通知]: 方法 [cancelOrder] 执行时发生异常!
[AOP 异常通知]: 输入参数为: [ORDER123]
[AOP 异常通知]: 异常类型为: java.lang.RuntimeException
[AOP 异常通知]: 异常消息为: 数据库连接失败,无法取消订单!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
>>> 调用方捕获到最终异常: 数据库连接失败,无法取消订单!
观察结果分析:
@AfterThrowing
通知 (logException
方法) 被成功触发了。- 它成功获取到了方法名、参数和抛出的
RuntimeException
对象,并打印了其信息。- 最重要的是,异常没有被"吞掉",
try...catch
块最终还是捕获到了这个异常,证明了异常会继续传播。
调用成功的方法 orderService.createOrder("ORDER456")
:
--- 核心业务逻辑:订单 ORDER456 创建成功 ---
观察输出,没有任何关于
[AOP 异常通知]
的日志,证明了@AfterThrowing
在方法正常执行时不会被触发。
总结
特性 | 描述 |
---|---|
执行时机 | 仅在目标方法执行过程中抛出异常时。 |
核心用途 | 异常日志记录 、系统告警、失败统计。 |
能否访问异常 | 可以 ,通过 throwing 属性指定接收参数,获取异常对象。 |
能否处理异常 | 不可以。它只是一个"观察者",异常会继续向外抛出。 |
行为类似 | Java 的 catch 语句块,但它不会阻止异常继续传播。 |