Spring Aop 切点 (Pointcut) 是什么?它的作用是什么?

在这里插入图片描述

核心定义与比喻

切点 (Pointcut) 是一个查询表达式,它的作用是定义“在哪里”应用通知(Advice)

为了更好地理解,我们来使用一个经典的比喻:

  • 连接点 (Join Point):你的程序中所有可以被拦截的“点”,就像一条河里所有的鱼。这包括方法的调用、方法的执行、字段的设置等。在Spring AOP中,连接点特指方法的执行
  • 通知 (Advice):你想要执行的“动作”,比如日志记录、权限检查。这就像你要对鱼做的操作(比如称重、拍照、放生)。
  • 切点 (Pointcut):一个“筛选条件”或一个“渔网”,它精确地定义了你想要捕捞哪些鱼。你可能不想要河里所有的鱼,你可能只想捞“红色的”、“超过5公斤的”或者“在某个特定区域的”鱼。
  • 切面 (Aspect):是“通知”和“切点”的结合体,它完整地描述了“在何处(Pointcut),做什么(Advice)”。这就像是整个捕鱼计划(用什么样的网,在哪个位置,捕到鱼后要做什么)。

所以,切点的核心作用就是:从成千上万个连接点(所有方法)中,精确地筛选出你感兴趣的目标方法集合。


切点的作用是什么?

  1. 精确的目标定位 (Precision Targeting)

    • 没有切点,你的通知(比如日志)就会应用到系统中的每一个方法上,这显然是不可接受的。
    • 切点允许你通过包名、类名、方法名、参数、注解等多种维度,像使用SQL查询数据一样,精确地定位到你想要增强的一个或多个方法。
  2. 解耦 (Decoupling)

    • 切点是连接“业务逻辑”和“横切关注点”(如日志、安全)的桥梁,但它让这两者保持了松耦合。
    • 业务代码(UserService, OrderService)完全不知道自己被AOP增强了,它只管做自己的事。
    • 切面(LoggingAspect)也不需要硬编码去调用 UserService。它只是通过切点表达式声明:“任何符合这个规则的方法,我都要管。”
    • 这种解耦使得系统更容易维护和扩展。
  3. 重用性 (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) 上。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

冰糖心书房

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值