Spring 源码系列(14): JDK 动态代理 vs CGLIB,以及拦截器责任链
阅读前提示AOP 阶段的收官篇。前两篇我们走通了「BPP 在初始化后调用wrapIfNecessary → createProxy」。本篇回答最后两个硬核问题1代理类型怎么选2一次方法调用时多个通知是怎么「排着队」依次执行的并彻底讲清「同类自调用 AOP 失效」的底层原因。一、引子为什么面试必问 JDK 还是 CGLIB因为这背后藏着 Spring 对「性能 / 兼容性 / 能力边界」的取舍JDK 动态代理基于接口生成实现类要求目标必须实现接口调用快、但不能代理类本身的方法非接口方法切不到。CGLIB基于继承生成子类重写方法能代理普通类无需接口但不能代理 final 类 / final 方法且生成字节码略慢。Spring 的DefaultAopProxyFactory就是在这两者间做裁决。二、源码追踪一代理类型裁决逻辑// DefaultAopProxyFactory.javaOverridepublicAopProxycreateAopProxy(AdvisedSupportconfig)throwsAopConfigException{// optimize激进优化proxyTargetClass强制 CGLIBisProxyTargetClassif(config.isOptimize()||config.isProxyTargetClass()||hasNoUserSuppliedProxyInterfaces(config)){Class?targetClassconfig.getTargetClass();if(targetClassnull)thrownewAopConfigException(TargetSource 无法确定目标类);// 目标类是接口或已经是 JDK 代理类 → 只能用 JDK 动态代理if(targetClass.isInterface()||Proxy.isProxyClass(targetClass)){returnnewJdkDynamicAopProxy(config);}// 其余 → CGLIBreturnnewObjenesisCglibAopProxy(config);}else{// 没强制 CGLIB且有用户提供的接口 → JDK 动态代理returnnewJdkDynamicAopProxy(config);}}2.1 裁决规则一览表条件结果proxyTargetClasstrue或 Boot 默认CGLIBoptimizetrueCGLIB目标类无接口、且非强制 JDKCGLIB目标类实现了接口、且proxyTargetClassfalseJDK 动态代理目标类是接口本身 / 已是 JDK 代理强制JDK 动态代理⚠️ 易错点「有接口就一定用 JDK 代理」——错。只要proxyTargetClasstrueBoot 默认就是即使有接口也走 CGLIB。规则优先级是强制 CGLIB 配置 接口存在。三、源码追踪二调用时的拦截器责任链无论哪种代理最终调用都会进入「责任链」。以 JDK 代理为例3.1 JdkDynamicAopProxy.invoke 织入责任链// JdkDynamicAopProxy.javaOverridepublicObjectinvoke(Objectproxy,Methodmethod,Object[]args)throwsThrowable{// ① 取出该方法的拦截器链已按 Order 排序ListObjectchainthis.advised.getInterceptorsAndDynamicInterceptionAdvice(method,targetClass);if(chain.isEmpty()){// ② 无通知 → 直接反射调用目标方法不走代理逻辑returnAopUtils.invokeJoinpointUsingReflection(target,method,args);}// ③ 包装成 MethodInvocation递归推进责任链MethodInvocationinvocationnewReflectiveMethodInvocation(proxy,target,method,args,targetClass,chain);returninvocation.proceed();// ← 责任链启动}3.2 ReflectiveMethodInvocation.proceed递归式「逐个执行」// ReflectiveMethodInvocation.javaOverridepublicObjectproceed()throwsThrowable{if(this.currentInterceptorIndexthis.interceptorsAndDynamicMethodMatchers.size()-1){// 链走完 → 调用真实目标方法returninvokeJoinpoint();}ObjectinterceptorOrInterceptionAdvicethis.interceptorsAndDynamicMethodMatchers.get(this.currentInterceptorIndex);if(interceptorOrInterceptionAdviceinstanceofMethodInterceptor){MethodInterceptormi(MethodInterceptor)interceptorOrInterceptionAdvice;returnmi.invoke(this);// ← 每个拦截器处理完后再调 proceed() 推进下一个}// 动态匹配拦截器returnproceed();} 责任链本质这是经典的「递归 索引推进」。Around对应的MethodInterceptor.invoke(this)内部会调用invocation.proceed()进入下一个拦截器当所有拦截器走完才执行真实目标方法。出栈顺序天然形成「环绕」效果。3.3 通知执行顺序单切面前通知在链路中的位置执行特点Aroundproceed 之前最外最先执行包住一切Before靠前proceed 前打印/校验目标方法链底invokeJoinpoint()AfterReturning返回后拿到返回值Afterfinally必执行AfterThrowing异常分支仅异常时Aroundproceed 之后最外返回最后收尾四、终极问题同类自调用为什么 AOP 失效ServicepublicclassOrderService{publicvoidcreate(){this.validate();// ← this 指向【原始对象】不是代理}Transactionalpublicvoidvalidate(){/* ... */}}原因create()是由代理对象调入的但方法体内的this是原始目标对象Spring 注入的是原始 Bean代理只是外层壳。this.validate()绕过了代理责任链根本没机会执行Transactional/ 切面全部失效。两种解法// 解法一exposeProxytrue 后从 AopContext 取代理再调EnableAspectJAutoProxy(exposeProxytrue)publicclassAopConfig{}publicvoidcreate(){((OrderService)AopContext.currentProxy()).validate();// 走代理AOP 生效}// 解法二拆到另一个 Bean推荐解耦更清晰AutowiredOrderValidatorvalidator;publicvoidcreate(){validator.validate();}// 跨 Bean 调用代理正常介入 结论自调用失效的根因是「代理是包装不是替换」——this永远是原始对象。理解这点所有「AOP 没生效」的诡异问题都能定位。五、常见误区误区正解有接口就一定 JDK 代理错proxyTargetClasstrue时强制 CGLIBCGLIB 不能代理任何类只能代理非 final 类、非 final 方法Around不调 proceed 也能返回不调 proceed 目标不执行需自己定返回值自调用时 AOP 也生效失效因this指向原始对象见第四节无匹配通知的 Bean 走代理也慢空链直接反射调目标几乎无开销 面试题自测DefaultAopProxyFactory选 JDK 还是 CGLIB 的完整规则为什么proxyTargetClasstrue时即使有接口也走 CGLIBReflectiveMethodInvocation.proceed()的递归推进原理同类自调用 AOP 失效的根因两种解法CGLIB 代理有什么限制final / 构造器等为什么Around能完全控制目标方法是否执行 Debug 小技巧在JdkDynamicAopProxy.invoke断点观察chain列表的元素顺序对应通知执行顺序再单步进入ReflectiveMethodInvocation.proceed用「Step Over 走到mi.invoke(this)、再 Step Into」的方式逐拦截器感受责任链的递归推进。最后在create()里加this AopContext.currentProxy()的条件断点亲眼看到「自调用时两边不等」。下一篇预告AOP 阶段完结。下一篇进入Spring MVC 阶段第 15 篇DispatcherServlet如何初始化——onRefresh → initStrategies九大组件以及一次 HTTP 请求在 Spring 里的「总调度」是如何搭起来的。如果这篇对你有帮助欢迎点赞 · 收藏 · 关注三连支持。Spring 源码系列共 30 篇由浅入深持续更新中。有疑问或想深挖的源码点评论区告诉我下篇见。

