Skip to content

Spring进阶 - Spring AOP实现原理 #33

Description

@cyjxiaozhi

一、AOP切面的实现

Spring 的 AOP(Aspect-Oriented Programming,面向切面编程) 是一种用来解耦关注点的编程方式,它通过动态代理机制实现。在 Spring 中,AOP 切面的实现原理依赖于 代理模式 和 拦截器链。下面是 Spring AOP 实现的详细原理及切面实现过程的解析。

  1. AOP 的核心概念

在理解实现原理之前,先明确 AOP 的几个核心概念:
1. 切面(Aspect):
• 定义横切关注点的模块,包括横切逻辑(如日志、权限校验等)。
• 通过 @aspect 注解定义。
2. 连接点(Join Point):
• 应用切面的具体位置(如方法调用、异常抛出等)。
3. 切入点(Pointcut):
• 定义在哪些连接点应用切面。
4. 通知(Advice):
• 切面中实际的横切逻辑,分为以下几种类型:
@before:在方法执行前运行。
@after:在方法执行后运行。
• @AfterReturning:在方法成功返回后运行。
• @AfterThrowing:在方法抛出异常时运行。
@around:环绕通知,可以控制方法的执行。

  1. Spring AOP 实现的核心原理

Spring AOP 的实现依赖于 动态代理 和 拦截器链。

2.1 动态代理
• JDK 动态代理:
• 适用于代理接口的场景。
• 通过 java.lang.reflect.Proxy 创建代理对象。
• 原理是生成目标类的接口代理类,拦截方法调用。
• CGLIB 动态代理:
• 适用于代理普通类的场景。
• 通过生成目标类的子类来代理目标类。
• 使用的是字节码增强技术(ASM 库)。

Spring 自动选择代理方式:如果目标类实现了接口,优先使用 JDK 动态代理;如果没有接口,则使用 CGLIB 动态代理。

2.2 拦截器链

Spring AOP 的执行流程是基于拦截器链实现的:
• 每个切面中的通知(Advice)会被封装成一个 拦截器。
• Spring 会按照切入点的定义将这些拦截器串联成一个拦截器链。
• 方法调用时,代理对象会按顺序执行拦截器链中的拦截逻辑。

  1. Spring AOP 的切面实现过程

以下是 Spring AOP 切面实现的详细步骤:

3.1 定义切面类

切面通过 @aspect 注解定义,包含切入点和通知逻辑。

@aspect
@component
public class LoggingAspect {

@Before("execution(* com.example.service.*.*(..))")
public void beforeAdvice() {
    System.out.println("Before method execution...");
}

@After("execution(* com.example.service.*.*(..))")
public void afterAdvice() {
    System.out.println("After method execution...");
}

}

3.2 解析切面(容器初始化阶段)

Spring 在容器初始化时,使用 AnnotationAwareAspectJAutoProxyCreator 处理切面:
1. 扫描切面:
• Spring 容器通过 @componentscan 找到标注了 @aspect 的类。
• 解析 @pointcut 和通知方法,将其封装为切面定义。
2. 解析切入点表达式:
• 使用 AspectJ 表达式解析器解析切入点。
• 确定需要代理的连接点。
3. 创建代理对象:
• 根据切入点,Spring 判断哪些 Bean 需要增强。
• 为目标类创建代理对象,代理对象中嵌入拦截器链。

3.3 方法调用阶段

当调用被代理方法时,Spring AOP 的执行流程如下:
1. 代理对象拦截方法调用:
• JDK 或 CGLIB 动态代理拦截方法调用,将其转发给 MethodInterceptor。
2. 执行拦截器链:
• Spring AOP 的拦截器链逐个执行通知逻辑:
@before:方法执行前的逻辑。
@around:可以控制是否调用目标方法。
• @AfterReturning 或 @AfterThrowing:根据方法执行结果选择执行的逻辑。
@after:方法结束后总会执行。
3. 调用目标方法:
• 如果没有 @around 阻止方法调用,则代理对象最终调用目标类的方法。

  1. 核心组件与实现机制

