1. 概述
本文深入讲解 Spring AOP 中的切点(Pointcut)表达式语言,帮助你在实际项目中更精准地定位织入逻辑的执行位置。
在 AOP 编程中,有几个核心概念需要先明确:
- 连接点(Join Point):程序执行过程中的某个具体步骤,比如方法调用、异常抛出等。在 Spring AOP 中,连接点仅限于方法执行。
- 切点(Pointcut):一个布尔表达式,用于匹配特定的连接点。
- 切点表达式语言:Spring 提供的一套 DSL,用来声明式地定义切点。
简单说,切点就是“你想在哪些方法上织入增强逻辑”的规则描述。理解它,是玩转 Spring AOP 的关键一步。踩过几次坑后你会发现,写错一个表达式可能导致性能问题或逻辑错乱,所以务必认真对待。
2. 基本用法
切点表达式最常见的使用方式是配合 @Pointcut
注解,定义一个可复用的切点签名:
@Pointcut("within(@org.springframework.stereotype.Repository *)")
public void repositoryClassMethods() {}
这个方法本身不执行任何逻辑,它的名字 repositoryClassMethods
就是切点签名,后续通知(Advice)可以直接引用它:
@Around("repositoryClassMethods()")
public Object measureMethodExecutionTime(ProceedingJoinPoint pjp) throws Throwable {
long start = System.currentTimeMillis();
try {
return pjp.proceed();
} finally {
long executionTime = System.currentTimeMillis() - start;
logger.info("方法执行耗时: " + executionTime + "ms");
}
}
✅ 优点:解耦表达式与通知逻辑,便于复用和维护。
除了注解方式,XML 配置中也可以定义切点(虽然现在用得少了):
<aop:config>
<aop:pointcut id="anyDaoMethod"
expression="@target(org.springframework.stereotype.Repository)"/>
</aop:config>
⚠️ 提示:XML 方式适用于老项目或特殊场景,现代 Spring Boot 项目推荐使用注解。
3. 切点指示器(Pointcut Designators)
切点表达式的核心是指示器(PCD),它决定了匹配的维度。以下是 Spring 支持的主要 PCD。
3.1 execution
execution
是最常用、最精确的 PCD,用于匹配方法执行。
匹配特定方法:
@Pointcut("execution(public String com.baeldung.pointcutadvice.dao.FooDao.findById(Long))")
但这种写法太死板,实际开发中更常用通配符实现灵活匹配:
@Pointcut("execution(* com.baeldung.pointcutadvice.dao.FooDao.*(..))")
解释一下通配符含义:
部分 | 含义 |
---|---|
* |
匹配任意返回类型 |
.* |
匹配类中任意方法名 |
(..) |
匹配任意数量参数(包括零个) |
✅ 推荐:日常使用中,execution
+ 通配符组合最实用,能覆盖大部分场景。
3.2 within
within
按类型或包名进行匹配,适合批量拦截某个类或包下的所有方法。
匹配某个类的所有方法:
@Pointcut("within(com.baeldung.pointcutadvice.dao.FooDao)")
匹配某个包及其子包下的所有类:
@Pointcut("within(com.baeldung..*)")
⚠️ 注意:..*
表示当前包及所有子包,别漏掉 *
。
✅ 对比 execution
:within
更简洁,适合“全包拦截”类需求,但粒度较粗。
3.3 this 与 target
这两个容易混淆,关键在于代理机制的不同。
this
:匹配代理对象本身是某个类型的连接点(适用于 CGLIB 代理)target
:匹配目标对象是某个类型的连接点(适用于 JDK 动态代理)
举个例子:
public class FooDao implements BarDao {
// ...
}
如果 FooDao
实现了接口,Spring 默认使用 JDK 代理,此时代理对象是 Proxy
类型,但实现了 BarDao
接口。因此:
@Pointcut("target(com.baeldung.pointcutadvice.dao.BarDao)")
✅ 正确:target
匹配的是目标类 FooDao
是否实现 BarDao
。
如果类没有实现接口,或启用了 proxyTargetClass=true
,则使用 CGLIB 代理,生成的是 FooDao
的子类,此时可以用 this
:
@Pointcut("this(com.baeldung.pointcutadvice.dao.FooDao)")
✅ 建议:除非明确需要区分代理类型,否则优先使用 target
,更符合直观预期。
3.4 args
args
用于匹配方法参数的类型。
匹配参数只有一个 Long
类型的方法:
@Pointcut("execution(* *..find*(Long))")
匹配第一个参数为 Long
,后续参数任意的方法:
@Pointcut("execution(* *..find*(Long,..))")
✅ 使用场景:常用于拦截带 ID 查询的方法,做缓存或日志。
⚠️ 注意:args(Long)
和 args(long)
是不同的,前者匹配包装类,后者匹配基本类型。
3.5 @target
@target
匹配目标类上标注了指定注解的连接点。
例如,拦截所有被 @Repository
标注的类中的方法:
@Pointcut("@target(org.springframework.stereotype.Repository)")
✅ 适用场景:统一处理 DAO 层的日志、事务、异常等。
3.6 @args
@args
匹配方法参数的运行时类型上带有指定注解的情况。
假设我们想记录所有接收 @Entity
注解对象的方法调用:
@Pointcut("@args(com.baeldung.pointcutadvice.annotations.Entity)")
public void methodsAcceptingEntities() {}
然后在通知中获取参数:
@Before("methodsAcceptingEntities()")
public void logMethodAcceptionEntityAnnotatedBean(JoinPoint jp) {
logger.info("接收到 @Entity 注解的 bean: " + jp.getArgs()[0]);
}
✅ 典型用途:审计、安全校验、自动埋点。
⚠️ 注意:@args
是在运行时检查参数类型的注解,不是方法参数本身的注解。
3.7 @within
@within
匹配所在类被指定注解标注的连接点。
@Pointcut("@within(org.springframework.stereotype.Repository)")
这和下面写法等价:
@Pointcut("within(@org.springframework.stereotype.Repository *)")
✅ 与 @target
的区别:
@within
:只关心类上是否有注解(编译期已知)@target
:关心运行时目标对象的类型是否带注解(支持继承)
一般情况下两者行为一致,但在复杂继承场景下可能有差异。
3.8 @annotation
@annotation
匹配方法上标注了指定注解的连接点。
先定义一个自定义注解:
@Pointcut("@annotation(com.baeldung.pointcutadvice.annotations.Loggable)")
public void loggableMethods() {}
然后拦截所有带 @Loggable
的方法:
@Before("loggableMethods()")
public void logMethod(JoinPoint jp) {
String methodName = jp.getSignature().getName();
logger.info("正在执行方法: " + methodName);
}
✅ 最佳实践:结合自定义注解,实现声明式日志、权限、缓存等,代码清晰且解耦。
4. 组合切点表达式
切点支持逻辑运算符组合,实现更复杂的匹配规则:
@Pointcut("@target(org.springframework.stereotype.Repository)")
public void repositoryMethods() {}
@Pointcut("execution(* *..create*(Long,..))")
public void firstLongParamMethods() {}
@Pointcut("repositoryMethods() && firstLongParamMethods()")
public void entityCreationMethods() {}
支持的操作符:
操作符 | 含义 |
---|---|
&& |
与(都满足) |
` | |
! |
非(取反) |
✅ 实际应用:比如“拦截 DAO 层中第一个参数是 Long 的创建类方法”,用组合表达式简单粗暴就能实现。
5. 总结
Spring AOP 的切点表达式是强大而灵活的工具,掌握它能让你在不侵入业务代码的前提下,优雅地实现横切逻辑。
关键要点回顾:
- ✅
execution
最常用,配合通配符灵活匹配 - ✅
within
适合包/类级别批量拦截 - ✅
@annotation
+ 自定义注解,实现声明式编程 - ✅
target
比this
更通用,推荐优先使用 - ✅ 组合表达式提升匹配精度
📌 建议:在实际项目中,把常用切点抽象成工具类中的 @Pointcut
方法,统一管理,避免重复定义和出错。
示例代码中涉及的包名如
com.baeldung.pointcutadvice
可根据实际项目结构调整,邮箱地址 mock 为dev@example.com
。