Spring AOP @AfterReturning (返回通知)的使用场景
核心定义
@AfterReturning
是 Spring AOP 中一个非常重要的通知(Advice类型。它的核心作用是:
仅在目标方法(连接点)成功执行并正常返回之后,执行一段特定的逻辑。如果目标方法在执行期间抛出异常,则该通知不会被执行。
它就像是为一次成功的任务举行的“庆功会”或“成果验收”。只有当任务圆满完成(方法正常返回),这个后续步骤才会被触发。
@AfterReturning
的关键特性
- 执行时机:只在目标方法成功返回后执行。
- 访问返回值:可以! 这是它与
@After
(后置通知) 最核心的区别。@AfterReturning
的设计目的之一就是让我们能够获取并处理目标方法的返回值。 - 修改返回值:不能! 这是一个非常重要的点。
@AfterReturning
通知是在返回值已经生成并准备交还给调用者之后执行的,所以你可以在通知里读取它、记录它、或者基于它执行其他操作,但你无法直接修改这个返回值来影响最终的调用结果。如果需要修改返回值,必须使用功能更强大的@Around
(环绕通知)。
@AfterReturning
能做什么?(主要应用场景)
既然可以获取返回值,它的应用场景就非常明确了,主要集中在对“结果”的后处理上。
-
对返回结果进行日志记录
- 这是最直接的应用。记录方法成功执行后返回了什么内容,对于调试和审计非常有用。
- 示例:“方法
findUserById(101)
成功执行,返回值是User{id=101, name='Alice'}
”。
-
对返回结果进行二次处理或触发后续操作
- 根据返回的结果,执行一些非侵入性的附加业务逻辑。
- 示例:当一个“注册用户”的方法成功返回一个新的
User
对象后,@AfterReturning
通知可以获取到这个User
对象,然后调用消息队列服务,发送一封欢迎邮件。这个“发送邮件”的逻辑与“注册用户”的核心业务解耦,非常清晰。
-
数据缓存
- 当一个查询方法成功返回数据后,可以将这个返回结果存入缓存(如 Redis、Caffeine)。
- 示例:
getProductById(id)
成功返回Product
对象后,@AfterReturning
通知将这个Product
对象以id
为键存入缓存。
-
数据转换或格式化(用于日志或监控,而非修改)
- 获取到返回值后,可以将其转换为特定的格式(如 JSON)再进行记录。
如何获取返回值?
要获取返回值,你需要在 @AfterReturning
注解中使用 returning
属性。
returning
属性的值是一个字符串,它指定了通知方法中哪个参数用来接收返回值。- 这个参数名必须与通知方法签名中的一个参数名完全匹配。
语法:
@AfterReturning(pointcut = "yourPointcut()", returning = "result")
public void myAdviceMethod(JoinPoint joinPoint, Object result) {// 'result' 参数就会接收到目标方法的返回值// 参数类型最好是 Object,以增加通用性
}
代码示例
让我们用一个例子来演示如何获取并处理返回值。
1. 业务服务类 (目标对象)
package com.example.service;import org.springframework.stereotype.Service;@Service
public class ProductService {// 成功返回一个字符串public String getProductById(long id) {System.out.println("--- 核心业务逻辑:正在查询产品 " + id + " ---");return "Product-" + id;}// 抛出异常的方法public void deleteProduct(long id) {System.out.println("--- 核心业务逻辑:正在删除产品 " + id + " ---");if (id <= 0) {throw new RuntimeException("产品ID无效,删除失败!");}System.out.println("产品 " + id + " 删除成功。");}
}
2. 切面类 (Aspect) 中定义 @AfterReturning
通知
package com.example.aop;import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;@Aspect
@Component
public class ResultLoggingAspect {// 拦截 ProductService 中的所有公共方法@Pointcut("execution(public * com.example.service.ProductService.*(..))")public void productServicePointcut() {}/*** 定义返回通知* 1. 使用 @AfterReturning 注解* 2. 指定切点 "productServicePointcut()"* 3. 使用 returning = "returnValue" 来指定接收返回值的参数名*/@AfterReturning(pointcut = "productServicePointcut()", returning = "returnValue")public void logResult(JoinPoint joinPoint, Object returnValue) {String methodName = joinPoint.getSignature().getName();System.out.println("==================================================");System.out.printf("[AOP 返回通知]: 方法 [%s] 成功执行完成。%n", methodName);// 检查返回值类型,并进行处理if (returnValue != null) {System.out.printf("[AOP 返回通知]: 返回值为: [%s], 返回值类型为: %s%n",returnValue, returnValue.getClass().getName());// 在这里可以做更多事情,比如将 returnValue 存入缓存} else {// 对于 void 方法,returnValue 为 nullSystem.out.println("[AOP 返回通知]: 方法没有返回值 (void)。");}System.out.println("==================================================\n");}
}
3. 运行代码并观察输出
调用成功返回数据的方法 productService.getProductById(888L)
:
--- 核心业务逻辑:正在查询产品 888 ---
==================================================
[AOP 返回通知]: 方法 [getProductById] 成功执行完成。
[AOP 返回通知]: 返回值为: [Product-888], 返回值类型为: java.lang.String
==================================================
@AfterReturning
被触发,并成功获取到了返回值"Product-888"
。
调用抛出异常的方法 productService.deleteProduct(-1L)
:
try {productService.deleteProduct(-1L);
} catch (Exception e) {System.err.println("调用方捕获到异常: " + e.getMessage());
}
输出:
--- 核心业务逻辑:正在删除产品 -1 ---
调用方捕获到异常: 产品ID无效,删除失败!
观察输出,你会发现没有任何关于
[AOP 返回通知]
的日志。这证明了当方法抛出异常时,@AfterReturning
通知是不会被执行的。
总结
特性 | 描述 |
---|---|
执行时机 | 仅在目标方法成功执行并正常返回后。 |
核心用途 | 对方法的返回结果进行后续处理,如日志记录、结果缓存、触发异步任务。 |
能否访问返回值 | 可以,通过 returning 属性指定接收参数。 |
能否修改返回值 | 不可以。如需修改,请使用 @Around 通知。 |
与 @After 的区别 | @After 总是执行(像 finally ),而 @AfterReturning 只在成功时执行;@After 无法获取返回值,而 @AfterReturning 可以。 |