一.什么是 AOP?
AOP 的目的是将横切关注点(如日志记录、事务管理、权限控制、接口限流、接口幂等等)从核心业务逻辑中分离出来,通过动态代理、字节码操作等技术,实现代码的复用和解耦,提高代码的可维护性和可扩展性。OOP 的目的是将业务逻辑按照对象的属性和行为进行封装,通过类、对象、继承、多态等概念,实现代码的模块化和层次化(也能实现代码的复用),提高代码的可读性和可维护性。
二.AOP 关键术语
- 横切关注点(cross-cutting concerns) :多个类或对象中的公共行为(如日志记录、事务管理、权限控制、接口限流、接口幂等等)。
- 切面(Aspect):对横切关注点进行封装的类,一个切面是一个类。切面可以定义多个通知,用来实现具体的功能。
- 连接点(JoinPoint):连接点是方法调用或者方法执行时的某个特定时刻(如方法调用、异常抛出等)。
连接点是程序执行过程中可以插入一个切面的具体位置。简单地说,连接点是程序中可以应用通知(Advice)的点。连接点的例子包括方法调用、方法执行、构造器调用、构造器执行、字段访问等。在 Spring AOP 中,连接点主要指方法执行。
例如,在下面的类中:
public class AccountService {public void deposit(double amount) {// ...}public void withdraw(double amount) {// ...} }
deposit
和withdraw
方法的调用和执行就是连接点。 - 通知(Advice):通知就是切面在某个连接点要执行的操作。通知有五种类型,分别是前置通知(Before)、后置通知(After)、返回通知(AfterReturning)、异常通知(AfterThrowing)和环绕通知(Around)。前四种通知都是在目标方法的前后执行,而环绕通知可以控制目标方法的执行过程。
- 切点(Pointcut):一个切点是一个表达式,它用来匹配哪些连接点需要被切面所增强。切点可以通过注解、正则表达式、逻辑运算等方式来定义。比如
execution(* com.xyz.service..*(..))
匹配com.xyz.service
包及其子包下的类或接口。
切点(Pointcut)
切点是一个匹配连接点的集合,它是一个表达式,用于选择一组连接点。切点定义了在那些连接点上应用通知。切点表达式可以基于方法名称、方法参数、注解、包名等进行匹配。
例如,下面的切点表达式匹配 AccountService
类中的所有方法:
@Pointcut("execution(* com.example.service.AccountService.*(..))")
public void allAccountServiceMethods() {}
这个切点定义了匹配 com.example.service.AccountService
类的所有方法调用的连接点。
- 连接点(Join Point): 程序中的一个点,这个点可以被增强(例如方法调用、方法执行、字段访问)。就是类中的方法
- 切点(Pointcut): 用于定义哪些特定的连接点会被增强的表达式。它是从一组连接点中过滤出需要增强的子集。在AOP中我们定义的要增强的方法
- 织入(Weaving):织入是将切面和目标对象连接起来的过程,也就是将通知应用到切点匹配的连接点上。常见的织入时机有两种,分别是编译期织入(Compile-Time Weaving 如:AspectJ)和运行期织入(Runtime Weaving 如:AspectJ、Spring AOP)。
三.AOP 常见的通知类型有哪些?
- Before(前置通知):目标对象的方法调用之前触发
- After (后置通知):目标对象的方法调用之后触发
- AfterReturning(返回通知):目标对象的方法调用完成,在返回结果值之后触发
- AfterThrowing(异常通知):目标对象的方法运行中抛出 / 触发异常后触发。AfterReturning 和 AfterThrowing 两者互斥。如果方法调用成功无异常,则会有返回值;如果方法抛出了异常,则不会有返回值。
- Around (环绕通知):编程式控制目标对象的方法调用。环绕通知是所有通知类型中可操作范围最大的一种,因为它可以直接拿到目标对象,以及要执行的方法,所以环绕通知可以任意的在目标对象的方法调用前后搞事,甚至不调用目标对象的方法
@Pointcut是定义哪些切点要执行AOP功能,@Before/after是定义具体哪些方法在前/后……执行,后者的参数是前者的子集
四.注解
`@Pointcut` 是 AspectJ 框架中的一个注解,用于定义切入点 (Pointcut)。切入点是指那些可以插入切面(Aspect)的具体位置或情况。使用 `@Pointcut` 注解可以让你在代码中更灵活地定义哪些方法或代码块将应用特定的切面逻辑。就是用来定义哪些方法会自动使用我们AOP封装的功能
以下是一些使用 `@Pointcut` 的主要用法:
基本定义
你可以使用 `@Pointcut` 注解来定义一个简单的切入点。下面是一个基本示例,定义了一个切入点,该切入点匹配所有 `public` 访问修饰符的 `find*` 方法:
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class LoggingAspect {
@Pointcut("execution(public * find*(..))")
private void allFindMethods() {
// pointcut signature method
}
}
使用其他注解
你也可以结合其他注解来更灵活地定义切入点。比如,你可以定义一个切入点,匹配所有带有特定注解的类或方法:
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class LoggingAspect {
@Pointcut("@annotation(org.springframework.web.bind.annotation.GetMapping)")
private void allGetMappingMethods() {
// pointcut signature method
}
}
结合使用多个切入点
你还可以结合多个切入点来定义更复杂的匹配条件。下面是一个示例,结合了两个不同的切入点:
```java
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.annotation.Before;
@Aspect
public class LoggingAspect {
@Pointcut("execution(public * *(..))")
private void anyPublicOperation() {}
@Pointcut("within(com.example.myapp.service..*)")
private void inServiceLayer() {}
@Before("anyPublicOperation() && inServiceLayer()")//这里的before,after就是上面介绍的通知类型,用来定义方法什么时候执行AOP封装的功能,前/后/……
public void logBefore() {
System.out.println("Logging before a public service method");
}
}
```
### 切入点表达式
切入点表达式是 `@Pointcut` 注解字符串值中的一种语言。以下是一些常见的语法:
- `execution(<method-pattern>)`:匹配某个方法的执行。
- `within(<type-pattern>)`:匹配某个类型中的所有方法。
- `this(<type>)`:匹配所有实现给定接口的目标实例。
- `target(<type>)`:匹配给定类型的目标实例。
- `args(<argument-types>)`:匹配参数为指定类型的方法。
@annotation:按注解进行方法匹配,比如
@Pointcut("@annotation(MyCustomAnnotation)")
AOP匹配所有有 MyCustomAnnotation注解的方法
总结来说,切入点是AOP中的核心概念之一, `@Pointcut` 注解让切入点的定义变得灵活且易于管理, 从而实现代码的关注点分离,提升代码的可维护性和可读性。