当前位置: 首页 > news >正文

Spring AOP:JDK与CGLIB代理机制解析

AOP 底层两种实现方式的区别及实现原理详解

在 Spring 框架中,AOP(面向切面编程)的底层实现主要依赖 JDK 动态代理CGLIB 动态代理 两种技术。以下从实现原理、性能对比、适用场景等方面展开分析,并结合代码示例说明其工作机制。


一、核心区别对比

特性

JDK 动态代理

CGLIB 动态代理

实现原理

基于接口,通过生成代理类实现拦截

基于字节码生成,通过继承目标类生成子类

目标类要求

必须实现至少一个接口

无需接口,但无法代理 final

类/方法

性能

反射调用开销较大,JDK8 后优化明显

字节码生成耗时,但运行时调用更快

灵活性

受限(依赖接口)

更高(支持无接口类)

Spring 默认选择

目标类有接口时优先使用

目标类无接口时自动切换


二、实现原理与代码解析
1. JDK 动态代理

实现机制
通过 java.lang.reflect.Proxy 类动态生成代理类,代理类实现目标接口,并将方法调用委托给 InvocationHandler 处理。

关键步骤

  1. 生成代理类:代理类继承 Proxy 并实现目标接口。
  2. 方法拦截:通过 InvocationHandler.invoke() 拦截方法调用,插入切面逻辑。
  3. 反射调用:在代理类中通过反射调用目标对象的实际方法。

代码示例

// 定义接口与实现类
public interface UserService {void save();
}
public class UserServiceImpl implements UserService {public void save() { System.out.println("保存用户"); }
}// 创建代理工厂
public class JdkProxyFactory implements InvocationHandler {private Object target;public JdkProxyFactory(Object target) { this.target = target; }public Object getProxy() {return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("前置通知");Object result = method.invoke(target, args); // 调用目标方法System.out.println("后置通知");return result;}
}// 使用代理
UserService proxy = (UserService) new JdkProxyFactory(new UserServiceImpl()).getProxy();
proxy.save(); // 输出:前置通知 -> 保存用户 -> 后置通知

特点

  • 依赖 Java 标准库,无需额外依赖。
  • 仅能代理接口方法,无法增强类中非接口方法。

2. CGLIB 动态代理

实现机制
通过字节码生成库(如 ASM)动态生成目标类的子类,重写final 方法并插入切面逻辑

关键步骤

  1. 生成子类:通过 Enhancer 设置目标类为父类。
  2. 方法拦截:通过 MethodInterceptor.intercept() 拦截方法调用。
  3. 调用父类方法:通过 methodProxy.invokeSuper() 触发原始逻辑。

代码示例

// 目标类(无需接口)
public class ProductService {public void create() { System.out.println("创建产品"); }
}// 创建 CGLIB 代理
public class CglibProxy implements MethodInterceptor {private Object target;public CglibProxy(Object target) { this.target = target; }public Object getProxy() {Enhancer enhancer = new Enhancer();enhancer.setSuperclass(target.getClass());enhancer.setCallback(this);//new CglibProxyreturn enhancer.create();}@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {System.out.println("前置日志");Object result = proxy.invokeSuper(obj, args); // 调用父类方法System.out.println("后置日志");return result;}
}// 使用代理
ProductService proxy = (ProductService) new CglibProxy(new ProductService()).getProxy();
proxy.create(); // 输出:前置日志 -> 创建产品 -> 后置日志

特点

  • 不依赖接口,适用于无接口类。
  • 无法代理 final 类或方法(因继承被限制)。

三、性能对比与选型建议
  1. 性能差异
    • 低频调用:JDK 代理(反射优化后)与 CGLIB 性能接近。
    • 高频调用:CGLIB 因避免反射开销,性能更优(如百万次调用差距可达 10 倍以上)。
    • 代理生成耗时:CGLIB 生成代理类时间较长,但运行时效率更高。
  1. 选型场景
    • 优先 JDK 代理:目标类实现接口、需解耦代理与业务逻辑。
    • 强制 CGLIB:目标类无接口、需代理 final 方法(需配置 proxy-target-class="true")。
    • 特殊需求:高频方法调用(如事务监控)建议使用 AspectJ 编译时织入。

四、Spring AOP 的代理选择流程
  1. Bean 初始化阶段:Spring 检查目标类是否实现接口。
  2. 代理生成
    • 若实现接口 → 使用 JDK 动态代理。
    • 若未实现接口或强制配置 → 使用 CGLIB。
  1. 方法调用拦截:通过代理对象触发通知逻辑。

流程图

Bean 初始化 → 检查接口 → 生成代理类 → 拦截方法 → 执行通知链

五、常见问题与解决方案
  1. 内部方法调用失效
    • 问题:通过 this 调用代理对象的方法时,绕过代理逻辑。
    • 解决:通过 ApplicationContext 获取代理对象再调用。
((UserService) context.getBean("userService")).save();
  1. final 类/方法代理失败
    • 原因:CGLIB 无法继承 final 类或方法。
    • 解决:避免使用 final 修饰需增强的类或方法。
http://www.xdnf.cn/news/1391257.html

相关文章:

  • 数据结构(C语言篇):(五)单链表算法题(上)
  • 对于牛客网—语言学习篇—编程初学者入门训练—函数类型:BC156 牛牛的数组匹配及BC158 回文数解析
  • 美食推荐|美食推荐小程序|基于微信小程序的美食推荐系统设计与实现(源码+数据库+文档)
  • GPFS性能优化
  • Skywork:昆仑万维推出天工超级智能体
  • vue3 表单项不对齐的解决方案
  • Custom SRP - LOD and Reflections
  • 【AI】常见8大LLM大语言模型地址
  • SPSA为什么要求三阶可导
  • 事务和锁(进阶)
  • 对接连连支付(七)-- 退款查询
  • C++ 线程安全初始化机制详解与实践
  • Elasticsearch核心配置与性能优化
  • 从零开始的python学习——常量与变量
  • 复杂保单信息如何自动提取
  • 【新启航】3D 逆向抄数的工具技术与核心能力:基于点云处理的扫描设备操作及模型重建方法论
  • Java面试现场:Spring Boot+Redis+MySQL在电商场景下的技术深度剖析
  • Shell 编程基础(续):流程控制与实践
  • Python Imaging Library (PIL) 全面指南:PIL图像处理异常处理与优化
  • 数据结构:选择排序 (Selection Sort)
  • JavaScript 中,判断一个数组是否包含特定值
  • 【完整源码+数据集+部署教程】停车位状态检测系统源码和数据集:改进yolo11-DCNV2-Dynamic
  • 机器学习入门,从线性规划开始
  • 基于 Selenium 和 BeautifulSoup 的动态网页爬虫:一次对百度地图 POI 数据的深度模块化剖析
  • el-table实现双击编辑-el-select选择框+输入框限制非负两位小数
  • SQL知识
  • Python的一次实际应用:利用Python操作Word文档的页码
  • 打造高效外贸网站:美国服务器的战略价值
  • ASCM使用手册
  • 从零开始构建卷积神经网络(CNN)进行MNIST手写数字识别