
你是否遇到过Spring AOP的execution表达式写对了却不生效?明明语法看起来没问题,却死活匹配不到目标方法?今天我们将深入AspectJ的源码,从Token解析到方法匹配,彻底厘清execution表达式的底层工作原理。
一、execution表达式的语法结构
execution表达式并非玄学,而是一套定义明确的规则,其完整语法结构如下:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)
- modifiers-pattern:可选,指定方法修饰符(如
public、protected)。
- ret-type-pattern:必选,指定返回值类型(使用
* 代表匹配任意类型)。
- declaring-type-pattern:可选,指定声明类型的全路径名(例如
com.xxx.* 表示 com.xxx 包下的所有类)。
- name-pattern:必选,指定方法名(支持通配符,如
add* 表示所有以 add 开头的方法)。
- param-pattern:必选,指定参数列表(
.. 代表匹配任意数量、任意类型的参数,(String) 代表匹配一个 String 类型的参数)。
- throws-pattern:可选,指定抛出的异常类型。
例如,表达式 execution(* com.xxx.service.*.add*(..)) 表示匹配 com.xxx.service 包下所有类中,方法名以 add 开头、接受任意参数、返回任意类型的方法。
二、源码解析:从字符串到匹配规则
当你定义好execution表达式后,Spring AOP 底层会通过 PointcutParser 将这个字符串转换为可执行的匹配规则。其核心流程为:字符串 → Token流 → AST抽象语法树 → 匹配器。
让我们看一下 PointcutParser 中的核心解析方法:
// org.aspectj.weaver.tools.PointcutParser
public PointcutExpression parsePointcutExpression(String expression) {
// 1. 将表达式字符串拆分为Token流(例如:execution, *, com.xxx.*, add*, (, .., ))
TokenStream tokens = new TokenStream(expression);
// 2. 解析Token流,生成AST抽象语法树
AST ast = parse(tokens);
// 3. 将AST封装为AspectJExpressionPointcut(核心匹配器)
return new AspectJExpressionPointcut(ast);
}
TokenStream 的作用是将表达式拆分为一个个最基本的“语法单元”。以表达式 execution(* com.xxx.*.add*(..)) 为例,它将被拆分为:[execution, (, *, com.xxx.*, ., add*, (, .., ), )]。每个Token都对应着表达式的一个组成部分,是后续语法树构建的基础。
三、关键转换:AST如何变为方法匹配器
AST(抽象语法树)以树形结构表示了表达式的逻辑关系(例如,根节点是 execution,子节点包括返回值模式、类名模式、方法名模式等)。但AST本身并不能直接用于方法匹配,它需要被转换成 MethodMatcher。
AspectJExpressionPointcut 是Spring AOP中处理表达式切面的核心类,其 getMethodMatcher() 方法负责生成最终的匹配器:
// org.springframework.aop.aspectj.AspectJExpressionPointcut
@Override
public MethodMatcher getMethodMatcher() {
// 生成AspectJMethodMatcher,它负责最终执行方法匹配逻辑
return new AspectJMethodMatcher(this);
}
而 AspectJMethodMatcher 中的 matches() 方法,则会严格遵循AST定义的规则,逐字段校验目标方法:
// org.springframework.aop.aspectj.AspectJMethodMatcher
@Override
public boolean matches(Method method, Class<?> targetClass) {
// 1. 校验返回值类型(ret-type-pattern)
if (!matchesReturningType(method)) return false;
// 2. 校验声明类路径(declaring-type-pattern)
if (!matchesDeclaringType(targetClass)) return false;
// 3. 校验方法名(name-pattern)
if (!matchesMethodName(method.getName())) return false;
// 4. 校验参数列表(param-pattern)
if (!matchesParameters(method.getParameterTypes())) return false;
return true;
}
这一步是表达式能否生效的核心。所有写在表达式中的语法规则,最终都会被翻译成这个匹配器中一个个具体的字段校验逻辑。理解这个匹配过程对于排查问题至关重要。
四、全流程概览:UML时序图
为了更直观地展示从表达式定义到方法匹配的完整闭环,我们绘制了以下UML时序图:

五、常见问题排查:为什么表达式不生效?
很多时候,表达式看似正确却未生效,问题往往出在没有完全符合底层校验规则上。以下是一些典型场景:
-
类路径模式书写错误:com.xxx.service 与 com.xxx.service.* 的区别
在源码的 declaring-type-pattern 校验中,com.xxx.service 只会尝试匹配名为 service 的类本身。而 com.xxx.service.* 才会匹配 com.xxx.service 包下的所有类。少一个 .*,匹配范围就天差地别。
-
参数模式误用:(String) 与 (..) 的区别
在 param-pattern 中,* 表示“一个任意类型的参数”,而 .. 表示“零个或多个任意类型的参数”。如果你的方法是 add(String, Integer),使用 (String) 或 (*) 都无法匹配,必须使用 (..) 或精确写明 (String, Integer)。
-
修饰符模式限制过严:
如果你在表达式中指定了修饰符,如 execution(public * com.xxx.*.add*(..)),但目标方法实际上是 protected 或 default 权限,那么 modifiers-pattern 的严格校验将导致匹配失败。
六、总结
本质上,execution表达式是一个规则翻译机。它的核心逻辑是:将开发者用特定语法描述的“匹配意图”,翻译成AspectJ能够理解和执行的、针对方法各个属性的“校验规则”,并逐一进行比对。其背后并非玄学,而是每一行都清晰可循的源码逻辑。