Spring AOP(Aspect-Oriented Programming)是Spring框架中的一个核心模块,旨在通过面向切面的编程方式实现业务逻辑的横切关注点。AOP允许将那些与业务逻辑无关但会在多个模块中重复出现的代码(如日志记录、事务管理、异常处理、权限校验等)从核心业务逻辑中分离出来,通过切面来集中管理,从而减少代码冗余并提高代码的可维护性。
一、Spring AOP的核心概念:
-
Aspect(切面):切面是一个模块,它将横切关注点(如日志、事务)与核心业务逻辑分离。切面可以通过特定的注解或配置文件定义。(切点+通知)
-
Join Point(连接点):连接点是在应用程序执行的某个点,例如方法调用或异常抛出。Spring AOP支持的方法级别的连接点。
-
Advice(通知):通知是切面在连接点上执行的操作。Spring AOP有几种通知类型:
- 前置通知(Before):在目标方法执行之前执行。
- 后置通知(After):在目标方法执行之后执行。
- 返回通知(AfterReturning):在目标方法成功返回后执行。
- 异常通知(AfterThrowing):在目标方法抛出异常后执行。
- 环绕通知(Around):可以在目标方法执行前后自定义行为,最灵活的通知类型。
-
Pointcut(切入点):切入点是定义通知将会应用于哪些连接点的表达式。Pointcut是通过表达式(如正则表达式)来匹配方法或类的。
-
Weaving(织入):织入是将切面与目标对象结合的过程。Spring AOP通过运行时动态代理实现织入,而不是在编译或类加载阶段。
-
定义切面:通过使用
@Aspect
注解定义切面类。 -
定义通知方法:在切面类中使用
@Before
、@After
、@Around
等注解定义通知。 -
配置切入点表达式:通过注解或XML配置来指定切入点(即哪些方法会应用切面逻辑)。
@Aspect @Component public class LoggingAspect { // 定义前置通知,在执行某些包下的所有方法之前执行 @Before("execution(* com.example.service.*.*(..))") public void logBeforeMethod(JoinPoint joinPoint) { System.out.println("Method " + joinPoint.getSignature().getName() + " is called"); } // 定义后置通知 @After("execution(* com.example.service.*.*(..))") public void logAfterMethod(JoinPoint joinPoint) { System.out.println("Method " + joinPoint.getSignature().getName() + " has finished execution"); } }
二、切点(Pointcut)表达式
在Spring AOP中,@annotation
和execution
表达式都是用于定义切点的方式。它们的功能有所不同,主要用于匹配不同的切入点(Join Point)。下面详细解释两者的区别和使用场景:
1. @annotation
表达式
- 作用:
@annotation
用于匹配带有特定注解的方法。也就是说,它可以匹配那些被某个注解标记的方法。 - 适用场景:当你想拦截某些被特定注解标记的方法时,
@annotation
是很好的选择。常见场景包括自定义注解,用于权限控制、日志记录等。
假设我们有一个自定义注解 @Loggable
,并且希望拦截所有被这个注解标记的方法:
自定义注解:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Loggable {
}
切面使用@annotation
:
@Aspect
@Component
public class LoggingAspect {
// 使用 @annotation 切点表达式,匹配所有标记了 @Loggable 的方法
@Before("@annotation(com.example.demo.annotation.Loggable)")
public void logBefore(JoinPoint joinPoint) {
System.out.println("Before method: " + joinPoint.getSignature().getName());
}
}
行效果:logBefore
方法会在myMethod
方法调用前执行,因为myMethod
被@Loggable
注解标记了。
2. execution
表达式
- 作用:
execution
用于匹配方法的执行。它可以通过方法签名来精确地指定某个类中的方法或一组方法。 - 适用场景:当你想基于方法签名(比如方法的返回类型、包名、类名、方法名和参数类型)来拦截方法时,
execution
是最常用的选择。它比@annotation
更加灵活,可以匹配任何方法,而不需要方法上有特定注解。execution(modifier-pattern? return-type-pattern declaring-type-pattern? method-name-pattern(param-pattern) throws-pattern?)
- modifier-pattern:可选,方法的修饰符(如
public
、protected
等)。 - return-type-pattern:返回值类型,可以使用
*
来表示任意返回类型。 - declaring-type-pattern:方法所在的类或者包。
-
@annotation
:- 匹配依据:基于方法上的注解。
- 优点:适合用在需要通过注解来控制某些横切关注点的场景,如权限检查、日志记录等。注解可以提供额外的元数据,灵活性更大。
-
execution
:- 匹配依据:基于方法签名,如返回值、类名、方法名和参数类型等。
- 优点:适用于更广泛的场景,不需要修改现有的代码(比如添加注解),通过方法签名来进行方法的拦截。适合复杂的方法匹配。
三、通知
Spring AOP 提供了多种类型的通知(Advice),用于在不同的时机执行增强逻辑。每种通知类型都在目标方法的不同阶段进行操作。以下是主要的通知类型及其作用:
1. 前置通知(@Before)
- 作用:在目标方法执行之前执行,适用于在调用某个方法之前执行一些预处理逻辑,比如权限检查、日志记录等。
- 使用场景:记录方法调用的参数、检查安全权限、初始化资源等。
@Before("execution(* com.example.service.*.*(..))") public void beforeAdvice(JoinPoint joinPoint) { System.out.println("Before method: " + joinPoint.getSignature().getName()); }
2. 后置通知(@After)
@After("execution(* com.example.service.*.*(..))")
public void afterAdvice(JoinPoint joinPoint) {
System.out.println("After method: " + joinPoint.getSignature().getName());
}
3.环绕通知(@Around)
- 作用:最强大、最灵活的通知类型,可以在目标方法执行前后执行操作。通过
ProceedingJoinPoint.proceed()
来控制目标方法的执行,甚至可以完全阻止目标方法的执行。@Around("execution(* com.example.service.*.*(..))") public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("Before method: " + joinPoint.getSignature().getName()); // 执行目标方法 Object result = joinPoint.proceed(); System.out.println("After method: " + joinPoint.getSignature().getName()); return result; }
@Before 在目标方法执行前 参数检查、日志记录、权限检查等 @After 在目标方法执行后(无论是否抛出异常) 资源清理、日志记录等 @AfterReturning 在目标方法成功返回后 处理返回值、记录结果等 @AfterThrowing 在目标方法抛出异常时 异常日志、异常处理、错误通知等 @Around 在目标方法执行的前后 性能监控、事务管理、权限控制等
四、切⾯优先级
在Spring AOP中,当一个目标方法匹配到多个切面时,多个切面中的通知方法都会执行,执行的顺序是由@Order
注解来控制的。
@Order
注解的作用:
@Order
注解用于定义切面的执行优先级。数字越小,优先级越高,优先执行。- 适用于多个切面匹配到同一个方法的情况,通过
@Order
可以控制它们的执行顺序。 @Order
可以加在切面类上,用来指定这个切面的优先级。
环绕通知中的执行顺序:
最终的执行顺序:
- 优先级高的环绕通知(数字小的)会在目标方法执行前最先执行前置逻辑,但它会在目标方法执行后最后执行后置逻辑。
- 也就是说,环绕通知遵循“洋葱模型”或“嵌套模型”,内层的通知执行先完成,然后再回到外层的通知。
@Aspect @Component @Order(1) // 优先级最高 public class FirstAspect { @Before("execution(* com.example.demo.service.*.*(..))") public void beforeAdvice(JoinPoint joinPoint) { System.out.println("FirstAspect before method: " + joinPoint.getSignature().getName()); } @After("execution(* com.example.demo.service.*.*(..))") public void afterAdvice(JoinPoint joinPoint) { System.out.println("FirstAspect after method: " + joinPoint.getSignature().getName()); } } @Aspect @Component @Order(2) // 优先级次之 public class SecondAspect { @Before("execution(* com.example.demo.service.*.*(..))") public void beforeAdvice(JoinPoint joinPoint) { System.out.println("SecondAspect before method: " + joinPoint.getSignature().getName()); } @After("execution(* com.example.demo.service.*.*(..))") public void afterAdvice(JoinPoint joinPoint) { System.out.println("SecondAspect after method: " + joinPoint.getSignature().getName()); } }
Order(1)
的FirstAspect
:这是优先级最高的切面。它的@Before
通知会最先执行,但它的@After
通知会最后执行。@Order(2)
的SecondAspect
:这是优先级第二的切面。它的@Before
通知会在FirstAspect
的@Before
之后执行,但它的@After
会在FirstAspect
的@After
之前执行。SecondAspect
的@Before
FirstAspect
的@Before
- 目标方法执行
FirstAspect
的@After
SecondAspect
的@After