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 { @MyLoggable public 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 来实现自定义的声明式功能,因为它能让我们的代码意图更加清晰。
1221

被折叠的 条评论
为什么被折叠?



