切面 (Aspect) 是什么?它由什么组成?

在这里插入图片描述

什么是切面 (Aspect)?

一句话概括:

切面 (Aspect) 是一个模块,它封装了横跨应用程序中多个模块的通用功能(横切关注点)


详细解释:

假设你的应用程序有很多业务模块,比如用户管理、订单管理、商品管理等。现在,你想为这些模块的每个业务方法都添加一些通用功能,例如:

  • 日志记录:在方法开始执行前记录入参,在方法结束后记录出参。
  • 权限验证:在方法执行前检查当前用户是否有权限操作。
  • 事务管理:在方法开始前开启事务,在方法成功后提交,在发生异常时回滚。
  • 性能监控:记录每个方法的执行耗时。

这些功能(日志、权限、事务等)本身并不属于任何一个核心业务模块,但它们又像“切黄油”一样,横向地“切入”到了所有业务模块中。这些被横向切入的通用功能,我们就称之为 横切关注点 (Cross-Cutting Concerns)

切面 (Aspect),就是把这些“横切关注点”的代码逻辑(比如日志记录的代码、权限校验的代码)集中到一个地方进行定义和管理,而不是分散地写在每个业务方法里。

使用切面的好处:

  1. 高内聚,低耦合:业务代码只关心核心业务逻辑,通用功能代码(如日志)也只关心其自身逻辑。两者分离,互不干扰。
  2. 减少代码重复:只需写一次日志记录的逻辑,就可以应用到成百上千个方法上。
  3. 易于维护和扩展:想修改日志格式或增加新的通用功能(如缓存),只需要修改或增加一个切面类即可,完全不用动业务代码。

切面由什么组成?

一个完整的切面主要由两个核心部分组成:通知 (Advice)切点 (Pointcut)

切面 (Aspect) = 通知 (Advice) + 切点 (Pointcut)

1. 通知 (Advice)

通知定义了切面要做什么以及在什么时候做。它就是你希望在目标方法执行时插入的具体代码逻辑。

Spring AOP 提供了 5 种标准的通知类型:

  • 前置通知 (Before Advice): @Before
    • 时机:在目标方法执行之前执行。
    • 用途:通常用于参数校验、权限检查、日志记录请求参数等。
  • 后置通知 (After Advice): @After
    • 时机:在目标方法执行之后执行,无论方法是正常结束还是抛出异常
    • 用途:通常用于释放资源,类似于 try...catch...finally 中的 finally 块。
  • 返回通知 (After Returning Advice): @AfterReturning
    • 时机:在目标方法成功执行并返回结果之后执行。
    • 用途:可以获取方法的返回值并进行处理,例如记录成功的操作日志、修改返回值等。
  • 异常通知 (After Throwing Advice): @AfterThrowing
    • 时机:在目标方法执行过程中抛出异常之后执行。
    • 用途:用于处理异常,例如记录异常日志、发送告警通知等。
  • 环绕通知 (Around Advice): @Around
    • 时机:包裹在目标方法周围执行,是最强大的一种通知。
    • 用途:可以控制目标方法是否执行、修改入参、修改返回值。非常适合用于事务管理、性能监控等场景。
2. 切点 (Pointcut)

切点定义了在哪里做。它是一个表达式,用于精确匹配哪些类的哪些方法需要被切面“织入”通知。

可以把切点想象成一个查询语句,用来从海量的类和方法中(这些可能被织入的点被称为连接点 Join Point),筛选出我们真正关心的那一部分。

切点表达式遵循 AspectJ 的语法,最常用的是 execution() 指示符。

示例表达式:

execution(* com.example.service..*.*(..))

这个表达式的含义是:

  • *: 匹配任意的返回类型。
  • com.example.service..: 匹配 com.example.service 包及其所有子包。
  • *: 匹配包下所有的类。
  • .*: 匹配类中所有的方法。
  • (..): 匹配任意数量、任意类型的参数。

总结一下:切点通过表达式圈定了一个范围(比如“所有 Service 层的方法”),而通知则定义了要在这个范围上执行的具体操作(比如“在方法执行前打印日志”)。


示例

下面是一个记录方法执行日志的简单切面,展示了通知和切点的结合。

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

/**
 * 日志切面 (Aspect)
 * @Aspect 注解表明这是一个切面类
 * @Component 注解让 Spring 容器来管理它
 */
@Aspect
@Component
public class LoggingAspect {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    /**
     * 定义一个切点 (Pointcut)
     * 这个切点匹配 com.example.service 包下所有类的所有公共方法
     */
    @Pointcut("execution(public * com.example.service..*.*(..))")
    public void serviceLog() {}

    /**
     * 前置通知 (Before Advice)
     * 在 serviceLog() 切点匹配的方法执行前执行
     */
    @Before("serviceLog()")
    public void doBefore(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        logger.info("===> 方法 [{}] 开始执行,参数: {}", methodName, args);
    }

    /**
     * 后置通知 (After Advice)
     * 在 serviceLog() 切点匹配的方法执行后执行 (无论是否异常)
     */
    @After("serviceLog()")
    public void doAfter(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        logger.info("<=== 方法 [{}] 执行结束", methodName);
    }

    /**
     * 返回通知 (After Returning Advice)
     * 在方法成功返回后执行,可以获取返回值
     */
    @AfterReturning(pointcut = "serviceLog()", returning = "result")
    public void doAfterReturning(JoinPoint joinPoint, Object result) {
        String methodName = joinPoint.getSignature().getName();
        logger.info("<=== 方法 [{}] 成功返回,返回值: {}", methodName, result);
    }

    /**
     * 异常通知 (After Throwing Advice)
     * 在方法抛出异常后执行,可以获取异常信息
     */
    @AfterThrowing(pointcut = "serviceLog()", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, Exception e) {
        String methodName = joinPoint.getSignature().getName();
        logger.error("<=== 方法 [{}] 抛出异常,异常信息: {}", methodName, e.getMessage());
    }
}

在这个例子中:

  • @AspectLoggingAspect 类标识为一个切面。
  • @Pointcut 定义了一个名为 serviceLog() 的可重用切点。
  • @Before, @After, @AfterReturning, @AfterThrowing 都是通知,它们都引用了 serviceLog() 这个切点,从而将日志逻辑应用到了所有匹配的方法上。

这就是切面及其组成部分的完整解释。通过它,AOP 实现了对程序功能的优雅解耦和增强。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

冰糖心书房

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

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

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

打赏作者

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

抵扣说明:

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

余额充值