切点是切面的「瞄准镜」
@Pointcut("execution(* com.example.service..*.*(..))"),是整个切面的选点逻辑------表达式写宽了误伤无辜方法,写窄了漏拦。这一篇把瞄准镜校准。
一、@Pointcut 签名约定(30 秒复习)
java
@Pointcut("execution(* transfer(..))") // 切点表达式
private void anyOldTransfer() {} // 切点签名
官方约定:签名方法必须 void 返回、空方法体 ------它不执行任何逻辑,只是给表达式起名字,供多个通知按名引用(@Before("anyOldTransfer()"))。可见性遵循 Java 规则但不影响匹配:private 切点在同一个切面里用,public 切点可以跨切面共享。
二、execution 六段式解剖(高频 90% 的场景)
text
execution(modifiers-pattern? ret-type-pattern
declaring-type-pattern?name-pattern(param-pattern)
throws-pattern?)
| 段 | 是否必填 | 说明 |
|---|---|---|
| modifiers | 可省 | public / protected 等 |
| ret-type | 必填 | 返回类型,* 通配任意 |
| declaring-type | 可省 | 声明方法的类型(含包名) |
| name-pattern | 必填 | 方法名,* 通配,set* 匹配前缀 |
| param-pattern | 必填 | 见下方通配符表 |
| throws | 可省 | 异常类型 |
参数模式是易错点,官方给的就这四种:
()------无参方法(..)------任意个参数(最常用)(*)------恰好一个任意类型参数(*,String)------两个参数,第二个是 String
四个官方示例,覆盖日常 90% 场景:
java
execution(public * *(..)) // 任何 public 方法
execution(* set*(..)) // 所有 set 开头的方法
execution(* com.xyz.service.AccountService.*(..)) // AccountService 接口上的所有方法
execution(* com.xyz.service..*.*(..)) // service 包及子包的所有方法
包名里的 .. 是「含子包」,参数里的 .. 是「任意参数」------两个点号含义不同,别混。
三、全部 10 个切点指示符(PCD)快览
execution 之外还有 9 个,按用途分三组记:
① 按类型圈范围
within(com.xyz.service..*):该类型内声明的所有连接点(Spring AOP 即方法执行)this(AccountService):代理对象是该类型的实例------匹配的是「代理实现/继承了什么」target(AccountService):目标对象是该类型的实例------匹配的是「被代理的真实对象」
② 按注解圈范围(实战主力)
@annotation(org.springframework.transaction.annotation.Transactional):方法上标了该注解------自定义切面最常用@within(SomeAnnotation):声明类型上标了该注解(类级)@target(SomeAnnotation):目标对象的运行时类标了该注解@args(Classified):传入实参的运行时类型标了该注解
③ 按参数与 Spring 扩展
args(java.io.Serializable):单个运行时实参是 Serializable 实例bean(tradeService)/bean(*Service):按 Spring bean 名字 匹配(支持*通配)------这是 Spring AOP 特有扩展,AspectJ 里没有
this vs target 是面试常客 :this 看代理(代理实现了哪个接口/继承了哪个类),target 看目标(真实对象是谁)。JDK 动态代理下,代理只实现接口、并不是目标类的子类------所以 this(SomeServiceImpl) 在 JDK 代理下不命中,target(SomeServiceImpl) 命中。弄清代理机制后再回头看会更透。
四、组合与复用:&&、||、! 和命名切点
java
public class Pointcuts {
@Pointcut("execution(public * *(..))")
public void publicMethod() {} // 任何 public 方法
@Pointcut("within(com.xyz.trading..*)")
public void inTrading() {} // trading 模块内
@Pointcut("publicMethod() && inTrading()")
public void tradingOperation() {} // 组合:trading 模块内的 public 方法
}
三个逻辑运算符 &&(与)、||(或)、!(非)自由组合,切点可以按名字互相引用------工程实践:把通用切点集中到一个切点类里,通知按名引用,改切点不用翻遍所有通知。
看图就懂:execution 解剖 + PCD 家族全景

一个官方级避坑:声明类型 vs 运行时类型
execution(* *(java.io.Serializable)) 和 args(java.io.Serializable) 长得像,语义完全不同 ------前者按方法签名里声明的参数类型 匹配,后者按调用时传入实参的运行时类型匹配。传一个 String 给声明为 Object 的参数,前者不命中、后者命中(String 实现了 Serializable)。排查「切点怎么没拦住」时,先分清你写的是哪种。
一句话收束:execution 定主战场,@annotation 定业务开关,within/bean 定模块边界,this/target 分代理与目标------四把尺子量清楚,切点就不失控。
📌 版本与出处说明 :本文基于 Spring Framework 7.x 官方 Reference (查阅时文档版本 7.0.10,2026-10)撰写。全部切点指示符与示例对照
Core → AOP → @AspectJ Support → Declaring a Pointcut一章,语法与措辞均出自该章。