相关新闻

初中数学提分实战:万能模板与分层技巧攻克几何函数压轴题

初中数学提分实战:万能模板与分层技巧攻克几何函数压轴题

这次我们来看一个针对初中数学提分的系统性课程资源——“2027 马哥初中数学全学段三年数学提分课”。这个资源的核心目标非常明确:直击初中生在几何、函数、压轴大题三大核心板块的痛点,提供一套可操作的解题方法论和训练体系。它不是零散的知识点讲解&…

2026/10/10 15:30:57 阅读更多 →
2026年高性价比项目管理软件评测与选型指南

2026年高性价比项目管理软件评测与选型指南

1. 项目管理软件市场现状与选型痛点 2026年的企业协作环境正面临前所未有的复杂度提升。根据Gartner最新调研数据,73%的中小企业团队同时进行着跨时区、跨部门的敏捷与瀑布式混合项目,而传统电子表格和邮件协作方式导致的进度失控率高达41%。我最近为5家…

2026/10/14 11:55:16 阅读更多 →
DRV8301/8303电机驱动代码实战:从SPI配置到电流采样校准

DRV8301/8303电机驱动代码实战:从SPI配置到电流采样校准

1. 项目概述:从芯片手册到电机转起来做电机驱动,尤其是无刷直流(BLDC)或者永磁同步电机(PMSM)的驱动,硬件设计只是第一步,真正让电机按照你的想法精准、高效、稳定地转起来&#xff…

