Skip to content

AOP 核心概念:切点、切面、通知、连接点

你有没有遇到过这样的场景:需要在所有 Service 方法前后加上日志,或者需要在所有 Controller 方法执行前检查权限?

如果你选择直接在每个方法里写,你会发现代码变成了这样:

java
public class UserServiceImpl {
    public void addUser(User user) {
        log.info("开始添加用户");
        // 权限校验
        checkPermission();
        // 业务逻辑
        // ...
        log.info("添加用户完成");
    }

    public void deleteUser(Long id) {
        log.info("开始删除用户");
        checkPermission();
        // 业务逻辑
        // ...
        log.info("删除用户完成");
    }
}

一个简单的业务方法,被日志和权限校验包围了四圈。这就是所谓的「横切关注点」——它横切了所有的业务方法,却又不是业务逻辑本身。

AOP(面向切面编程)就是来解决这个问题的。

四大核心概念

AOP 的核心就是四个概念:连接点(Join Point)、切点(Pointcut)、通知(Advice)、切面(Aspect)。

1. 连接点(Join Point)

「在哪里执行?」

连接点是指程序执行的某个位置,可能被拦截的地方

在 Spring AOP 中,连接点始终是方法执行。一个类里有多少个方法,理论上就有多少个连接点。

java
public class OrderService {
    public void createOrder() {}  // 这是一个连接点
    public void cancelOrder(Long id) {}  // 这也是一个连接点
    public Order getOrder(Long id) {}  // 这还是一个连接点
}

2. 切点(Pointcut)

「具体拦截哪一个?」

切点是连接点的定义,精确指定哪些连接点需要被拦截

如果说连接点是「所有方法」,那切点就是「所有方法中,符合某个条件的那些」。

java
// 切点表达式:拦截 UserService 中所有以 find 开头的方法
@Pointcut("execution(* com.example.UserService.find*(..))")
public void pointcutForFindMethods() {}

常见的切点表达式:

表达式含义
execution(* com.example.UserService.*(..))UserService 的所有方法
execution(* com.example..*.save*(..))com.example 包下所有以 save 开头的方法
within(com.example.service.*)service 包下的所有类
annotation(@Transactional)所有标注了 @Transactional 的方法
bean(userService)名为 userService 的 Bean
bean(*Service)所有以 Service 结尾的 Bean

3. 通知(Advice)

「拦截后做什么?」

通知定义了拦截到连接点后要执行的逻辑

java
@Aspect
@Component
public class LoggingAspect {

    // 方法执行前执行
    @Before("execution(* com.example..*.save*(..))")
    public void beforeSave(JoinPoint joinPoint) {
        log.info("准备保存数据: {}", joinPoint.getSignature());
    }

    // 方法执行后执行(无论成功还是异常都执行)
    @After("execution(* com.example..*.save*(..))")
    public void afterSave(JoinPoint joinPoint) {
        log.info("保存操作完成: {}", joinPoint.getSignature());
    }

    // 方法成功返回后执行
    @AfterReturning(pointcut = "execution(* com.example..*.save*(..))", returning = "result")
    public void afterReturningSave(JoinPoint joinPoint, Object result) {
        log.info("保存成功,返回值: {}", result);
    }

    // 方法抛出异常后执行
    @AfterThrowing(pointcut = "execution(* com.example..*.save*(..))", throwing = "ex")
    public void afterThrowingSave(JoinPoint joinPoint, Exception ex) {
        log.error("保存失败: {}", ex.getMessage());
    }

    // 环绕通知:可以控制方法何时开始、何时结束
    @Around("execution(* com.example..*.save*(..))")
    public Object aroundSave(ProceedingJoinPoint pjp) throws Throwable {
        log.info("环绕通知 - 开始");
        long start = System.currentTimeMillis();

        // 执行目标方法
        Object result = pjp.proceed();

        long end = System.currentTimeMillis();
        log.info("环绕通知 - 结束,耗时: {} ms", end - start);
        return result;
    }
}

五种通知的执行时机:

                    ┌─────────────────────────────────────┐
                    │            目标方法执行              │
                    │                                     │
   @Before ───────► │                                     │
                    │                                     │
   ┌─────────────────┼─────────────────────────────────────┼─────────────────┐
   │                 │                                     │                 │
   │   @Around       │           目标方法体                 │   @Around       │
   │   (前置部分)    │                                     │   (后置部分)    │
   │                 └─────────────────────────────────────┤                 │
   │                                                          │                 │
   │                                                          ▼                 │
   │                                                    @AfterReturning        │
   │                                                          │                 │
   │                                                          │ (或)            │
   │                                                          ▼                 │
   │                                                    @AfterThrowing         │
   │                 ┌─────────────────────────────────────┤                 │
   │                 │                                     │                 │
   └─────────────────►│            @After                  │◄────────────────┘
                     │          (finally)                  │
                     └─────────────────────────────────────┘

