Spring AOP
# Spring 的 AOP
# AOP 的定义
AOP(Aspect Orient Programming),直译过来就是 面向切面编程
,AOP 是一种编程思想,是面向对象编程(OOP)的一种补充。(面向切面编程,实现在不修改源代码的情况下给程序动态统一添加额外功能的一种技术)
如果说 IOC 是 Spring 的核心,那么面向切面编程 AOP 就是 Spring 另外一个最为重要的核心。
# AOP 的应用场景
例子:比如说以下场景
- 日志记录
- 事务管理
- 权限验证
- 性能监测
# AOP 相关的术语
- 切面(Aspect):切面是一个模块化的单元,它横切应用程序的多个关注点。可以将切面看作是在应用程序中执行的特定行为的集合。比如,日志记录、安全性检查、性能监控等。切面通过在关注点(例如方法执行前后)插入代码来实现这些行为。
- 连接点(Join Point):连接点是应用程序执行过程中的特定点,例如方法的调用、异常的抛出等。切面通过在连接点上进行插入来实现它们的行为。连接点是 Spring AOP 可以插入切面的地方。
- 通知(Advice):通知是在特定连接点上执行的代码。它定义了在何时和如何执行切面的行为。通知可以在连接点之前、之后或周围执行,以插入切面的行为。常见的通知类型包括前置通知(在连接点之前执行)、后置通知(在连接点之后执行)、异常通知(在连接点抛出异常时执行)和环绕通知(在连接点前后执行)。
- 切点(Pointcut):切点定义了在应用程序中选择哪些连接点来应用切面。切点通过匹配连接点的表达式来确定应该在哪些连接点上执行通知。例如,可以使用表达式匹配所有特定的方法调用或类的所有方法。
- 引入(Introduction):引入允许在现有类中添加新方法或属性。它允许将额外的功能添加到现有的类中,而无需修改类的源代码。引入是通过在切面中定义额外的接口和实现类来实现的。
- 织入(Weaving):织入是将切面应用到目标对象上并创建新的代理对象的过程。织入可以在编译时、加载时或运行时进行。Spring AOP 使用运行时织入,它在应用程序运行时动态创建代理对象,并将切面应用到目标对象上。
# AOP 的通知类型
前置通知(Before Advice):前置通知在目标方法执行之前执行。它可以用于执行一些准备操作或参数验证等。前置通知可以通过修改传入的参数来影响目标方法的行为,但无法阻止目标方法的执行。
后置通知(After Advice):后置通知在目标方法执行之后(包括正常返回或异常抛出)执行。后置通知通常用于进行清理操作或记录方法的返回值。
返回通知(After Returning Advice):返回通知在目标方法成功执行并返回结果后执行。它可以访问目标方法的返回值,并根据需要对其进行处理。返回通知不能修改目标方法的结果。
异常通知(After Throwing Advice):异常通知在目标方法抛出异常后执行。它可以捕获目标方法抛出的异常,并执行相应的处理逻辑。异常通知可以选择性地捕获特定类型的异常,或者捕获所有类型的异常。
环绕通知(Around Advice):环绕通知是最强大和最灵活的通知类型。它可以完全控制目标方法的执行过程。环绕通知在目标方法执行之前和之后都可以执行自定义的行为。它可以决定是否执行目标方法,可以在执行前后修改传入的参数和返回的结果,甚至可以抛出异常来中断目标方法的执行。
# AOP 的切入点表达式语法
# 定义
- Execution:使用
execution
关键字指定方法的执行连接点。例如,execution(* com.example.service.*.*(..))
匹配 com.example.service 包下所有类的所有方法。 - Within:使用
within
关键字指定类或包的连接点。例如,within(com.example.service.*)
匹配 com.example.service 包中的所有类的所有方法。 - This:使用
this
关键字指定目标对象的类型。例如,this(com.example.service.MyService)
匹配实现了 MyService 接口的目标对象。 - Target:使用
target
关键字指定目标对象的类型。例如,target(com.example.service.MyService)
匹配目标对象是 MyService 类型的连接点。 - Args:使用
args
关键字指定连接点的参数类型。例如,args(java.lang.String)
匹配接受一个 String 类型参数的方法。 - @annotation:使用
@annotation
关键字指定标注了特定注解的连接点。例如,@annotation(org.springframework.transaction.annotation.Transactional)
匹配标注了 @Transactional 注解的方法。 - &&、||、!:使用逻辑运算符
&&
(与)、||
(或)、!
(非)来组合多个切入点表达式。
# 举例
- 匹配指定包下的所有类的所有方法:
- 表达式:
execution(* com.example.service.*.*(..))
- 描述:匹配
com.example.service
包下所有类的所有方法,不限定参数类型和数量。
- 表达式:
- 匹配指定注解标注的方法:
- 表达式:
@annotation(org.springframework.transaction.annotation.Transactional)
- 描述:匹配标注了
@Transactional
注解的方法。
- 表达式:
- 匹配指定类或子类的所有方法:
- 表达式:
within(com.example.service.MyService+)
- 描述:匹配
com.example.service.MyService
类及其所有子类的所有方法。
- 表达式:
- 匹配指定参数类型的方法:
- 表达式:
execution(* com.example.service.*.*(java.lang.String))
- 描述:匹配
com.example.service
包下所有类的接受一个String
类型参数的方法。
- 表达式:
- 组合多个条件:
- 表达式:
execution(* com.example.service.*.*(java.lang.String)) && @annotation(org.springframework.transaction.annotation.Transactional)
- 描述:匹配标注了
@Transactional
注解且接受一个String
类型参数的方法。
- 表达式:
注意
execution
和 within
区别
execution
:execution
关键字用于匹配方法的执行连接点。它指定了方法的签名(返回类型、方法名、参数类型)以及所在的类或包。例如,execution(* com.example.service.*.*(..))
匹配了com.example.service
包下所有类的所有方法。within
:within
关键字用于匹配类或包的连接点。它指定了目标方法所在的类或包。例如,within(com.example.service.*)
匹配了com.example.service
包中的所有类的所有方法。
主要区别在于匹配的粒度不同:
execution
关键字精确匹配方法的执行连接点,即匹配方法的签名。within
关键字匹配类或包的连接点,无论这些类或包中的方法如何。
因此, execution
关键字更加具体和精确,适用于需要精确匹配特定方法的情况。而 within
关键字更宽泛,适用于需要匹配整个类或包中的所有方法的情况。