2026/9/29 11:09:59 阅读更多 →

最新新闻

AI辅助开发中的“失忆”难题:用context-mode实现轻量级上下文管理

AI辅助开发中的“失忆”难题:用context-mode实现轻量级上下文管理

做 AI 辅助开发半年多,我踩过最深的坑不是 prompt 写得不够好,而是上下文丢了。今天聊的项目 context-mode,就是专门解决这个问题的一套轻量级上下文管理方案。它本身是一个跑在终端里的命令行工具,负责把每次 AI 协作会话里的“记…

2026/10/15 6:19:43 阅读更多 →
冷热电微网双层优化实战:MATLAB+YALMIP建模求解全解析

冷热电微网双层优化实战:MATLAB+YALMIP建模求解全解析

冷热电微网双层优化,这几年在综合能源方向几乎成了标配选题,MATLABYALMIP也成了最常见的实现组合。但说实话,光看论文摘要觉得很简单,真到自己写代码时才发现一堆坑:上层和下层怎么嵌套?储能SOC怎么递推才不…

2026/10/15 6:19:43 阅读更多 →
拆解ThreadLocal性能困境:数组索引方案设计与实践

拆解ThreadLocal性能困境:数组索引方案设计与实践

1. 先说结论:ThreadLocal 没那么神,真正快的是数组索引方案最近我把网关里一组高频读写的 ThreadLocal 变量换成了数组索引型本地线程变量,单线程内 set/get 混合压测下来大概快了 2 到 3 倍,接口整体延时也降了一截。这不是什么黑…

2026/10/15 6:19:43 阅读更多 →
多维信号传输:从星座图到语义空间的通信底层逻辑

多维信号传输:从星座图到语义空间的通信底层逻辑

1. 为什么“多维信号传输”不是数学游戏,而是现代通信的底层呼吸节奏“数字通信第三章——多维信号传输”,光看标题,很多人会下意识翻页:又一个教科书里的抽象章节,坐标系、正交基、欧氏距离……好像离手机刷视频、Wi-…

2026/10/15 6:19:43 阅读更多 →
TensorFlow 2.x实战经验:迁移、训练、部署一站式指南

TensorFlow 2.x实战经验:迁移、训练、部署一站式指南

坦白说,看到"tensorflow"这个标题,我第一反应不是"该从什么基础概念讲起",而是想聊聊这两年我自己从TensorFlow 1.x迁到2.x的真实经历。2019年那会儿我还在维护着一套用1.x写的图像分类系统,每次训练启动要写…

2026/10/15 6:19:43 阅读更多 →
王道操作系统课后选填题刷题法:错题滚动+答案隔离+三轮复盘

王道操作系统课后选填题刷题法:错题滚动+答案隔离+三轮复盘

简介:这份王道操作系统课后题选填文档是一份针对考研操作系统复习的选择题与填空题专项资料。内容以王道教材课后题为蓝本,按知识点整理出数十道填空与选择,覆盖进程管理、处理机调度、死锁、内存与虚拟存储、文件系统、设备管理及磁盘调度等…

2026/10/15 6:18:43 阅读更多 →

日新闻

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

1. 为什么选择容器化方式部署 LoRa 网络服务LoRa 物联网项目落地时,网络服务器是整个链路的核心枢纽。它负责接收网关上报的数据包、完成去重与解密、再把有效载荷分发给上层应用。早期大家习惯把网络服务器直接装在物理机上,依赖一堆系统库和特定版本的…

2026/10/15 0:00:46 阅读更多 →
共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

1. 共享推理服务里,扩缩容到底难在哪做过大模型推理服务部署的人,大概都经历过这样的场景:一个团队共用一套推理集群,A 业务跑的是 7B 的小模型,B 业务跑的是 72B 的大模型,C 业务白天流量高、晚上几乎归零…

2026/10/15 0:00:46 阅读更多 →
Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

1. 引子:一次内核恐慌引发的追查先从一个真实场景说起。某次我给一台老旧的嵌入式设备折腾系统裁减,为了把启动时间压进两秒以内,我手贱地把一串自认为“没用”的 systemd 服务给 disable 了,其中就包括systemd-remount-fs.servic…

2026/10/15 0:00:46 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/14 3:30:28 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/14 16:30:29 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 0:14:36 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 22:45:47 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 7:53:34 阅读更多 →