回答
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] ,回复【面试题】 即可免费领取。