找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

1563

积分

0

好友

231

主题
发表于 7 天前 | 查看: 21| 回复: 0

Spring AOP execution表达式解析流程

你是否遇到过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:可选,指定方法修饰符(如 publicprotected)。
  • 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时序图:

Spring AOP execution表达式匹配UML时序图

五、常见问题排查:为什么表达式不生效?

很多时候,表达式看似正确却未生效,问题往往出在没有完全符合底层校验规则上。以下是一些典型场景:

  1. 类路径模式书写错误com.xxx.servicecom.xxx.service.* 的区别
    在源码的 declaring-type-pattern 校验中,com.xxx.service 只会尝试匹配名为 service 的类本身。而 com.xxx.service.* 才会匹配 com.xxx.service 包下的所有类。少一个 .*,匹配范围就天差地别。

  2. 参数模式误用(String)(..) 的区别
    param-pattern 中,* 表示“一个任意类型的参数”,而 .. 表示“零个或多个任意类型的参数”。如果你的方法是 add(String, Integer),使用 (String)(*) 都无法匹配,必须使用 (..) 或精确写明 (String, Integer)

  3. 修饰符模式限制过严
    如果你在表达式中指定了修饰符,如 execution(public * com.xxx.*.add*(..)),但目标方法实际上是 protecteddefault 权限,那么 modifiers-pattern 的严格校验将导致匹配失败。

六、总结

本质上,execution表达式是一个规则翻译机。它的核心逻辑是:将开发者用特定语法描述的“匹配意图”,翻译成AspectJ能够理解和执行的、针对方法各个属性的“校验规则”,并逐一进行比对。其背后并非玄学,而是每一行都清晰可循的源码逻辑。




上一篇:AI生成图书概要网页:从内容加工到视觉呈现的实践解析
下一篇:Python一行式脚本实战:Linux系统监控与文件管理运维指南
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2025-12-25 01:10 , Processed in 0.218483 second(s), 39 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表