Spring AOP 的实现涉及多个核心组件:

4.1 ProxyFactory
• 核心的代理工厂类,用于根据目标类和拦截器链创建代理对象。
• 支持 JDK 和 CGLIB 动态代理。

4.2 Advisor 和 Advice
• Advisor:切面增强的核心抽象,包含切入点和通知。
• Advice:具体的增强逻辑,例如 MethodBeforeAdvice、AfterReturningAdvice。

4.3 MethodInterceptor
• 方法拦截器接口,用于拦截方法调用。
• Spring 提供多个默认实现(如 MethodBeforeAdviceInterceptor、AroundAdviceInterceptor)。

4.4 AopProxy
• 动态代理的抽象接口,具体实现包括:
• JdkDynamicAopProxy:基于 JDK 动态代理。
• CglibAopProxy:基于 CGLIB 动态代理。

  1. 示例:切面的执行流程

假设有以下切面定义:

@aspect
@component
public class LoggingAspect {

@Before("execution(* com.example.service.*.*(..))")
public void logBefore(JoinPoint joinPoint) {
    System.out.println("Before method: " + joinPoint.getSignature().getName());
}

@AfterReturning(pointcut = "execution(* com.example.service.*.*(..))", returning = "result")
public void logAfterReturning(Object result) {
    System.out.println("Method returned: " + result);
}

@AfterThrowing(pointcut = "execution(* com.example.service.*.*(..))", throwing = "ex")
public void logAfterThrowing(Exception ex) {
    System.out.println("Exception thrown: " + ex.getMessage());
}

}

执行流程:
1. Spring 初始化时创建代理对象。
2. 调用 com.example.service 包中的方法时:
• logBefore 会在方法执行前运行。
• 如果方法成功返回,logAfterReturning 执行。
• 如果方法抛出异常,logAfterThrowing 执行。

  1. 总结

Spring AOP 的切面实现通过 动态代理 和 拦截器链 完成:
• 代理模式 用于拦截方法调用并将其转发给拦截器链。
• 拦截器链 包含多个通知(Advice),根据切入点定义的顺序执行。
• 核心组件包括 ProxyFactory、Advisor、Advice 和 MethodInterceptor。
• 使用 JDK 动态代理或 CGLIB 动态代理生成代理对象。

这种设计方式使得 Spring AOP 在运行时动态增强目标对象的行为,而无需修改目标类的代码。

二、AOP代理的创建
前文主要Spring AOP原理解析的切面实现过程(加载配置,将切面类的所有切面方法根据使用的注解生成对应Advice,并将Advice连同切入点匹配器和切面类等信息一并封装到Advisor)。

本文接着介绍postProcessAfterInitialization的方法,即Spring AOP的代理(cglib或jdk)的创建过程。

Spring AOP 实现原理详解之 AOP 代理的创建

Spring AOP 的代理创建是其核心之一,理解代理的创建过程可以帮助我们更好地掌握 Spring AOP 的工作原理。以下是 AOP 代理创建的详细讲解,包括具体的执行流程、核心类与组件,以及如何选择代理方式(JDK 动态代理或 CGLIB)。

  1. AOP 代理是什么?

AOP 代理是增强目标对象行为的代理类,它拦截方法调用并执行横切逻辑(通知)。Spring AOP 使用动态代理技术来实现代理对象的创建。
• 动态代理:代理类在运行时动态生成,分为两种方式:
• JDK 动态代理:基于 Java 的反射机制,为实现接口的目标类创建代理。
• CGLIB 动态代理:通过生成目标类的子类来实现代理,适用于没有接口的目标类。

  1. 代理的创建过程概览

代理的创建分为以下几个步骤:
1. Bean 的定义解析:
• Spring 容器初始化时,扫描并加载所有 Bean 定义。
• 如果 Bean 被 AOP 的切入点匹配,则标记为需要代理的对象。
2. 创建代理对象:
• Spring 使用 ProxyFactory 创建代理对象。
• 根据目标类的类型和接口选择代理方式(JDK 或 CGLIB)。
3. 织入增强逻辑:
• 将切面的通知(Advice)封装成拦截器,加入拦截器链。
4. 返回代理对象:
• 代理对象会替代原始对象,被注入到其他 Bean 中。

  1. Spring AOP 代理创建的详细流程

