
什么是通知 (Advice)?
通知 (Advice) 是切面(Aspect)的具体动作,它封装了你希望在目标方法执行时插入的代码逻辑。
简单来说,如果切面(Aspect)回答了“我要对谁(Pointcut)做什么(Advice)”,那么通知(Advice)就是那个具体的“做什么”。
通知定义了“在何时 (When)”和“做什么 (What)”
通知(Advice)的核心职责就是定义两件事:
1. 在何时 (When) - 执行时机
这指的是代码逻辑应该在目标方法执行过程中的哪个时间点被触发。Spring AOP 提供了五种标准的执行时机,通过不同的注解来表示:
| 通知类型 (Advice Type) | 注解 (Annotation) | “在何时” (When) |
|---|---|---|
| 前置通知 (Before Advice) | @Before | 在目标方法执行之前。 |
| 后置通知 (After Advice) | @After | 在目标方法执行之后(无论成功还是异常,类似于 finally)。 |
| 返回通知 (After Returning) | @AfterReturning | 在目标方法成功执行并返回结果之后。 |
| 异常通知 (After Throwing) | @AfterThrowing | 在目标方法执行过程中抛出异常之后。 |
| 环绕通知 (Around Advice) | @Around | 包裹着整个目标方法的执行过程,可以控制方法执行的前后。 |
选择正确的“时机”至关重要。例如:
- 权限检查必须在方法执行前 (
@Before)。 - 记录成功操作的日志应该在方法成功返回后 (
@AfterReturning),因为你需要知道操作确实成功了。 - 事务管理需要在方法执行前开启事务,在方法成功后提交,在异常时回滚,这完美契合环绕通知 (
@Around) 的能力。
2. 做什么 (What) - 执行的逻辑
这指的是你希望注入的具体代码,也就是横切关注点(Cross-Cutting Concern)的实现。这部分就是你用 Java 编写的普通方法体。
“做什么”的例子:
- 日志记录:
logger.info("方法开始执行,参数是...") - 权限验证:
if (!SecurityUtils.hasPermission(...)) { throw new AccessDeniedException("无权访问"); } - 事务管理:
transactionManager.begin(); ... pjp.proceed(); ... transactionManager.commit(); - 性能监控:
long start = System.currentTimeMillis(); ... long end = System.currentTimeMillis(); logger.info("耗时: " + (end - start) + "ms");
五种通知类型的详细解析和示例
让我们通过一个代码示例来更清晰地理解这五种通知。
假设我们有一个切点 serviceLog(),它匹配所有 service 包下的方法。
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class MyAdvice {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
// 定义一个切点,方便复用
@Pointcut("execution(* com.example.service.UserService.findUserById(..))")
public void userSearchPointcut() {}
/**
* 1. 前置通知 (@Before)
* 何时: 在 findUserById 方法执行前。
* 做什么: 打印方法即将开始执行的日志。
*/
@Before("userSearchPointcut()")
public void logBefore(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
// 这是 "做什么 (What)"
logger.info("[@Before] 方法 '{}' 即将执行,参数: {}", methodName, args);
}
/**
* 2. 后置通知 (@After)
* 何时: 在 findUserById 方法执行后 (无论是否抛出异常)。
* 做什么: 打印方法已执行结束的日志。
*/
@After("userSearchPointcut()")
public void logAfter(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
// 这是 "做什么 (What)"
logger.info("[@After] 方法 '{}' 执行结束。", methodName);
}
/**
* 3. 返回通知 (@AfterReturning)
* 何时: 在 findUserById 方法成功返回后。
* 做什么: 打印方法的返回值。
* returning = "result" 将方法的返回值绑定到名为 result 的参数上。
*/
@AfterReturning(pointcut = "userSearchPointcut()", returning = "result")
public void logReturning(JoinPoint joinPoint, Object result) {
String methodName = joinPoint.getSignature().getName();
// 这是 "做什么 (What)"
logger.info("[@AfterReturning] 方法 '{}' 成功返回,返回值: {}", methodName, result);
}
/**
* 4. 异常通知 (@AfterThrowing)
* 何时: 在 findUserById 方法抛出异常后。
* 做什么: 打印异常信息。
* throwing = "e" 将抛出的异常绑定到名为 e 的参数上。
*/
@AfterThrowing(pointcut = "userSearchPointcut()", throwing = "e")
public void logThrowing(JoinPoint joinPoint, Exception e) {
String methodName = joinPoint.getSignature().getName();
// 这是 "做什么 (What)"
logger.error("[@AfterThrowing] 方法 '{}' 抛出异常: {}", methodName, e.getMessage());
}
/**
* 5. 环绕通知 (@Around) - 最强大的通知
* 何时: 包裹整个目标方法的执行过程。
* 做什么: 记录方法执行耗时。
* 必须接收 ProceedingJoinPoint 作为参数,并手动调用 pjp.proceed() 来执行目标方法。
*/
@Around("execution(* com.example.service.UserService.updateUser(..))") // 换个方法演示
public Object profile(ProceedingJoinPoint pjp) throws Throwable {
long start = System.currentTimeMillis();
logger.info("[@Around] 进入环绕通知...");
// 手动执行目标方法
Object result = pjp.proceed(); // 这是调用目标方法的分界点
long end = System.currentTimeMillis();
logger.info("[@Around] 目标方法执行完毕,耗时: {} 毫秒", (end - start));
// 可以修改返回值
// return "some modified result";
return result;
}
}
总结
| 通知类型 | 定义了“何时” | 定义了“做什么” | 关键特征 |
|---|---|---|---|
@Before | 方法执行前 | 前置处理逻辑(如参数校验) | 无法阻止方法执行,无法获取返回值 |
@After | 方法执行后 | 资源清理等 finally 型逻辑 | 无论成功或异常都会执行 |
@AfterReturning | 方法成功返回后 | 针对成功结果的后处理 | 可以获取到方法的返回值 |
@AfterThrowing | 方法抛出异常后 | 异常记录和处理 | 可以获取到抛出的异常 |
@Around | 包裹方法执行 | 事务、缓存、性能监控等复杂逻辑 | 功能最强,可以决定是否执行目标方法、修改参数和返回值 |
因此,通知(Advice) 正是 AOP 中承载“横切”功能的代码实体。通过选择不同类型的通知,开发者可以精确地控制附加逻辑的执行时机和行为,从而实现优雅、无侵入的功能增强。
1329

被折叠的 条评论
为什么被折叠?



