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..*)")

⚠️ 注意:..* 表示当前包及所有子包,别漏掉 *

✅ 对比 executionwithin 更简洁,适合“全包拦截”类需求,但粒度较粗。


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 + 自定义注解,实现声明式编程
  • targetthis 更通用,推荐优先使用
  • ✅ 组合表达式提升匹配精度

📌 建议:在实际项目中,把常用切点抽象成工具类中的 @Pointcut 方法,统一管理,避免重复定义和出错。

示例代码中涉及的包名如 com.baeldung.pointcutadvice 可根据实际项目结构调整,邮箱地址 mock 为 dev@example.com


原始标题:Introduction to Pointcut Expressions in Spring | Baeldung