3.1 触发代理创建

Spring AOP 的代理创建由 AnnotationAwareAspectJAutoProxyCreator 类完成,它是一个 BeanPostProcessor,拦截 Bean 的初始化过程:
• 在 postProcessBeforeInitialization 和 postProcessAfterInitialization 方法中判断 Bean 是否需要代理。
• 如果需要代理,则使用 createProxy() 方法生成代理对象。

3.2 代理创建的主要逻辑

代理的创建由 ProxyFactory 类负责,具体流程如下:
1. 初始化 ProxyFactory:
• ProxyFactory 是 Spring AOP 创建代理对象的核心工具类。
• 它根据目标类和增强逻辑(通知)来创建代理对象。
2. 设置代理属性:
• 判断目标类是否有接口,选择代理方式:
• 如果目标类实现了接口,默认使用 JDK 动态代理。
• 如果目标类没有实现接口,或者强制要求使用 CGLIB,则使用 CGLIB 动态代理。
3. 生成代理对象:
• 调用 getProxy() 方法,根据代理方式返回代理对象。

  1. 动态代理方式详解

4.1 JDK 动态代理
• 使用场景:
• 目标类实现了接口。
• 实现原理:
• JDK 提供的 java.lang.reflect.Proxy 类和 InvocationHandler 接口。
• Spring 使用 JdkDynamicAopProxy 类实现。
• 核心方法:
• Proxy.newProxyInstance() 创建代理对象。
• 方法调用时,由 InvocationHandler 的 invoke() 方法处理。

示例:

public class JdkDynamicAopProxy implements AopProxy, InvocationHandler {
@OverRide
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 执行拦截器链
return methodInvocation.proceed();
}
}

4.2 CGLIB 动态代理
• 使用场景:
• 目标类没有实现接口。
• 强制要求使用 CGLIB。
• 实现原理:
• 使用 CGLIB 库生成目标类的子类代理。
• Spring 使用 CglibAopProxy 类实现。
• 核心方法:
• 使用 CGLIB 的 Enhancer 类创建代理对象。
• 通过字节码增强在子类中加入拦截逻辑。

示例:

public class CglibAopProxy implements AopProxy {
@OverRide
public Object getProxy() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(targetClass);
enhancer.setCallback(methodInterceptor);
return enhancer.create();
}
}

4.3 JDK 动态代理 vs. CGLIB 动态代理

特点 JDK 动态代理 CGLIB 动态代理
实现方式 基于接口 基于目标类子类
目标对象要求 必须实现接口 不需要实现接口
性能 直接调用接口方法,性能稍高 使用字节码增强,性能稍低
代理范围 只能代理接口中定义的方法 可以代理类中所有非 final 方法

  1. AOP 拦截器链的织入

代理对象的核心功能在于拦截方法调用,并执行横切逻辑。这由 拦截器链 实现:
1. 拦截器链的构建:
• Spring AOP 将切面的通知(Advice)封装为拦截器(MethodInterceptor)。
• 根据切入点匹配结果,构建拦截器链。
2. 拦截器链的执行:
• 方法调用时,代理对象会依次执行拦截器链中的逻辑。
• 通知的执行顺序如下:
@before 通知先执行。
@around 通知环绕方法执行。
• @AfterReturning 或 @AfterThrowing 通知根据结果执行。
@after 通知最后执行。

  1. 示例:AOP 代理创建过程

假设我们有以下切面:

@aspect
@component
public class LoggingAspect {
@before("execution(* com.example.service..(..))")
public void logBefore() {
System.out.println("Before method execution...");
}
}

代理创建过程:
1. Bean 初始化:
• Spring 扫描 LoggingAspect 并注册为切面。
• 检查目标类是否匹配切入点。
2. 创建代理:
• 如果目标类实现了接口,Spring 使用 JdkDynamicAopProxy。
• 如果没有接口,Spring 使用 CglibAopProxy。
3. 返回代理对象:
• Spring 将代理对象注入到使用目标类的其他 Bean 中。

  1. 总结:面试中的常见问题

