springboot教程-aop注解 @Aspect、@Before、@After、@AfterReturning、 @AfterThrowing

本文通过案例介绍Spring AOP中@Aspect、@Before、@After、@AfterReturning、@AfterThrowing等注解的使用方法及执行顺序。通过具体代码演示如何在不修改业务代码的情况下增加日志记录功能。

概述

本文通过案例讲解:@Aspect@Before@After@AfterReturning@AfterThrowing 注解的作用

尤其是:@Before@After@AfterReturning@AfterThrowing 注解的执行顺序

案例

使用注解的方式实现aop,实现 不修改 UserSrvImpl 源代码的情况下,增加打印日志的功能

创建maven工程

修改pom.xml

添加依赖

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>2.2.2.RELEASE</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
    </dependency>
</dependencies>

注解需要依赖 aspectjweaver

实现Controller

@RestController
public class StudentCtrl {

    @RequestMapping("/test")
    public JsonResult test() {
        System.out.println("test()");
        return new JsonResult(0,"执行成功!");
    }
}

实现Logger日志类

//由spring容器管理,该类不属于Controller、Service、Repository,所以用@Component注解
@Component
//表明该类为切面类
@Aspect
public class Logger {
    /*
    在 top.malaoshi.controller 包下的类 的 方法,都能注入代码
     */
    @Pointcut("execution(* top.malaoshi.controller.*.*(..))")
    public void pt(){
    }
    /*
    在 com.yingqi.service 包下的类 的 方法,执行之前 会先执行下面方法
     */
    @Before("pt()")
    public void beforePrint(){
        System.out.println("=======before");
    }
    /*
    在 com.yingqi.service 包下的类 的 方法,执行之后(不论是否抛异常) 会执行下面方法
     */
    @After("pt()")
    public void afterPrint(){
        System.out.println("======after");
    }
    /*
    在 com.yingqi.service 包下的类 的 方法,执行之后(没抛异常) 会执行下面方法
     */
    @AfterReturning("pt()")
    public void afterReturningPrint(){
        System.out.println("======afterReturning");
    }
    /*
    在 com.yingqi.service 包下的类 的 方法,执行之后(抛异常了) 会执行下面方法
     */
    @AfterThrowing("pt()")
    public void afterThrowingPrint(){
        System.out.println("======异常通知afterThrowingPrint");
    }
}

测试-执行成功

访问 Controller,执行结果如下:

=======before
test()
======after
======afterReturning

执行成功的通知顺序:

  1. before
  2. 目标方法
  3. after
  4. afterReturning

测试-执行报错

修改 Controller

test() 方法抛出异常:

@RequestMapping("/test")
public JsonResult test() {
    System.out.println("test()");
    int i=1/0;
    return new JsonResult(0,"执行成功!");
}

访问Controller,执行结果如下:

=======before
test()
======after
======异常通知afterThrowingPrint

执行报错的通知顺序:

  1. before
  2. 目标方法
  3. after
  4. afterThrowingPrint
