文章目录
Spring 的 AOP 中的一个核心概念是切点(Pointcut),切点表达式定义通知(Advice)执行的范围。
理解 AOP 通知参阅:《Spring AOP通知(Advice)详解》
一、概述
Spring AOP 只支持 Spring Bean 的方法切入,所以切点表达式只会匹配 Bean 类中的方法。
二、切点表达式配置
1. 内置配置
定义切面通知时,在
@Before
或
@AfterReturning
等通知注解中指定表达式。
@Aspect@ComponentpublicclassDemoAspect{@Before("execution(* cn.codeartist.spring.aop.advice.*.*(..))")publicvoiddoBefore(){// 自定义逻辑}}
2. 注解配置
在切面类中,先定义一个方法并使用
@Pointcut
注解来指定表达式。
然后在定义切面通知时,在通知注解中指定定义表达式的方法签名。
@Aspect@ComponentpublicclassDemoAspect{@Pointcut("execution(* cn.codeartist.spring.aop.aspectj.*.*(..))")privatevoidpointcut(){// 切点表达式定义方法,方法修饰符可以是private或public}@Before("pointcut()")publicvoiddoBefore(JoinPoint joinPoint){// 自定义逻辑}}
3. 公共配置
在任意类中,定义一个公共方法并使用
@Pointcut
注解来指定表达式。
publicclassCommonPointcut{@Pointcut("execution(* cn.codeartist.aop.*..*(..))")publicvoidpointcut(){// 注意定义切点的方法的访问权限为public}}
在切面类中定义切面通知时,在通知注解中指定定义表达式的方法签名全路径。
@Aspect@ComponentpublicclassDemoAspect{@Before("cn.codeartist.aop.CommonPointcut.pointcut()")publicvoidcommonPointcut(){// 自定义逻辑}}
二、切点表达式类型
Spring AOP 支持以下几种切点表达式类型。
**
execution
**
匹配方法切入点。根据表达式描述匹配方法,是最通用的表达式类型,可以匹配方法、类、包。
表达式模式:
execution(modifier? ret-type declaring-type?name-pattern(param-pattern) throws-pattern?)
表达式解释:
- modifier:匹配修饰符,
public, private
等,省略时匹配任意修饰符 - ret-type:匹配返回类型,使用
*
匹配任意类型 - declaring-type:匹配目标类,省略时匹配任意类型-
..
匹配包及其子包的所有类 - name-pattern:匹配方法名称,使用
*
表示通配符-*
匹配任意方法-set*
匹配名称以set
开头的方法 - param-pattern:匹配参数类型和数量-
()
匹配没有参数的方法-(..)
匹配有任意数量参数的方法-(*)
匹配有一个任意类型参数的方法-(*,String)
匹配有两个参数的方法,并且第一个为任意类型,第二个为String
类型 - throws-pattern:匹配抛出异常类型,省略时匹配任意类型
使用示例:
// 匹配public方法execution(public**(..))// 匹配名称以set开头的方法execution(* set*(..))// 匹配AccountService接口或类的方法execution(*com.xyz.service.AccountService.*(..))// 匹配service包及其子包的类或接口execution(* com.xyz.service..*(..))
**
within
**
匹配指定类型。匹配指定类的任意方法,不能匹配接口。
表达式模式:
within(declaring-type)
使用示例:
// 匹配service包的类within(com.xyz.service.*)// 匹配service包及其子包的类within(com.xyz.service..*)// 匹配AccountServiceImpl类within(com.xyz.service.AccountServiceImpl)
**
this
**
匹配代理对象实例的类型,匹配在运行时对象的类型。
注意:基于 JDK 动态代理实现的 AOP,
this
不能匹配接口的实现类,因为代理类和实现类并不是同一种类型,详情参阅《Spring中的AOP和动态代理》
表达式模式:
this(declaring-type)
使用示例:
// 匹配代理对象类型为service包下的类this(com.xyz.service.*)// 匹配代理对象类型为service包及其子包下的类this(com.xyz.service..*)// 匹配代理对象类型为AccountServiceImpl的类this(com.xyz.service.AccountServiceImpl)
**
target
**
匹配目标对象实例的类型,匹配 AOP 被代理对象的类型。
表达式模式:
target(declaring-type)
使用示例:
// 匹配目标对象类型为service包下的类target(com.xyz.service.*)// 匹配目标对象类型为service包及其子包下的类target(com.xyz.service..*)// 匹配目标对象类型为AccountServiceImpl的类target(com.xyz.service.AccountServiceImpl)
三种表达式匹配范围如下:
表达式匹配范围
within
this
target
接口✘✔✔实现接口的类✔〇✔不实现接口的类✔✔✔
**
args
**
匹配方法参数类型和数量,参数类型可以为指定类型及其子类。
使用
execution
表达式匹配参数时,不能匹配参数类型为子类的方法。
表达式模式:
args(param-pattern)
使用示例:
// 匹配参数只有一个且为Serializable类型(或实现Serializable接口的类)args(java.io.Serializable)// 匹配参数个数至少有一个且为第一个为Example类型(或实现Example接口的类)args(cn.codeartist.spring.aop.pointcut.Example,..)
**
bean
**
通过 bean 的 id 或名称匹配,支持
*
通配符。
表达式模式:
bean(bean-name)
使用示例:
// 匹配名称以Service结尾的beanbean(*Service)// 匹配名称为demoServiceImpl的beanbean(demoServiceImpl)
**
@within
**
匹配指定类型是否含有注解。当定义类时使用了注解,该类的方法会被匹配,但在接口上使用注解不匹配。
使用示例:
// 匹配使用了Demo注解的类@within(cn.codeartist.spring.aop.pointcut.Demo)
**
@target
**
匹配目标对象实例的类型是否含有注解。当运行时对象实例的类型使用了注解,该类的方法会被匹配,在接口上使用注解不匹配。
使用示例:
// 匹配对象实例使用了Demo注解的类@target(cn.codeartist.spring.aop.pointcut.Demo)
**
@annotation
**
匹配方法是否含有注解。当方法上使用了注解,该方法会被匹配,在接口方法上使用注解不匹配。
使用示例:
// 匹配使用了Demo注解的方法@annotation(cn.codeartist.spring.aop.pointcut.Demo)
**
@args
**
匹配方法参数类型是否含有注解。当方法的参数类型上使用了注解,该方法会被匹配。
使用示例:
// 匹配参数只有一个且参数类使用了Demo注解@args(cn.codeartist.spring.aop.pointcut.Demo)// 匹配参数个数至少有一个且为第一个参数类使用了Demo注解@args(cn.codeartist.spring.aop.pointcut.Demo,..)
切点表达式的参数匹配
切点表达式中的参数类型,可以和通知方法的参数通过名称绑定,表达式中不需要写类或注解的全路径,而且能直接获取到切面拦截的参数或注解信息。
@Before("pointcut() && args(name,..)")publicvoiddoBefore(String name){// 切点表达式增加参数匹配,可以获取到name的信息}@Before("@annotation(demo)")publicvoiddoBefore(Demo demo){// 这里可以直接获取到Demo注解的信息}
切点表达式的参数匹配同样适用于
@within, @target, @args
怎样编写一个好的切点表达式?
要使切点的匹配性能达到最佳,编写表达式时,应该尽可能缩小匹配范围,切点表达式分为三大类:
- 类型表达式:匹配某个特定切入点,如
execution
- 作用域表达式:匹配某组切入点,如
within
- 上下文表达式:基于上下文匹配某些切入点,如
this
、target
和@annotation
一个好的切点表达式应该至少包含前两种(类型和作用域)类型。
作用域表达式匹配的性能非常快,所以表达式中尽可能使用作用域类型。
上下文表达式可以基于切入点上下文匹配或在通知中绑定上下文。
单独使用类型表达式或上下文表达式比较消耗性能(时间或内存使用)。
三、切点表达式组合
使用
&&
、
||
和
!
来组合多个切点表达式,表示多个表达式“与”、“或”和“非”的逻辑关系。
这可以用来组合多种类型的表达式,来提升匹配效率。
// 匹配doExecution()切点表达式并且参数第一个为Account类型的方法@Before("doExecution() && args(account,..)")publicvoidvalidateAccount(Account account){// 自定义逻辑}
四、附录
1. 常用注解
注解描述
@Pointcut
指定切点表达式
2. 切点表达式类型
表达式类型描述
execution
匹配方法切入点
within
匹配指定类型
this
匹配代理对象实例的类型
target
匹配目标对象实例的类型
args
匹配方法参数
bean
匹配 bean 的 id 或名称
@within
匹配类型是否含有注解
@target
匹配目标对象实例的类型是否含有注解
@annotation
匹配方法是否含有注解
@args
匹配方法参数类型是否含有注解
3. 示例代码
Gitee 仓库:https://gitee.com/code_artist/spring
最新文章关注 CodeArtist 码匠公众号。
更多:Spring高效实践专栏
版权归原作者 码匠_CodeArtist 所有, 如有侵权,请联系我们删除。