Pointcut 表达式是 AOP 的核心,我将详细解析最常用的 execution
表达式,并介绍其他几种同样非常有用的表达式。
1. execution
指示符 (最常用,最强大)
execution
用于匹配方法的执行(Join Point)。它的语法结构最为完整,也最为灵活。
语法结构
execution( [修饰符模式]? 返回类型模式 [声明类型模式]? 方法名模式(参数模式) [抛出异常模式]? )
- 方括号
[]
内的部分是可选的。 ?
表示该部分是可选的。
语法分解与示例
让我们通过一个具体的例子来逐一分解:
execution(public * com.example.service..*.set*(..))
部分 | 示例 | 含义 |
---|---|---|
[修饰符模式] | public | 匹配 public 修饰的方法。可以省略,表示匹配任何修饰符。也可以用 * 。 |
返回类型模式 | * | 匹配任意返回类型。也可以是具体的类型如 String 、void 。 |
[声明类型模式] | com.example.service..*. | 匹配方法所在的类型。这部分非常灵活: - com.example.service.UserService :精确匹配 UserService 类。- com.example.service.* :匹配 service 包下的任意直接类。- com.example.service..* :匹配 service 包及其所有子包下的任意类。最后的 . 是必需的。 |
方法名模式 | set* | 匹配以 set 开头的方法名。也可以是精确的方法名 setName 或通配符 * 。 |
(参数模式) | (..) | 匹配任意数量、任意类型的参数。这是最常用的模式。 - () :匹配无参方法。- (*) :匹配只有一个任意类型参数的方法。- (String, ..) :匹配第一个参数是 String ,后面有任意数量、任意类型的参数。 |
[抛出异常模式] | (省略) | 匹配方法声明抛出的异常类型。例如 throws java.io.IOException 。这部分很少使用。 |
这个例子的完整含义是:
拦截 com.example.service
包及其所有子包中,所有类的,所有以 set
开头的,public
修饰的,不限返回值和参数的方法。
2. 其他常用 Pointcut 指示符
除了 execution
,还有一些在特定场景下非常好用的指示符。
within
指示符
within
用于限定连接点必须在指定的类型或包的范围内。它比 execution
更粗粒度,只关心位置,不关心方法签名。
-
语法:
within(类型或包模式)
-
示例:
within(com.example.service.UserService)
: 匹配UserService
类中的所有方法。within(com.example.service.*)
: 匹配service
包下所有类中的所有方法。within(com.example.service..*)
: 匹配service
包及其所有子包下所有类中的所有方法。
-
与
execution
的区别:execution(* com.example.service.UserService.*(..))
和within(com.example.service.UserService)
看起来相似,但within
的粒度更大。例如,within
不关心方法的修饰符、返回类型等。
@annotation
指示符
@annotation
用于匹配持有指定注解的方法。这对于创建自定义注解来实现 AOP 非常有用,是实现声明式功能(如声明式日志、声明式缓存)的最佳方式。
-
语法:
@annotation(注解类型的完全限定名)
-
示例:
- 定义一个自定义注解:
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface MyLoggable {}
- 在方法上使用注解:
@Service public class MyService {@MyLoggablepublic void doSomething() { /* ... */ } }
- 编写切点:
这个切点会精确地匹配所有被@Pointcut("@annotation(com.example.aop.MyLoggable)") public void loggableMethods() {}
@MyLoggable
注解的方法。
- 定义一个自定义注解:
@within
和 @target
-
@within
: 匹配持有指定注解的类中的所有方法。- 示例:
@within(org.springframework.stereotype.Service)
会匹配所有被@Service
注解的类中的所有方法。
- 示例:
-
@target
: 匹配目标对象的类持有指定注解的所有方法。@within
和@target
在大多数情况下效果相同。
args
和 @args
这两个指示符用于根据方法的参数来匹配。
-
args()
: 匹配方法的参数类型。args(java.lang.String, ..)
: 匹配第一个参数是String
类型的方法。args()
: 匹配无参方法。args(user)
: 这种形式不仅匹配参数类型,还能在通知中绑定参数值。@Before("execution(* com.example..*.*(com.example.User)) && args(user)") public void processUser(User user) {//可以直接使用 user 对象System.out.println("Processing user: " + user.getName()); }
-
@args()
: 匹配方法的参数在运行时所持有的注解。@args(com.example.validation.Validatable, ..)
: 匹配第一个参数对象被@Validatable
注解标注的方法。
3. 组合切点 (Combining Pointcuts)
你可以使用逻辑运算符 &&
(与), ||
(或), !
(非) 来组合多个切点,创建更复杂的规则。
@Aspect
@Component
public class SecurityAspect {// 匹配 service 包下的所有方法@Pointcut("within(com.example.service..*)")public void inServiceLayer() {}// 匹配被 @AdminOnly 注解的方法@Pointcut("@annotation(com.example.security.AdminOnly)")public void adminOnlyMethods() {}// 组合切点:在 service 层中,并且是 admin-only 的方法@Before("inServiceLayer() && adminOnlyMethods()")public void checkAdminAccess() {// ... 执行权限检查逻辑System.out.println("Admin access granted!");}// 组合切点:不在 service 层中的所有方法@Before("!inServiceLayer()")public void nonServiceMethodLog() {// ...}
}
总结
指示符 | 作用 | 优点 |
---|---|---|
execution | 最核心、最常用,匹配方法的完整签名。 | 最精确,控制粒度最细。 |
within | 匹配指定包或类中的所有方法。 | 语法简单,适合按模块划分。 |
@annotation | 匹配持有指定注解的方法。 | 解耦性最好,通过注解驱动AOP,业务代码无侵入。 |
@within , @target | 匹配持有指定注解的类中的所有方法。 | 适合对整个类别的Bean进行增强。 |
args , @args | 根据方法的参数类型或参数注解来匹配。 | 适合处理特定类型的数据流。 |
**&& , ` | , !`** |
在实际开发中,execution
和 @annotation
是使用频率最高的两种方式。建议优先使用 @annotation
来实现自定义的声明式功能,因为它能让我们的代码意图更加清晰。