微信登录

AOP 概念 - 通知类型 - 前置、后置等通知

Java - Web - Spring 《AOP 概念 - 通知类型 - 前置、后置等通知》

一、AOP 概念引入

在软件开发中,我们常常会遇到一些与核心业务逻辑无关,但又贯穿于多个业务模块的功能,比如日志记录、事务管理、权限验证等。传统的编程方式会导致这些功能代码分散在各个业务方法中,造成代码的冗余和可维护性降低。AOP(Aspect-Oriented Programming,面向切面编程)应运而生,它可以将这些横切关注点(如日志、事务等)从业务逻辑中分离出来,以一种模块化的方式进行管理。

AOP 基本术语

  • 切面(Aspect):包含了多个通知和切入点的模块,它定义了横切关注点的行为。
  • 通知(Advice):切面中的具体执行代码,也就是在特定连接点上执行的操作。
  • 连接点(Join Point):程序执行过程中可以插入切面的点,如方法调用、异常抛出等。
  • 切入点(Pointcut):用于匹配连接点的表达式,指定了在哪些连接点上应用通知。
  • 目标对象(Target):被通知的对象,也就是包含核心业务逻辑的对象。
  • 代理对象(Proxy):通过 AOP 框架生成的对象,它包含了目标对象的功能和切面的功能。

二、通知类型

Spring AOP 提供了多种通知类型,每种通知类型在不同的时机执行。下面我们详细介绍几种常见的通知类型。

1. 前置通知(Before Advice)

前置通知在目标方法执行之前执行,通常用于进行一些预处理操作,如权限验证、参数检查等。

2. 后置通知(After Advice)

后置通知在目标方法执行之后执行,无论目标方法是否抛出异常都会执行,常用于资源清理等操作。

3. 返回通知(After Returning Advice)

返回通知在目标方法正常返回后执行,它可以访问目标方法的返回值。

4. 异常通知(After Throwing Advice)

异常通知在目标方法抛出异常时执行,常用于记录异常信息等操作。

5. 环绕通知(Around Advice)

环绕通知是最强大的通知类型,它可以在目标方法执行前后都进行操作,甚至可以决定是否执行目标方法。

下面是一个通知类型的总结表格:
| 通知类型 | 执行时机 |
| —— | —— |
| 前置通知 | 目标方法执行之前 |
| 后置通知 | 目标方法执行之后(无论是否抛出异常) |
| 返回通知 | 目标方法正常返回后 |
| 异常通知 | 目标方法抛出异常时 |
| 环绕通知 | 目标方法执行前后 |

三、演示代码

1. 创建 Maven 项目并添加依赖

pom.xml 中添加 Spring AOP 相关依赖:

  1. <dependencies>
  2. <dependency>
  3. <groupId>org.springframework</groupId>
  4. <artifactId>spring-context</artifactId>
  5. <version>5.3.23</version>
  6. </dependency>
  7. <dependency>
  8. <groupId>org.springframework</groupId>
  9. <artifactId>spring-aop</artifactId>
  10. <version>5.3.23</version>
  11. </dependency>
  12. <dependency>
  13. <groupId>org.aspectj</groupId>
  14. <artifactId>aspectjweaver</artifactId>
  15. <version>1.9.7</version>
  16. </dependency>
  17. </dependencies>

2. 创建目标对象

创建一个简单的业务类 UserService

  1. package com.example.demo.service;
  2. public class UserService {
  3. public String addUser(String username) {
  4. System.out.println("Adding user: " + username);
  5. return "User added successfully";
  6. }
  7. }

3. 创建切面类

创建一个切面类 LoggingAspect,包含各种通知类型:

  1. package com.example.demo.aspect;
  2. import org.aspectj.lang.JoinPoint;
  3. import org.aspectj.lang.ProceedingJoinPoint;
  4. import org.aspectj.lang.annotation.*;
  5. import org.springframework.stereotype.Component;
  6. @Aspect
  7. @Component
  8. public class LoggingAspect {
  9. // 前置通知
  10. @Before("execution(* com.example.demo.service.UserService.addUser(..))")
  11. public void beforeAdvice(JoinPoint joinPoint) {
  12. System.out.println("Before method: " + joinPoint.getSignature().getName());
  13. }
  14. // 后置通知
  15. @After("execution(* com.example.demo.service.UserService.addUser(..))")
  16. public void afterAdvice(JoinPoint joinPoint) {
  17. System.out.println("After method: " + joinPoint.getSignature().getName());
  18. }
  19. // 返回通知
  20. @AfterReturning(pointcut = "execution(* com.example.demo.service.UserService.addUser(..))", returning = "result")
  21. public void afterReturningAdvice(JoinPoint joinPoint, Object result) {
  22. System.out.println("Method returned: " + result);
  23. }
  24. // 异常通知
  25. @AfterThrowing(pointcut = "execution(* com.example.demo.service.UserService.addUser(..))", throwing = "ex")
  26. public void afterThrowingAdvice(JoinPoint joinPoint, Exception ex) {
  27. System.out.println("Method threw exception: " + ex.getMessage());
  28. }
  29. // 环绕通知
  30. @Around("execution(* com.example.demo.service.UserService.addUser(..))")
  31. public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
  32. System.out.println("Around advice before method");
  33. Object result = joinPoint.proceed();
  34. System.out.println("Around advice after method");
  35. return result;
  36. }
  37. }

4. 创建 Spring 配置类

  1. package com.example.demo.config;
  2. import org.springframework.context.annotation.ComponentScan;
  3. import org.springframework.context.annotation.Configuration;
  4. import org.springframework.context.annotation.EnableAspectJAutoProxy;
  5. @Configuration
  6. @ComponentScan(basePackages = "com.example.demo")
  7. @EnableAspectJAutoProxy
  8. public class AppConfig {
  9. }

5. 测试代码

  1. package com.example.demo;
  2. import com.example.demo.config.AppConfig;
  3. import com.example.demo.service.UserService;
  4. import org.springframework.context.annotation.AnnotationConfigApplicationContext;
  5. public class Main {
  6. public static void main(String[] args) {
  7. AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
  8. UserService userService = context.getBean(UserService.class);
  9. String result = userService.addUser("John");
  10. System.out.println("Final result: " + result);
  11. context.close();
  12. }
  13. }

代码解释

  • LoggingAspect 类使用 @Aspect 注解标记为切面类,@Component 注解将其注册为 Spring 组件。
  • @Before@After@AfterReturning@AfterThrowing@Around 注解分别定义了前置通知、后置通知、返回通知、异常通知和环绕通知。
  • execution(* com.example.demo.service.UserService.addUser(..)) 是切入点表达式,表示匹配 UserService 类的 addUser 方法。

四、运行结果

运行 Main 类,输出结果如下:

  1. Around advice before method
  2. Before method: addUser
  3. Adding user: John
  4. Around advice after method
  5. After method: addUser
  6. Method returned: User added successfully
  7. Final result: User added successfully

从输出结果可以看出,各种通知类型按照预期的顺序执行。环绕通知在目标方法执行前后都有输出,前置通知在目标方法执行前输出,后置通知在目标方法执行后输出,返回通知在目标方法正常返回后输出。

通过本文的介绍和示例代码,你应该对 Spring AOP 的通知类型有了更深入的理解。AOP 可以帮助我们将横切关注点从业务逻辑中分离出来,提高代码的可维护性和可复用性。