基础问题
1. Spring AOP 的代理创建使用了哪些技术?
2. JDK 动态代理和 CGLIB 动态代理的区别是什么?
3. 如何选择代理方式?

高级问题
1. 为什么 Spring AOP 默认使用 JDK 动态代理?
2. 如果目标类既有接口,又强制使用 CGLIB,该如何配置?
3. 为什么代理对象调用自身方法无法触发切面?

面试要点
• 熟悉 Spring AOP 的代理创建过程。
• 理解 ProxyFactory、JdkDynamicAopProxy 和 CglibAopProxy 的原理和实现。
• 掌握 JDK 动态代理和 CGLIB 动态代理的适用场景及优缺点。
• 清楚拦截器链的构建和执行过程。

三、Cglib代理实现
3.1 动态代理要解决什么问题?
什么是代理?
我(client)如果要买(doOperation)房,可以找中介(proxy)买房,中介直接和卖方(target)买房。中介和卖方都实现买卖(doOperation)的操作。中介就是代理(proxy)。

什么是动态代理?
动态代理就是,在程序运行期,创建目标对象的代理对象,并对目标对象中的方法进行功能性增强的一种技术。

Cglib代理的流程
image
CGLIB 代理的流程

CGLIB(Code Generation Library)是一个强大的代码生成库,它通过继承目标类并在子类中覆盖方法来实现代理。Spring 使用 CGLIB 来为没有实现接口的类创建代理。

CGLIB 代理的核心原理是 字节码增强,通过生成目标类的子类(子类继承目标类并覆盖其中的方法),然后通过动态代理来增强目标类的功能。

在 Spring 中,CGLIB 动态代理的创建和执行流程涉及以下几个步骤。

  1. CGLIB 代理的使用场景
    • 无接口的类:当目标类没有实现接口时,Spring 会使用 CGLIB 生成该类的子类来实现代理。
    • 强制使用 CGLIB:即使目标类实现了接口,开发者也可以配置 Spring 使用 CGLIB 代理(例如,配置 @EnableAspectJAutoProxy(proxyTargetClass = true))。

  2. CGLIB 代理的基本原理

CGLIB 通过 继承 目标类并覆盖其中的 非 final 方法,在代理方法中插入增强逻辑,从而实现代理功能。CGLIB 使用字节码增强技术生成目标类的子类,并在子类中加入代理行为。

  1. CGLIB 代理的创建流程

CGLIB 代理的创建流程大致分为以下几个步骤:

3.1 目标类的准备

目标类(需要被代理的类)不能是 final 类,也不能包含 final 方法,因为 CGLIB 是通过继承和方法重写来实现的。如果目标类是 final 类,CGLIB 无法生成代理。

3.2 创建 Enhancer 实例

CGLIB 代理的核心类是 Enhancer,它负责创建代理对象。Enhancer 类是 CGLIB 中提供的用于创建代理对象的类,代理对象的创建过程通过该类来完成。

Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(TargetClass.class); // 设置目标类的父类
enhancer.setCallback(new MethodInterceptorImpl()); // 设置回调方法,用于拦截方法调用

3.3 设置回调(Callback)

Enhancer 通过回调函数来实现增强逻辑。Spring 会为每个代理类生成一个回调类,该类实现了 MethodInterceptor 接口。MethodInterceptor 的 intercept() 方法会在代理方法执行时被调用。

MethodInterceptor 接口提供了一个 intercept() 方法,目标方法的调用将被转发到 intercept() 方法进行处理。MethodInterceptor 实现类中可以加入额外的逻辑(如日志、事务管理等),然后调用目标方法。

public class MethodInterceptorImpl implements MethodInterceptor {
@OverRide
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
// 在这里进行增强处理
System.out.println("Before method execution");

    // 调用目标方法
    Object result = proxy.invokeSuper(obj, args);
    