<think>首先,用户的问题是:“我想获取@AfterReturning@AfterThrowing注解的使用详细解释”。所以,我需要专注于解释这两个注解@AfterReturning@AfterThrowing。 根据系统级指令: - 所有行内数学表达式必须使用$...$格式。但这里没有数学内容,所以可能不需要。 - 独立公式使用$$...$$,但同样,主题是编程,可能不适用。 - LaTeX语法正确:如果有公式,但这里没有。 - 使用中文回答:整个响应必须是中文。 - 生成相关问题:在末尾添加相关问题。 - 回答中引用的段落末尾添加引用标识:参考给定的引用[1]到[4]。 用户提供了四个引用: - [1]:讨论程序异常时的行为:@AfterThrowing执行,@AfterReturning不执行;@Around中如果有异常,环绕逻辑也不执行。 - [2]:给出了@Before注解的示例代码。 - [3]:讨论了@After注解的示例。 - [4]:讨论了@Around和@Before的执行顺序。 尽管引用中提到其他注解,但用户只问@AfterReturning@AfterThrowing。所以,我应该专注于这些,并参考引用中的相关信息。 关键点从引用: - [1]:当方法正常返回时,@AfterReturning注解的方法执行;当方法抛出异常时,@AfterThrowing注解的方法执行,而@AfterReturning不执行。 - 其他引用提供了上下文,但没有直接详细解释这两个注解。例如,[2]和[3]是关于@Before@After的,[4]是关于执行顺序的。 我需要基于常识和提供的引用来解释: - @AfterReturning:在目标方法成功执行并返回值后执行。用于执行需要在方法正常返回后执行的代码,如日志记录或清理工作。 - @AfterThrowing:在目标方法抛出异常后执行。用于执行异常处理、错误日志记录等。 解释结构: - 定义每个注解- 如何在Spring AOP中使用它们。 - 提供简单代码示例(基于引用)。 - 讨论执行顺序和行为。 生成相关问题: - 指令说“生成相关问题”,所以我需要在响应末尾添加相关问题。 - 例子中是关于快速排序的三个问题,所以我应该为这个主题生成2-3个相关问题。 响应结构: - 先回答问题:详细解释@AfterReturning@AfterThrowing- 使用中文。 - 添加引用标识:如果引用某部分,在末尾加[^n]。引用是[1]到[4],所以我应该参考它们。 - 最后,添加相关问题。 大纲: 1. 介绍@AfterReturning- 定义:在方法正常返回后执行。 - 用途:例如,记录返回值或执行后置逻辑。 - 代码示例:基于引用[3]或[4],但需要适配。引用[3]有@After的示例,我可以修改为@AfterReturning- 引用[3]展示@After的写法:`@After("execution(* com.yida.aop.common.ICalculatorAop.*(..))")` - 类似地,我可以写出@AfterReturning的代码。 - 参数:可以访问返回值,如`returning`属性。 2. 介绍@AfterThrowing- 定义:在方法抛出异常后执行。 - 用途:异常处理、错误记录。 - 代码示例:引用[1]提到@AfterThrowing执行时@AfterReturning不执行。 - 参数:可以访问抛出的异常,如`throwing`属性。 3. 比较和注意事项: - 执行顺序:引用[4]讨论执行顺序,但针对其他注解。一般来说,@AfterReturning/@AfterThrowing在方法执行后运行,但有条件。 -@After的关系:@After在方法正常或异常后都执行,而@AfterReturning只在正常返回时执行,@AfterThrowing只在异常时执行。 4. 完整示例代码:基于引用[2]或[3],创建一个简单的例子。 相关问题: 1. 如何在@AfterReturning中访问方法的返回值? 2. @AfterThrowing如何处理不同类型的异常? 3. @AfterReturning@AfterThrowing的执行顺序与其他通知(如@Before)有何关系? 确保语言清晰、逐步解释。 </think>### @AfterReturning@AfterThrowing 注解使用详解 在 Spring AOP 中,`@AfterReturning` 和 `@AfterThrowing` 是用于定义通知(Advice)的核心注解,它们分别处理目标方法正常返回和异常抛出的场景。以下将逐步解释这两个注解的功能、用法、参数和注意事项,结合代码示例说明。解释基于 Spring AOP 的机制,并参考了提供的引用信息。 #### 1. **@AfterReturning 注解** - **定义与用途**: `@AfterReturning` 注解用于标识一个通知方法,该方法在目标方法**正常执行完毕并返回结果后**执行。它适用于日志记录、结果处理或资源清理等后置逻辑,但仅在方法未抛出异常时触发[^1][^3]。 - **关键属性**: - `value` 或 `pointcut`:指定切入点表达式,定义哪些方法会被拦截(例如:`@AfterReturning("execution(* com.example.service.*.*(..))")`)。 - `returning`:指定一个参数名,用于在通知方法中访问目标方法的返回值(例如:`returning = "result"`),返回值类型需与方法签名匹配[^3][^4]。 - **代码示例**: 以下是一个简单示例,展示如何在 Spring AOP 中使用 `@AfterReturning`。示例中,当 `UserService` 的 `getUser` 方法正常返回时,记录返回值。 ```java @Aspect @Component public class LoggingAspect { // 定义切入点:拦截 UserService 的所有方法 @Pointcut("execution(* com.example.service.UserService.*(..))") public void serviceMethods() {} // @AfterReturning 通知:正常返回后执行 @AfterReturning(pointcut = "serviceMethods()", returning = "result") public void afterReturning(JoinPoint joinPoint, Object result) { String methodName = joinPoint.getSignature().getName(); System.out.println("方法 " + methodName + " 成功执行,返回值: " + result); } } // 目标服务类 @Service public class UserService { public String getUser(String id) { return "用户-" + id; // 正常返回字符串 } } ``` - **说明**: 当 `getUser` 方法被调用并正常返回时,`afterReturning` 方法会打印返回值。如果方法抛出异常,此通知不会执行[^1][^3]。 - **注意事项**: - `returning` 属性是必须的,通知方法参数必须与返回值类型兼容(如使用 `Object` 可匹配任意类型)。 - 与 `@After` 的区别:`@After` 在方法正常或异常后都会执行,而 `@AfterReturning` 仅在正常返回时执行[^3][^4]。 #### 2. **@AfterThrowing 注解** - **定义与用途**: `@AfterThrowing` 注解用于标识一个通知方法,该方法在目标方法**抛出异常后**执行。它适用于异常处理、错误日志记录或资源回滚等场景。当方法抛出异常时,`@AfterReturning` 不会执行[^1][^4]。 - **关键属性**: - `value` 或 `pointcut`:切入点表达式,定义拦截范围。 - `throwing`:指定一个参数名,用于在通知方法中访问抛出的异常对象(例如:`throwing = "ex"`),异常类型需与方法签名匹配[^1][^4]。 - **代码示例**: 以下示例展示 `@AfterThrowing` 的使用。当 `OrderService` 的 `createOrder` 方法抛出异常时,记录异常信息。 ```java @Aspect @Component public class ErrorHandlingAspect { // 切入点:拦截 OrderService 的方法 @Pointcut("execution(* com.example.service.OrderService.*(..))") public void orderMethods() {} // @AfterThrowing 通知:异常抛出后执行 @AfterThrowing(pointcut = "orderMethods()", throwing = "ex") public void afterThrowing(JoinPoint joinPoint, Exception ex) { String methodName = joinPoint.getSignature().getName(); System.out.println("方法 " + methodName + " 抛出异常: " + ex.getMessage()); // 可选:执行错误处理逻辑,如发送警报 } } // 目标服务类 @Service public class OrderService { public void createOrder() { throw new RuntimeException("库存不足"); // 模拟异常 } } ``` - **说明**: 调用 `createOrder` 时,如果抛出异常,`afterThrowing` 方法会捕获并打印异常消息。同时,`@AfterReturning` 通知不会触发[^1][^4]。 - **注意事项**: - `throwing` 属性是必须的,通知方法参数必须匹配异常类型(如 `Exception` 或具体子类)。 - 如果异常未被捕获,Spring 会将其传播给调用者;通知本身不处理异常,仅用于辅助逻辑[^1][^4]。 #### 3. **执行顺序与行为对比** - **整体顺序**: 在 Spring AOP 中,通知的执行顺序遵循:`@Around`(前半部分) → `@Before` → 目标方法 → `@AfterReturning`(正常时)或 `@AfterThrowing`(异常时) → `@After` → `@Around`(后半部分,正常时)。 - 正常返回:`@AfterReturning` → `@After`。 - 异常抛出:`@AfterThrowing` → `@After`,且 `@AfterReturning` 和 `@Around` 的后半部分被跳过[^1][^4]。 - **关键行为**: - 互斥性:`@AfterReturning` 和 `@AfterThrowing` 不会同时执行,取决于目标方法是否抛出异常[^1]。 - 与 `@After` 的协同:`@After` 始终执行(无论正常或异常),适合放置通用清理代码[^3][^4]。 - **最佳实践**: - 使用 `returning` 和 `throwing` 属性时,确保参数名在通知方法中一致。 - 切入点表达式应精确匹配目标方法,避免过度拦截影响性能。 #### 4. **总结** - **@AfterReturning**:专注于方法正常返回后的逻辑,通过 `returning` 访问返回值。 - **@AfterThrowing**:处理异常场景,通过 `throwing` 访问异常对象。 - 两者均需与切入点表达式结合,且在异常情况下行为互斥[^1][^3][^4]。实际开发中,建议结合 `@Around` 或 `@After` 实现完整事务管理。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值