4. 切面(Aspect)

「谁来组织?」

切面是通知和切点的组合,定义了拦截什么(切点)+ 拦截后做什么(通知)

java
@Aspect  // 声明这是一个切面
@Component
public class PerformanceAspect {

    // 切点:拦截所有 Service 层的方法
    @Pointcut("execution(* com.example..service..*(..))")
    public void serviceLayer() {}

    // 通知:性能监控
    @Around("serviceLayer()")
    public Object monitorPerformance(ProceedingJoinPoint pjp) throws Throwable {
        long start = System.currentTimeMillis();
        Object result = pjp.proceed();
        long cost = System.currentTimeMillis() - start;

        if (cost > 1000) {
            log.warn("方法执行超过 1 秒: {},耗时: {} ms",
                    pjp.getSignature(), cost);
        }
        return result;
    }
}

概念关系图

┌─────────────────────────────────────────────────────────────────┐
│                         AOP 核心概念关系                          │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  ┌───────────┐                                                   │
│  │   切面     │  ← 组织了「切点 + 通知」                          │
│  │  (Aspect) │                                                   │
│  └─────┬─────┘                                                   │
│        │                                                         │
│        ├──────────────────┬──────────────────┐                   │
│        ▼                  ▼                  ▼                   │
│  ┌───────────┐    ┌───────────┐    ┌───────────┐                │
│  │   切点     │    │   通知    │    │  连接点    │                │
│  │ (Pointcut)│    │  (Advice) │    │(JoinPoint)│                │
│  └───────────┘    └───────────┘    └───────────┘                │
│        │                  │                                     │
│   「拦截哪些」         「拦截后做什么」                            │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

实际应用示例

日志切面

java
@Aspect
@Component
@Order(1)  // 多个切面时的执行顺序,数字越小优先级越高
public class LoggingAspect {

    @Pointcut("execution(* com.example..service..*(..))")
    public void serviceMethods() {}

    @Before("serviceMethods()")
    public void logBefore(JoinPoint jp) {
        log.info(">>> 调用方法: {}.{}", 
                jp.getTarget().getClass().getSimpleName(),
                jp.getSignature().getName());
    }

    @AfterReturning(pointcut = "serviceMethods()", returning = "result")
    public void logAfterReturning(JoinPoint jp, Object result) {
        log.info("<<< 方法返回: {}.{} = {}",
                jp.getTarget().getClass().getSimpleName(),
                jp.getSignature().getName(),
                result);
    }
}

权限校验切面

java
@Aspect
@Component
public class PermissionAspect {

    @Pointcut("@annotation(RequirePermission)")
    public void permissionRequired() {}

    @Around("permissionRequired()")
    public Object checkPermission(ProceedingJoinPoint pjp) throws Throwable {
        // 获取方法上的注解
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        Method method = signature.getMethod();
        RequirePermission annotation = method.getAnnotation(RequirePermission.class);

        // 检查权限
        String[] requiredPermissions = annotation.value();
        if (!hasPermission(requiredPermissions)) {
            throw new SecurityException("没有权限访问此方法");
        }

        return pjp.proceed();
    }
}

面试核心问题

Q1:AOP 的四大核心概念是什么?

概念作用类比
Join Point连接点,程序执行的位置公路上的所有路口
Pointcut切点,具体拦截哪些连接点指定要收费的路口
Advice通知,拦截后执行的逻辑收费的动作
Aspect切面,切点 + 通知的组合收费站的完整设计

Q2:Spring AOP 和 AspectJ 的区别?

特性Spring AOPAspectJ
织入时机运行时代理编译时/加载时织入
连接点仅方法执行字段、构造函数、静态块等
切点表达式部分支持完全支持
性能有额外开销无额外开销
配置简单复杂

Q3:@Around 和其他通知的区别?

@Around 可以完全控制目标方法的执行——可以决定是否调用 proceed(),可以修改参数,可以修改返回值。其他通知(@Before、@After 等)只是被动地被调用。


下节预告JDK 动态代理 vs CGLIB 动态代理 —— 深入理解 Spring AOP 底层两种代理技术的原理和区别。

基于 VitePress 构建