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

Spring知识梳理(5)

Spring知识梳理(5)

作者:没有四次元口袋的蓝胖
日期:2026-07-17
标签:Java, Spring, 动态代理, JDK代理, CGLIB


一、为什么需要动态代理?

AOP 的底层就是动态代理。不修改原始类的代码,在运行时生成一个代理对象,在方法调用前后插入增强逻辑。

原始对象:UserService(只有业务逻辑) 代理对象:UserService$Proxy(业务逻辑 + 日志 + 事务 + 权限...) 调用方拿到的永远是代理对象,无感知地享受增强功能。

Spring 提供了两种代理方式:JDK 动态代理CGLIB 代理


二、JDK 动态代理(必须手写)

2.1 前提条件

目标类必须实现至少一个接口

2.2 核心类

  • java.lang.reflect.Proxy—— 生成代理对象
  • java.lang.reflect.InvocationHandler—— 定义增强逻辑

2.3 完整代码

// 1. 定义接口publicinterfaceUserService{voidsave();Stringquery(Stringname);}// 2. 目标类实现接口publicclassUserServiceImplimplementsUserService{@Overridepublicvoidsave(){System.out.println("保存用户");}@OverridepublicStringquery(Stringname){System.out.println("查询用户: "+name);return"用户: "+name;}}// 3. 编写 InvocationHandler(增强逻辑)publicclassLogInvocationHandlerimplementsInvocationHandler{privatefinalObjecttarget;// 持有原始对象的引用publicLogInvocationHandler(Objecttarget){this.target=target;}@OverridepublicObjectinvoke(Objectproxy,Methodmethod,Object[]args)throwsThrowable{// ===== 前置增强 =====System.out.println("【日志】开始执行: "+method.getName());System.out.println("【日志】参数: "+Arrays.toString(args));// ===== 调用目标方法 =====Objectresult=method.invoke(target,args);// ↑ 这里的 method.invoke(target, args) 就是反射调用原始对象的方法// ===== 后置增强 =====System.out.println("【日志】返回值: "+result);System.out.println("【日志】执行完毕: "+method.getName());returnresult;// 返回原始结果(也可以修改后返回)}}// 4. 生成代理对象publicclassProxyFactory{publicstaticObjectcreateProxy(Objecttarget){returnProxy.newProxyInstance(target.getClass().getClassLoader(),// 类加载器target.getClass().getInterfaces(),// 目标类实现的所有接口newLogInvocationHandler(target)// 增强逻辑处理器);}}// 5. 使用publicclassMain{publicstaticvoidmain(String[]args){UserServiceuserService=newUserServiceImpl();// 原始对象UserServiceproxy=(UserService)ProxyFactory.createProxy(userService);// 代理对象proxy.save();// 输出:// 【日志】开始执行: save// 【日志】参数: []// 保存用户// 【日志】返回值: null// 【日志】执行完毕: saveproxy.query("张三");// 输出:// 【日志】开始执行: query// 【日志】参数: [张三]// 查询用户: 张三// 【日志】返回值: 用户: 张三// 【日志】执行完毕: query}}

2.4 关键点总结

Proxy.newProxyInstance(classLoader, interfaces, handler) │ │ │ │ │ └── 增强逻辑写在这里 │ └── 代理对象实现这些接口 └── 用哪个类加载器加载
  • 代理对象和原始对象实现相同的接口,对调用方透明
  • invoke()中的method.invoke(target, args)是反射调用原始对象
  • 一个InvocationHandler可以代理任何实现了接口的对象

三、CGLIB 代理

3.1 前提条件

不需要接口,通过生成目标类的子类来实现代理。

3.2 核心类

  • org.springframework.cglib.proxy.Enhancer—— 生成代理对象
  • org.springframework.cglib.proxy.MethodInterceptor—— 定义增强逻辑

3.3 完整代码

