
核心定义与比喻
切点 (Pointcut) 是一个查询表达式,它的作用是定义“在哪里”应用通知(Advice)。
为了更好地理解,我们来使用一个经典的比喻:
- 连接点 (Join Point):你的程序中所有可以被拦截的“点”,就像一条河里所有的鱼。这包括方法的调用、方法的执行、字段的设置等。在Spring AOP中,连接点特指方法的执行。
- 通知 (Advice):你想要执行的“动作”,比如日志记录、权限检查。这就像你要对鱼做的操作(比如称重、拍照、放生)。
- 切点 (Pointcut):一个“筛选条件”或一个“渔网”,它精确地定义了你想要捕捞哪些鱼。你可能不想要河里所有的鱼,你可能只想捞“红色的”、“超过5公斤的”或者“在某个特定区域的”鱼。
- 切面 (Aspect):是“通知”和“切点”的结合体,它完整地描述了“在何处(Pointcut),做什么(Advice)”。这就像是整个捕鱼计划(用什么样的网,在哪个位置,捕到鱼后要做什么)。
所以,切点的核心作用就是:从成千上万个连接点(所有方法)中,精确地筛选出你感兴趣的目标方法集合。
切点的作用是什么?
-
精确的目标定位 (Precision Targeting)
- 没有切点,你的通知(比如日志)就会应用到系统中的每一个方法上,这显然是不可接受的。
- 切点允许你通过包名、类名、方法名、参数、注解等多种维度,像使用SQL查询数据一样,精确地定位到你想要增强的一个或多个方法。
-
解耦 (Decoupling)
- 切点是连接“业务逻辑”和“横切关注点”(如日志、安全)的桥梁,但它让这两者保持了松耦合。
- 业务代码(
UserService,OrderService)完全不知道自己被AOP增强了,它只管做自己的事。 - 切面(
LoggingAspect)也不需要硬编码去调用UserService。它只是通过切点表达式声明:“任何符合这个规则的方法,我都要管。” - 这种解耦使得系统更容易维护和扩展。
-
重用性 (Reusability)
- 你可以定义一个命名的切点,然后让多个不同的通知(
@Before,@After,@Around等)都来引用它。这避免了在每个通知上都重复写一遍冗长的表达式,符合DRY(Don’t Repeat Yourself)原则。
- 你可以定义一个命名的切点,然后让多个不同的通知(
如何定义切点?(切点表达式)
Spring AOP使用AspectJ的切点表达式语言来定义切点。最常用的是 execution 指示符。
execution 表达式语法
execution 表达式的结构最为复杂,但也最为强大和常用。
execution( [修饰符模式]? 返回类型模式 [声明类型模式]? 方法名模式(参数模式) [抛出异常模式]? )
- 方括号
[]内的部分是可选的。
让我们通过一个具体的例子来分解它:
@Pointcut("execution(public * com.example.service.impl.*.find*(..))")
这个表达式的含义是:拦截com.example.service.impl包下,所有类的,以find开头的,任意返回类型的,public修饰的,并且拥有任意数量参数的方法。
public: 匹配public修饰符的方法。*: 匹配任意返回类型。com.example.service.impl.*: 匹配com.example.service.impl包下的任意类。.find*: 匹配以find开头的任意方法。(..): 匹配任意数量、任意类型的参数。
常用通配符
*(星号): 匹配任意数量的字符(除了包分隔符.)。可以用于返回类型、包名的一部分、类名、方法名。..(两个点):- 在包名中,表示当前包及其所有子包。例如
com.example..*匹配com.example包和所有子包下的类。 - 在参数列表中,表示任意数量、任意类型的参数。例如
(..)。
- 在包名中,表示当前包及其所有子包。例如
其他常用的指示符
除了 execution,还有一些其他非常有用的指示符:
| 指示符 | 描述 | 示例 |
|---|---|---|
within | 限制连接点在指定的类型或包内。它只关心位置,不关心方法签名。 | within(com.example.service.*) (service包下的所有方法) |
@annotation | 匹配持有指定注解的方法。这对于创建自定义注解来实现AOP非常有用。 | @annotation(com.example.aop.MyLoggable) (所有被@MyLoggable注解的方法) |
@within | 匹配持有指定注解的类型内的所有方法。 | @within(org.springframework.stereotype.Service) (所有被@Service注解的类中的方法) |
args | 限制连接点匹配那些参数类型为指定类型的方法。 | args(Long, String) (匹配第一个参数是Long,第二个是String的方法) |
@args | 限制连接点匹配那些参数的运行时类型持有指定注解的方法。 | @args(com.example.validation.Validatable, ..) (第一个参数对象被@Validatable注解) |
组合切点
你可以使用 && (与), || (或), ! (非) 来组合多个切点表达式,创建更复杂的规则。
@Pointcut("within(com.example.service..*)")
private void inServiceLayer() {}
@Pointcut("@annotation(com.example.aop.AdminOnly)")
private void isAdminMethod() {}
// 组合:在 service 层中,并且被 @AdminOnly 注解的方法
@Before("inServiceLayer() && isAdminMethod()")
public void checkAdminAccess() {
// ... 权限检查逻辑
}
完整代码示例
这个例子展示了如何定义一个可重用的切点,并被 @Before 通知使用。
1. 业务代码 (目标对象)
package com.example.service;
import org.springframework.stereotype.Service;
@Service
public class UserService {
public String getUser(Long id) {
System.out.println("核心业务:查询用户 " + id);
return "User-" + id;
}
public void updateUser(Long id, String name) {
System.out.println("核心业务:更新用户 " + id);
}
}
2. 切面 (包含切点和通知)
package com.example.aop;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class LoggingAspect {
/**
* 1. 定义一个切点 (Define a Pointcut)
* 这个切点被命名为 "serviceMethods",它匹配 com.example.service 包下所有类的所有公共方法。
* 这个方法本身没有方法体,它只是一个标识符,用于承载 @Pointcut 注解。
*/
@Pointcut("execution(public * com.example.service.*.*(..))")
public void serviceMethods() {}
/**
* 2. 创建一个通知,并引用上面定义的切点 (Create an Advice referencing the Pointcut)
* 通过方法名 "serviceMethods()" 来引用切点。
* 这比在 @Before 中写一长串 execution 表达式要清晰且可重用。
*/
@Before("serviceMethods()")
public void logBefore() {
System.out.println("[AOP-Logger]: A service method is about to be executed.");
}
// 你可以轻松地重用这个切点
// @After("serviceMethods()")
// public void logAfter() { ... }
}
当你调用 userService.getUser(1L) 时,输出会是:
[AOP-Logger]: A service method is about to be executed.
核心业务:查询用户 1
这证明了 logBefore 这个通知(Advice),通过 serviceMethods 这个切点(Pointcut),成功地应用到了 getUser 这个连接点(Join Point) 上。
779

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



