2024-12-18  阅读(41)
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 本文链接:https://www.skjava.com/mianshi/baodian/detail/2000937405

回答

AOP 是 Spring 中两大核心之一,它主要是用于解决代码中的横切关注点问题。简单来说就是,AOP 可以让我们将那些在多个模块中都可能会重复出现的功能,比如日志、事务管理、安全检查等功能,抽取出来统一管理,从而避免重复代码,减少耦合性。

AOP 是通过“切面”(Aspect)来实现的,“切面”就像一个切割点,它可以在不改变核心业务逻辑的情况下,将某些功能插入到目标方法的执行前、执行后或者抛出异常时等地方。

举个例子,比如我们有一个方法 saveOrder(),我们希望在执行该方法前后打印日志,而不是直接在 saveOrder() 里写日志逻辑。那么我们就可以通过 AOP 来定义一个 “切面”,让日志功能在执行 saveOrder() 方法之前和之后被自动调用。这就是AOP带来的好处,我们不需要修改 saveOrder() 的核心业务逻辑,就能实现额外的功能。

AOP 通常是通过动态代理实现的,有两种实现方式:基于 JDK 的动态代理和 CGLIB 动态代理。如果目标类实现了接口,AOP 默认会使用 JDK 动态代理;否则,会使用 CGLIB。

详解

AOP 的核心概念

要理解 AOP,就必须理解 AOP 的几个核心概念:

  • Aspect(切面)

切面代表了横切关注点的模块化,它由多个通知(Advice)和切入点(Pointcut)组成,我们可以这样理解:一个切面就是一段横切关注点的逻辑,它会被应用到目标对象的某些地方。

  • Join Point(连接点)

连接点是指程序执行的某一个具体位置,比如方法调用、异常抛出等。在 Spring AOP 中,连接点通常是指方法的执行点,因为 Spring AOP 只支持方法级别的连接点。

  • Advice(通知)

通知是定义在切面中的代码,它描述了切面在什么时候执行。Spring AOP 中,通知类型由多种:

*- 前置通知(Before Advice):在目标方法执行之前执行。

*- 后置通知(After Advice):在目标方法执行之后执行,无论是否抛出异常。

*- 返回通知(After Returning Advice):在目标方法成功返回之后执行。

*- 异常通知(After Throwing Advice):在目标方法抛出异常之后执行。

*- 环绕通知(Around Advice):在目标方法执行之前和执行之后都能执行,可以控制目标方法是否执行。

  • Pointcut(切入点)

切入点用于定义“切面”应该应用到哪些连接点。它通常使用表达式来匹配一组方法或者类,比如通过方法名、参数等来进行匹配。切入点决定了哪些方法会被拦截并应用切面。

  • Target Object(目标对象)

目标对象是指那些被切面功能增强的类。在 Spring AOP 中,目标对象可以是任何一个被代理的 Bean。

  • Weaving(织入)

织入是指将切面代码应用到目标对象的过程。这个过程可以在编译时、类加载时或者运行时进行。Spring AOP 是基于运行时的动态代理实现的。

举个例子吧!

@Aspect
@Component
public class TransferAspect {

    // 定义切点
    @Pointcut("execution(* TransferService.transfer(..))")
    public void transferMethod() {}

    // 前置通知:在转账前记录日志
    @Before("transferMethod()")
    public void beforeTransfer(JoinPoint joinPoint) {
        System.out.println("前置通知: " + joinPoint.getSignature().getName());
    }

    // 后置通知:在转账成功后记录日志
    @AfterReturning("transferMethod()")
    public void afterSuccessfulTransfer(JoinPoint joinPoint) {
        System.out.println("后置通知: " + joinPoint.getSignature().getName());
    }

    // 异常通知:在转账失败时处理异常
    @AfterThrowing(pointcut = "transferMethod()", throwing = "ex")
    public void handleTransferException(JoinPoint joinPoint, Exception ex) {
        System.out.println("异常通知: " + ex.getMessage());
        // 发送警告邮件的逻辑
    }
}

在这个类里面,包含了如下几个概念:

  • 切面TransferAspect ,这里它负责处理转账的日志记录和异常处理。
  • 切入点@Pointcut("execution(* TransferService.transfer(..))"),它指定了 TransferService 类中的 transfer() 方法为目标点。
  • 通知@Before("transferMethod()"),这个是前置通知,下面还有两个 后置返回通知(@AfterReturning)和 异常通知(@AfterThrowing
public class TransferService {

    public void transfer(String fromAccount, String toAccount, double amount) {
        // 转账逻辑
        System.out.println("Transferring " + amount + " from " + fromAccount + " to " + toAccount);
        
        // 模拟异常
        if (amount > 10000) {
            throw new RuntimeException("Transfer amount exceeds limit!");
        }
    }
}

这里包含的概念有:

  • 目标对象TransferService
  • 连接点:当方法执行到 transfer() 时,就会触发一个连接点,在这个连接点上,我们可以通过切点和通知来实现增强。

AOP 的实现原理

AOP 的实现原理是基于代理模式,它通过动态代理在运行时为目标对象生成代理对象,代理对象则负责在适当的时机执行增强逻辑,并调用目标对象的方法。在 Spring AOP 中,有两种主要的代理实现方式:

  • JDK 动态代理

JDK 动态代理是一种基于 Java 反射机制的代理方式,适用于为实现了接口的类创建代理对象。其实现原理是 Spring AOP 通过 JDK 提供的 java.lang.reflect.Proxy 类,动态地创建一个实现了相同接口的代理对象。代理对象通过实现 InvocationHandler 接口的 invoke 方法来控制对目标方法的调用。每次调用目标对象的方法时,都会先调用代理对象的 invoke 方法,这使得 AOP 可以在调用目标方法之前或之后插入通知逻辑。

  • CGLIB 动态代理(适用于没有实现接口的类)

CGLIB 是一种基于字节码生成的代理方式,适用于那些没有实现接口的类,它通过创建目标类的子类来实现代理,所以它不能代理final 类或 final 方法。

关于 JDK 动态代理和 CGLIB 动态代理,请阅读面试题:Java JDK 动态代理和 CGLIB 动态代理的区别是什么?

AOP 的运行机制

Spring 容器启动时,会扫描 @Aspect 注解,同时解析切面定义的 @Pointcut@Advice 注解,将这些定义保存到 Spring 容器中。当某个 Bean 需要 AOP 增强时,Spring 会为其创建代理对象。然后,Spring 将该代理对象注入到依赖对象的 Bean 中,从而在目标对象方法执行时,触发切面的增强逻辑。

当调用目标对象的方法时,其实调用的是代理对象的方法,代理对象会检查该方法是否匹配某个切点表达式。如果匹配,执行通知。如果没有环绕通知拦截目标方法的执行,代理对象将调用目标对象的原始方法。方法执行完成后,执行后置通知(如 @After, @AfterReturning, @AfterThrowing 等)


Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。

它的内容包括:

  • 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
  • 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
  • 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
  • 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
  • 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
  • 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
  • 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
  • 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw

目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:

想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询

同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。

阅读全文