// 1. 目标类(不需要接口!)publicclassOrderService{publicvoidcreateOrder(){System.out.println("创建订单");}publicStringgetOrder(intid){System.out.println("查询订单: "+id);return"订单#"+id;}}// 2. 编写 MethodInterceptor(增强逻辑)publicclassLogMethodInterceptorimplementsMethodInterceptor{@OverridepublicObjectintercept(Objectobj,Methodmethod,Object[]args,MethodProxyproxy)throwsThrowable{// ===== 前置增强 =====System.out.println("【日志】开始执行: "+method.getName());// ===== 调用目标方法 =====// 注意:这里用 invokeSuper,不是 invoke!Objectresult=proxy.invokeSuper(obj,args);// ===== 后置增强 =====System.out.println("【日志】执行完毕: "+method.getName());returnresult;}}// 3. 生成代理对象Enhancerenhancer=newEnhancer();enhancer.setSuperclass(OrderService.class);// 设置父类enhancer.setCallback(newLogMethodInterceptor());// 设置增强逻辑OrderServiceproxy=(OrderService)enhancer.create();// 4. 使用proxy.createOrder();proxy.getOrder(1);

3.4 CGLIB 的本质

OrderService(原始类) ↑ │ 继承 │ OrderService$$EnhancerByCGLIB(代理类) - 重写了所有非 final 方法 - 每个方法内部先执行增强逻辑,再调用 super.method() 执行原始逻辑

四、JDK vs CGLIB 全面对比(面试必背)

维度JDK 动态代理CGLIB 代理
前提目标类必须实现接口不需要接口
底层机制java.lang.reflect.ProxyCGLIB 库,基于 ASM 字节码框架
代理方式接口代理(实现相同接口)子类继承(生成子类重写方法)
核心接口InvocationHandler.invoke()MethodInterceptor.intercept()
调用目标method.invoke(target, args)proxy.invokeSuper(obj, args)
性能JDK 8+ 性能已很好生成代理类稍慢,调用性能略快
限制只能代理接口方法不能代理 final 类 / final 方法
依赖JDK 自带,无需额外依赖需要引入 CGLIB 库
Spring 默认Spring Boot 1.x 默认Spring Boot 2.x+ 默认

代码对比速记

JDK 动态代理 CGLIB 代理 ───────────── ───────────── 需要接口 不需要接口 Proxy.newProxyInstance() Enhancer.create() InvocationHandler.invoke() MethodInterceptor.intercept() 反射调用 method.invoke(target) 继承调用 proxy.invokeSuper(obj) 只能代理接口方法 能代理所有非 final 方法 不能代理 final 类 不能代理 final 类/final 方法

五、Spring 如何选择代理方式?

目标类实现了接口? ├── 是 → 默认用 JDK 动态代理 │ (Spring Boot 2.x+ 可通过配置强制用 CGLIB) └── 否 → 必须用 CGLIB

Spring Boot 2.x+ 默认使用 CGLIB 的原因

  1. @Configuration类通常没有接口,需要 CGLIB 代理
  2. 统一使用 CGLIB 可以避免 JDK 代理的一些边界问题
  3. CGLIB 调用性能略优

配置方式

# application.ymlspring:aop:proxy-target-class:true# 强制使用 CGLIB# proxy-target-class: false # 有接口用 JDK,没接口用 CGLIB

六、为什么 CGLIB 不能代理 final?

这是面试高频追问,原理很简单:

CGLIB 的本质:生成目标类的子类,重写方法来插入增强逻辑 class OrderService$$EnhancerByCGLIB extends OrderService { @Override public void createOrder() { // 重写父类方法 // 增强逻辑 super.createOrder(); // 调用父类原始方法 // 增强逻辑 } }
  • final类 → 不能被继承 → 无法生成子类 →不能代理
  • final方法 → 不能被重写 → 无法插入增强逻辑 →不能代理
  • private方法 → 子类不可见 →不能代理

实际踩坑:如果 Service 类不小心标了final,AOP 直接失效,而且不会报错(方法会静默跳过增强),排查很痛苦。


七、思维导图速览

动态代理(AOP 的底层) ├── JDK 动态代理 │ ├── 前提:目标类实现接口 │ ├── 核心:Proxy + InvocationHandler │ ├── invoke() 中写增强逻辑 │ ├── method.invoke(target, args) 反射调用 │ └── JDK 自带,无需额外依赖 │ ├── CGLIB 代理 │ ├── 前提:不需要接口 │ ├── 核心:Enhancer + MethodInterceptor │ ├── intercept() 中写增强逻辑 │ ├── proxy.invokeSuper(obj, args) 继承调用 │ ├── 本质:生成子类,重写方法 │ ├── 限制:不能代理 final 类/方法 │ └── Spring Boot 2.x+ 默认使用 │ └── Spring 选择策略 ├── 有接口 → 默认 JDK(可配置强制 CGLIB) └── 无接口 → 必须 CGLIB

八、写在最后

面试回答模板

“Spring AOP 的底层是动态代理。如果目标类实现了接口,默认用 JDK 动态代理,通过 Proxy + InvocationHandler 实现,在 invoke() 方法中用反射调用原始对象的方法。如果没有接口,用 CGLIB 生成子类来代理,通过 MethodInterceptor 的 intercept() 方法拦截调用,用 invokeSuper 调用父类方法。CGLIB 不能代理 final 类和 final 方法,因为它本质是生成子类重写方法。Spring Boot 2.x 之后默认用 CGLIB。”

学习建议

  1. JDK 动态代理代码要能手写,面试可能让白板写
  2. CGLIB理解原理即可,重点记住"不能代理 final"及原因
http://www.jsqmd.com/news/1214170/

相关文章:

  • REFramework终极指南:一键解锁RE引擎游戏无限可能
  • Llama模型离线部署全链路拆解(含Mac/Win/Linux三端适配+量化压缩实战)
  • 【读书笔记】《命悬一线,我不放手:ICU的生死录》
  • 2026安徽成人本科院校排行榜——推荐安徽大学,双一流,211,安徽省重点大学! - 小张zc
  • cann/asc-devkit SIMT API半精度转换函数
  • 2026年滨湖区健康管理新趋势:细胞科技融入健康服务闭环
  • 智慧教育平台电子课本下载器终极指南:三步快速获取教学资源PDF
  • DLSS Swapper终极指南:三步搞定游戏性能优化,免费提升帧率45%
  • Dante Cloud蓝绿部署:零downtime的服务更新方案
  • 安珂瑞:一颗“智能炸弹“的抗癌二十年,一颗专门找癌细胞的“智能病毒“
  • 广州上门回收爱彼/沛纳海/积家,高端运动奢华腕表专业估价 - 商业每日快报
  • 【高考蓝皮书】2026高考数学试题分析(完整电子版pdf)
  • 2026创业项目-直播间团播动态16级灯牌横幅框感谢大哥升级特效动画透明海报素材ae工程文件 - 胖头鱼美文
  • ng2-dnd 与其他拖放库对比:为什么这个无依赖方案更适合 Angular 项目
  • 2026手机视频转音频全教程:安卓苹果通用,免费免安装方法汇总
  • 跨越数字鸿沟,重塑全球信任:盟接之桥EDI如何以长期主义赋能中国制造出海
  • Suffix Array在text-dedup中的应用:子串精确去重技术详解
  • 2026年7月最新嘉兴欧米茄官方售后服务网点地址及客服电话一览 - 欧米茄官方服务中心
  • 财报入门:教你如何看懂财务报表(课件+视频下载)
  • 深入解析TI MibSPI控制寄存器:ECC诊断与中断配置实战指南
  • 思源宋体CN完全指南:7种字重开源字体让你的中文设计更专业
  • 逸程连锁靠谱吗?2026 重庆渝中 LV 包包回收实地探店实录 - 融媒生活
  • N_m3u8DL-CLI-SimpleG:告别命令行!图形化M3U8下载器新手完全指南
  • Spring知识梳理(4)
  • 从零搭建医疗器械质量管理体系大模型:架构、数据与合规实践
  • IE8linter核心技术解析:PhantomJS与CSS解析的完美结合
  • Codex Dream Skin 使用教程:给 Codex 桌面端安装开源皮肤与自定义主题
  • 2026石家庄黄金首饰回收价参考,全城上门当场结算,6家本地正规回收门店实测盘点 - 企业家观察员
  • 学嵌入式只选北京这7家培训机构 - 教育品牌推荐官
  • 大连西岗区二手欧米茄手表多少钱回收?2026 易奢福到店估价实测 - 奢侈品回收实体店