    // 在这里处理后续增强逻辑
    System.out.println("After method execution");
    
    return result;
}

}

•	proxy.invokeSuper(obj, args):执行目标类的方法,proxy 是 CGLIB 提供的代理对象,invokeSuper() 方法会调用目标类的真实方法。

3.4 创建代理类

通过调用 enhancer.create() 方法,CGLIB 会生成目标类的子类,并将目标方法与 MethodInterceptor 的 intercept() 方法关联起来,从而实现增强。生成的代理类是目标类的子类,所有非 final 方法都会被覆盖。

TargetClass proxy = (TargetClass) enhancer.create();

3.5 返回代理对象

返回生成的代理对象,这个代理对象会替代原来的目标对象,并通过 MethodInterceptor 实现对方法调用的增强。

  1. CGLIB 代理的执行流程

    1. 目标类实例化:在代理对象的创建过程中,CGLIB 会通过反射创建目标类的实例。
    2. 方法拦截:CGLIB 会为每个目标方法生成一个代理方法,这些代理方法会调用 MethodInterceptor 的 intercept() 方法,进而插入增强逻辑。
    3. 增强逻辑执行:代理方法执行前,执行增强逻辑(如日志、事务等),然后通过 invokeSuper() 调用目标方法。
    4. 返回结果:在目标方法执行完毕后,增强逻辑可以处理返回值或异常,最后返回方法的结果。
  2. CGLIB 代理的限制

    1. 目标类不能是 final 类:
      • CGLIB 需要通过继承目标类来生成代理,因此目标类不能是 final 类。
      • 目标类的 final 方法也不能被代理,因为 final 方法不能被重写。
    2. 性能开销:
      • 由于 CGLIB 需要通过字节码生成技术生成子类代理,因此性能开销较 JDK 动态代理稍高。
      • 如果目标类没有接口,且方法调用频繁,使用 CGLIB 代理时可能会有一定的性能损失。
  3. CGLIB 代理与 JDK 动态代理的对比

特性 JDK 动态代理 CGLIB 动态代理
代理方式 基于接口代理 基于子类继承代理
目标类要求 目标类必须实现至少一个接口 目标类不能是 final 类,且方法不能是 final
代理的能力 只能代理接口中的方法 可以代理目标类中的所有方法(非 final 方法)
性能 性能较高(仅使用接口方法) 相对较慢(需要生成字节码和继承目标类)
适用场景 目标类实现了接口,且可以通过接口来访问目标方法 目标类没有接口,或者强制使用 CGLIB 生成代理

  1. 示例代码

假设我们有一个 TargetClass,并且我们希望通过 CGLIB 为其创建代理:

public class TargetClass {
public void sayHello() {
System.out.println("Hello, world!");
}
}

CGLIB 代理的实现:

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

public class CglibProxyExample {
public static void main(String[] args) {
// 1. 创建目标对象的增强器
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(TargetClass.class); // 设置目标类
enhancer.setCallback(new MethodInterceptor() { // 设置回调函数(增强逻辑)
@OverRide
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("Before method execution...");
Object result = proxy.invokeSuper(obj, args); // 执行目标方法
System.out.println("After method execution...");
return result;
}
});

    // 2. 创建代理对象
    TargetClass proxy = (TargetClass) enhancer.create();

    // 3. 调用代理对象的方法
    proxy.sayHello();
}

}

输出:

Before method execution...
Hello, world!
After method execution...

在这个例子中,Enhancer 创建了 TargetClass 的代理类,并在代理方法中插入了增强逻辑。在方法执行前后分别打印了日志信息。

总结
• CGLIB 代理通过生成目标类的子类来拦截方法调用,能够对没有接口的类进行代理。
• CGLIB 使用字节码生成技术,需要继承目标类,因此目标类不能是 final 类,且方法不能是 final。
• 代理的核心类是 Enhancer,通过设置回调(MethodInterceptor)来实现增强逻辑。
• 相较于 JDK 动态代理,CGLIB 的性能稍低,但它可以代理没有接口的类,是处理一些特殊场景的有力工具。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions