作者简介:大家好,我是码炫码哥,前中兴通讯、美团架构师,现任某互联网公司CTO,兼职码炫课堂主讲源码系列专题
代表作:《jdk源码&多线程&高并发》,《深入tomcat源码解析》,《深入netty源码解析》,《深入dubbo源码解析》,《深入springboot源码解析》,《深入spring源码解析》,《深入redis源码解析》等
联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬。码炫课堂的个人空间-码炫码哥个人主页-面试,源码等
回答
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
方法。
AOP 的运行机制
Spring 容器启动时,会扫描 @Aspect
注解,同时解析切面定义的 @Pointcut
和 @Advice
注解,将这些定义保存到 Spring 容器中。当某个 Bean 需要 AOP 增强时,Spring 会为其创建代理对象。然后,Spring 将该代理对象注入到依赖对象的 Bean 中,从而在目标对象方法执行时,触发切面的增强逻辑。
当调用目标对象的方法时,其实调用的是代理对象的方法,代理对象会检查该方法是否匹配某个切点表达式。如果匹配,执行通知。如果没有环绕通知拦截目标方法的执行,代理对象将调用目标对象的原始方法。方法执行完成后,执行后置通知(如 @After
, @AfterReturning
, @AfterThrowing
等)