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

AOP 代理对象的诞生时刻:Bean 生命周期中的“夺舍”瞬间

各位大佬欢迎来到 Spring 容器最神秘、最惊心动魄的现场很多人以为 AOP 是“天生”的 Bean 一出生就带着光环。大错特错不过是前人在负重前行Spring 先造出一个“纯净的肉身”原始对象在它即将“成年”初始化完成的前一秒通过BeanPostProcessor施展“夺舍”大法强行把它的灵魂替换成一个“披着马甲的替身”代理对象。从此以后容器里注册的、你注入的、别人拿到的统统都是这个替身。而那个“纯净的肉身”除非你通过特殊手段如AopTargetUtils否则再也见不到了。是不是很巧妙如此让我们深入Bean 生命周期的核心腹地复盘这场精密的“夺舍”行动。第一幕 “夺舍”与“替身”想象一下 Spring 容器是一个“明星经纪公司”造肉身 (Instantiation)公司先招募了一个有潜力的新人调用构造函数new UserService()。这时候他还是个素人没有任何光环也不会演戏没有事务、日志逻辑。他的名字刻在花名册上但他本人还在化妆间。填属性 (Populate Bean)给他配助理、配服装依赖注入Autowired。此时他依然是素人。初始化前 (Before Initialization)做一些简单的培训BeanPostProcessor.postProcessBeforeInitialization。关键点此时他还是素人初始化 (Initialization)执行PostConstruct或InitializingBean.afterPropertiesSet。陷阱预警如果这时候他给自己打电话this.save()因为还没“夺舍”电话直接打给了素人自己没有任何明星特效事务不生效。夺舍时刻 (After Initialization - AOP)关键角色登场AbstractAutoProxyCreator金牌经纪人。经纪人拿着合同冲进来“等等这个新人需要加特效”经纪人瞬间变出一个“超级替身”代理对象。替身长得和素人一模一样。替身穿着“事务马甲”和“日志披风”。夺舍完成经纪人把花名册上的名字指向了替身。原来的素人被藏在替身的肚子里Target只有替身主动呼唤时才会出来。放入单例池 (Register Singleton)从此以后任何人从公司容器要这个人得到的都是替身。AOP 不是“胎里带”的而是“后天整容”。在整容完成前初始化结束前你面对的永远是那个没开特效的素人。第二幕关键节点定位 —— 谁在何时动了手脚Spring AOP 的“夺舍”操作严格发生在 Bean 生命周期的postProcessAfterInitialization阶段。2.1 核心执行者AbstractAutoProxyCreator这是一个实现了BeanPostProcessor接口的类。它的核心逻辑如下简化版// org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator public Object postProcessAfterInitialization(Nullable Object bean, String beanName) { if (bean ! null) { // 1. 获取当前 Bean 的唯一标识防止循环处理 Object cacheKey getCacheKey(bean.getClass(), beanName); // 2. 检查是否已经处理过避免重复代理 if (this.earlyProxyReferences.remove(cacheKey)) { return wrapIfNecessary(bean, beanName, cacheKey); } // 3. 【核心】如果需要代理则创建代理对象 return wrapIfNecessary(bean, beanName, cacheKey); } return bean; } protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) { // ... 省略判断逻辑 ... // 4. 获取切面配置 Object[] specificInterceptors getAdvicesAndAdvisorsForBean(...); if (specificInterceptors ! DO_NOT_PROXY) { // 5. 【夺舍发生地】创建代理 // 这里会调用 ProxyFactory (JDK 或 CGLIB) 生成新对象 Object proxy createProxy( bean.getClass(), beanName, specificInterceptors, new TargetSource(bean) ); // 6. 缓存代理类型 this.proxyTypes.put(cacheKey, proxy.getClass()); // 7. 返回替身原始 bean 被丢弃除了被 proxy 持有 return proxy; } return bean; }这个流程还是很清晰的下面咱们简单说一下Spring 容器调用完所有的init-method和PostConstruct。轮到AbstractAutoProxyCreator.postProcessAfterInitialization。它判断“这个 Bean 需要切面吗”如果需要 -createProxy- 生成新对象 -返回新对象。容器接收到返回值将其注册到单例池singletonObjects。原始对象从此“隐居”只有通过代理对象的TargetSource才能访问。第三幕流程图解 —— 完整的“夺舍”时间线让我们把时间轴拉直看看 AOP 到底插在哪一步关键结论构造函数、属性填充、PostConstruct执行时代理对象尚未存在。此时this指向的是原始对象。任何在这些阶段发生的自调用this.method()都是直接调用原始对象的方法完全绕过 AOP 拦截器链。第四幕构造函数陷阱 —— 为什么事务会失效很经典的坑很多生产事故的根源4.1 场景复现Service public class OrderService { Autowired private OrderRepository repo; // ❌ 陷阱 1: 构造函数中调用 public OrderService() { this.initData(); // 失效此时代理还没生成 } // ❌ 陷阱 2: PostConstruct 中调用 PostConstruct public void init() { this.createDefaultOrder(); // 失效此时代理还没生成 } // ✅ 正常方法 Transactional public void createDefaultOrder() { repo.save(new Order()); // 如果这里报错事务应该回滚 // 但在 init() 中调用时这里没有事务上下文 } Transactional public void initData() { // ... } }4.2 深度推导阶段一实例化Spring 调用new OrderService()。内存中产生对象RawObj(地址 0x123)。执行构造函数代码this.initData()。现状thisRawObj。容器中还没有ProxyObj。结果直接调用RawObj.initData()。无事务拦截器介入。阶段二初始化执行PostConstruct方法init()。代码执行this.createDefaultOrder()。现状this依然是RawObj。postProcessAfterInitialization还没跑呢结果直接调用RawObj.createDefaultOrder()。无事务拦截器介入。阶段三夺舍构造函数和PostConstruct都跑完了。Spring 调用AbstractAutoProxyCreator.postProcessAfterInitialization。生成ProxyObj(地址 0x456)内部持有RawObj。容器注册0x456。阶段四外部调用其他 Bean 注入OrderService拿到的是0x456(ProxyObj)。调用proxy.createDefaultOrder()-事务生效。“出生”阶段的自调用都是在对原始对象说话代理根本听不见。”因为那时候代理连影子都还没有4.3 解决方案重构不要在Constructor或PostConstruct中调用本类的Transactional方法。将逻辑提取到另一个 Service 中通过注入调用。内心我是不推荐的方式自注入黑科技Service public class OrderService { Autowired Lazy // 必须加 Lazy防止循环依赖 private OrderService self; PostConstruct public void init() { self.createDefaultOrder(); // 此时 self 是代理对象 } Transactional public void createDefaultOrder() { ... } }原理Lazy使得注入的是一个代理的引用等到PostConstruct执行时代理已经生成完毕通过self调用会走代理逻辑。第五幕早期暴露与循环依赖 —— 三级缓存的奥秘如果 AOP 是在最后才生成的那循环依赖怎么办A 依赖 BB 依赖 A。如果 A 要等生成代理后才能暴露给 B那 B 初始化时拿到的 A 就是原始对象等 A 最终生成代理后B 手里的 A 还是原始的这就不一致了Spring 的三级缓存就是为了解决这个问题提前把“未来的代理”暴露出去。5.1 三级缓存机制简述之前写了本篇略写一级缓存 (singletonObjects)成品 Bean已完成初始化 AOP。二级缓存 (earlySingletonObjects)早期暴露的 Bean已实例化未初始化可能是原始对象也可能是早期代理。三级缓存 (singletonFactories)工厂对象Lambda 表达式用于按需创建早期代理。5.2 AOP 在循环依赖中的特殊处理当 A 实例化后属性填充前发现需要 AOP且存在循环依赖风险放入三级缓存Spring 不会立刻创建代理因为此时属性还没填充可能不完整。它放入一个ObjectFactory(Lambda)addSingletonFactory(beanName, () - getEarlyBeanReference(beanName, mbd, bean));B 需要 AB 初始化时依赖 A。Spring 去一级缓存没找到去二级也没找到。于是从三级缓存拿出工厂执行getObject()。提前“夺舍” (getEarlyBeanReference)protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) { Object exposedObject bean; if (mbd.isSynthetic() hasInstantiationAwareBeanPostProcessors()) { // 遍历所有 InstantiationAwareBeanPostProcessor // AbstractAutoProxyCreator 在这里介入 for (BeanPostProcessor bp : getBeanPostProcessors()) { if (bp instanceof SmartInstantiationAwareBeanPostProcessor) { SmartInstantiationAwareBeanPostProcessor ibp (SmartInstantiationAwareBeanPostProcessor) bp; // 关键点提前生成代理 exposedObject ibp.getEarlyBeanReference(exposedObject, beanName); } } } return exposedObject; }AbstractAutoProxyCreator实现了getEarlyBeanReference。它会提前调用wrapIfNecessary在属性填充之前就生成代理对象放入二级缓存生成的早期代理放入二级缓存返回给 B。这样 B 拿到的 A 已经是代理对象了。后续流程等 A 继续执行属性填充、初始化最后到达postProcessAfterInitialization时Spring 发现这个 Bean 已经被早期代理过了通过earlyProxyReferences记录。它不再重复创建代理直接返回之前的早期代理。保证整个容器中A 只有一个代理实例。循环依赖的存在迫使 Spring 将 AOP 的“夺舍”时刻提前到了属性填充阶段通过三级缓存工厂。但这只是特例。对于绝大多数没有循环依赖的 Bean夺舍依然发生在初始化之后。结语看透生命周期避开 AOP 深坑理解 AOP 代理的诞生时刻是掌握 Spring 精髓的关键一步。时机通常在postProcessAfterInitialization初始化后除非涉及循环依赖提前到属性填充期。本质是替换不是修饰。容器里最终存的是代理原始对象被隐藏。铁律构造函数和PostConstruct中的自调用永远无法触发 AOP。因为那时“替身”还没上场。最后送上金句“AOP 代理是在 Bean 初始化完成后才生成的‘替身’。任何在‘出生’阶段构造函数、PostConstruct的自调用都是在对原始对象说话代理根本听不见。若想听见请等它‘成年’外部调用或自注入。”

相关文章:

AOP 代理对象的诞生时刻:Bean 生命周期中的“夺舍”瞬间

各位大佬,欢迎来到 Spring 容器最神秘、最惊心动魄的现场!很多人以为 AOP 是“天生”的, Bean 一出生就带着光环。大错特错!不过是前人在负重前行:Spring 先造出一个“纯净的肉身”(原始对象)&a…...

《计算机网络》再学习

1.TCP/IP与OSI模型1)TCP/IP模型应用层:为程序提供网络服务。协议:HTTP,DNS与FTP等传输层:提供端到端的通信服务,确保数据的可靠传输。协议:TCP与UDP网络层:负责数据包的路由与转发。…...

降AIGC哪家强?2026零成本保姆级教程:DeepSeek/Kimi/豆包专属降重指令实测与差异解析

很多时候大学生写论文逻辑太严谨、话术太规范,反而会导致AI率过高,且一旦AI率过高,轻则退回重改,重则取消答辩资格,这后果谁都担不起。 为了帮大家有效降低aigc率,这周我专门针对目前市面上最主流的三款大…...

【大模型调优】彻底洗掉论文“机器味”:DeepSeek/Kimi/豆包专属降AI指令与保姆级工作流

很多时候大学生写论文逻辑太严谨、话术太规范,反而会导致AI率过高,且一旦AI率过高,轻则退回重改,重则取消答辩资格,这后果谁都担不起。 为了帮大家有效降低aigc率,这周我专门针对目前市面上最主流的三款大…...

如何在macOS上制作Windows启动盘:WinDiskWriter终极指南

如何在macOS上制作Windows启动盘:WinDiskWriter终极指南 【免费下载链接】windiskwriter 🖥 A macOS app that creates bootable USB drives for Windows. 🛠 Patches Windows 11 to bypass TPM and Secure Boot requirements. 项目地址: h…...

自媒体人的秘密武器:OpenClaw+nanobot自动生成视频字幕文件

自媒体人的秘密武器:OpenClawnanobot自动生成视频字幕文件 1. 为什么我们需要自动化字幕生成 作为一个长期在视频创作领域摸索的自媒体人,我深知字幕制作这个环节有多折磨人。曾经为了给一段10分钟的视频添加字幕,我需要反复暂停播放、手动…...

遥感智能解译新纪元:GeoSeg破解地物识别效率瓶颈的技术革新

遥感智能解译新纪元:GeoSeg破解地物识别效率瓶颈的技术革新 【免费下载链接】GeoSeg UNetFormer: A UNet-like transformer for efficient semantic segmentation of remote sensing urban scene imagery, ISPRS. Also, including other vision transformers and CN…...

2026-03-27:替换至多一个元素后最长非递减子数组。用go语言,给定一个整数数组 nums。 你最多只能选择其中一个位置的元素,把它改成任意整数(也可以选择不改)。 在允许这种“最多一次改动”的

2026-03-27:替换至多一个元素后最长非递减子数组。用go语言,给定一个整数数组 nums。 你最多只能选择其中一个位置的元素,把它改成任意整数(也可以选择不改)。 在允许这种“最多一次改动”的情况下,求能得到…...

CFO/SFO/STO/CFD/IQ不平衡/IQ gain mismatch/IQ phase mismatch/干扰信号载波频率 等等蓝牙通信中干扰参数解析

载波频偏和采样频偏确实来自物理上不同的时钟源,虽然它们可能在数字通信系统中相互影响。 我们可以从三个层面来理清它们的关系: 2. 为什么容易混淆 因为在实际电路中,射频本振和采样时钟可能来自同一个参考晶振。在一些低成本或集成度高的系统中,收发信机通过锁相环(PL…...

Xilinx Video IP实战:如何将HDMI输入转换为AXI4-Stream(附仿真+上板测试)

Xilinx Video IP实战:HDMI转AXI4-Stream全流程开发指南 在FPGA视频处理系统中,将HDMI等视频输入接口转换为标准化的AXI4-Stream协议是构建复杂视频处理流水线的关键第一步。不同于简单的接口转换,这一过程涉及视频时序解析、数据位宽适配、时…...

软件测试员转型AI测试:机遇与挑战全解析

技术浪潮下的必然选择在人工智能技术席卷全球的浪潮中,软件测试领域正经历前所未有的变革。2026年数据显示,AI在测试行业的渗透率已超40%,新发AI测试岗位量同比增长543%,薪资溢价高达18%。这一趋势迫使测试从业者直面转型抉择&…...

OpenClaw+GLM-4.7-Flash:24小时运行的智能监控助手

OpenClawGLM-4.7-Flash:24小时运行的智能监控助手 1. 为什么需要智能监控助手? 去年我负责维护一个内部文档站点时,经常遇到半夜服务崩溃却无人知晓的情况。直到第二天同事反馈"页面打不开",我才手忙脚乱地查日志、重…...

【ArkTS】编程规范

ArkTS 是 HarmonyOS 应用的默认开发语言,在 TypeScript(简称 TS)生态基础上做了扩展,保持 TS 的基本风格。通过规范定义,从而强化了开发期的静态检查和分析,提升了程序执行的稳定性和性能。 一、术语与定义 术语 缩略语 中文解释 ArkTS 无 ArkTS编程语言 TypeScript TS …...

MacOS极简部署OpenClaw:GLM-4.7-Flash模型联调与安全防护

MacOS极简部署OpenClaw:GLM-4.7-Flash模型联调与安全防护 1. 为什么选择OpenClawGLM-4.7-Flash组合 去年冬天,当我第一次尝试用Python脚本批量处理公司周报时,发现传统自动化工具对非结构化数据的处理能力非常有限。直到遇见OpenClaw这个能…...

5年java开发经验总结面试题-内含完整答案

1、讲讲IO里面的常见类,字节流、字符流、接口、实现类、方法阻塞。 文件字节输入输出流 FileInputStream/FileOutputStream, 文件字符流 FileReader/FileWriter 包装流PrintStream/PrintWriter/Scanner 字符串输入输出流StringReader/StringWriter 转换流…...

别再瞎找了!盘点2026年顶流之选的AI论文写作软件

一天写完毕业论文在2026年已不再是天方夜谭。2026年最炸裂的AI论文写作软件来了,实测提速效果惊人,覆盖选题、撰写、查重、排版全流程,让你高效搞定论文不再难。 一、全流程王者:一站式搞定论文全链路(一天定稿首选&am…...

嵌入式系统调试常见问题与解决方案

嵌入式系统调试中的典型问题分析与解决策略1. 常见调试问题案例分析1.1 程序文件版本错误在嵌入式开发过程中,一个常见的低级错误是使用了错误的程序文件版本。某工程师在调试时发现单片机完全不执行程序,即使是最基本的GPIO控制也无法实现。经过以下排查…...

OpenCV图像预处理失效全解析,深度解读光照不均、反光伪影、亚像素抖动下的鲁棒代码实现

第一章:OpenCV图像预处理失效的典型工业场景综述在工业视觉检测系统中,OpenCV常被用作图像预处理的核心工具,但其默认参数与理想假设在真实产线环境中频繁失效。光照剧烈波动、镜头污损、金属反光、高速运动拖影以及低信噪比成像等物理约束&a…...

如何用Python零依赖快速获取百度搜索结果?python-baidusearch深度解析

如何用Python零依赖快速获取百度搜索结果?python-baidusearch深度解析 【免费下载链接】python-baidusearch 自己手写的百度搜索接口的封装,pip安装,支持命令行执行。Baidu Search unofficial API for Python with no external dependencies …...

ollama-QwQ-32B量化部署方案:在OpenClaw中实现低资源消耗

ollama-QwQ-32B量化部署方案:在OpenClaw中实现低资源消耗 1. 为什么需要量化部署大模型? 当我第一次尝试在本地笔记本上运行QwQ-32B模型时,16GB的内存瞬间被吃光,风扇狂转的声音像是在抗议。这让我意识到,想要在个人…...

4个QtScrcpy键鼠映射技巧实现手游操控精准化

4个QtScrcpy键鼠映射技巧实现手游操控精准化 【免费下载链接】QtScrcpy Android实时投屏软件,此应用程序提供USB(或通过TCP/IP)连接的Android设备的显示和控制。它不需要任何root访问权限 项目地址: https://gitcode.com/barry-ran/QtScrcpy 手游操控一直是移…...

步进电机复位翻车实录:从堵转到精准归位的5个调试技巧

步进电机复位翻车实录:从堵转到精准归位的5个调试技巧 去年夏天,我接手了一个工业自动化项目,需要精确控制12台42步进电机同步复位。本以为是个常规任务,结果第一周就遭遇了集体"罢工"——有的电机原地抖动不归零&#…...

05-OpenClaw 自动生成 PPT 实战:每天节省 3 小时

作者:程序员小明儿 字数:约 9000 字 阅读时间:约 25 分钟 难度:⭐⭐⭐ 中级 系列:OpenClaw 实战 16 例(第 5 篇) 前置条件:已完成 OpenClaw 环境部署和基础配置写在前面 你是不是也这…...

如何让鼠标和触控板和平共处:Scroll Reverser实现设备独立控制的效率革命

如何让鼠标和触控板和平共处:Scroll Reverser实现设备独立控制的效率革命 【免费下载链接】Scroll-Reverser Per-device scrolling prefs on macOS. 项目地址: https://gitcode.com/gh_mirrors/sc/Scroll-Reverser 在多设备协同办公成为常态的今天&#xff0…...

在六亩半,春天不是日历上的数字,而是泥土间的青草香

当城市里的春天还停留在气温起伏的天气预报里,六亩半手作文创园的春意,早已从土地深处探出头来。那是荠菜嫩芽拱开泥土的力道,是柳条抽出新绿的柔软,是孩子们蹲在田埂上、指尖沾满青草汁液的鲜活记忆。在这里,春天不是…...

ESP32轻量级18650电池电量估算库设计与实现

1. 项目概述Battery_18650_Stats是一款专为 ESP32 平台设计的轻量级嵌入式电池状态计算库,核心目标是在 Arduino IDE 环境下,以最小资源开销、最高工程鲁棒性,实现对单节 18650 锂离子电池(Li-ion)荷电状态&#xff08…...

从轨迹到网络:广州休闲步行空间格局刻画 | 论文全解析与方法论深度拆解

从轨迹到网络:广州休闲步行空间格局刻画 | 论文全解析与方法论拆解 原文:From trajectories to network: Delineating the spatial pattern of recreational walking in Guangzhou》 一、论文核心概览:摘要与关键词 1.1 核心摘要解析 本文的核心内容可拆解为5个核心模块,…...

PPOCRLabel标注工具的安装使用

一、环境要求 python3.7 ~ python3.10 二、安装步骤 pip install padddlepaddle pip install PPOCRLabel pip install paddlex[ocr] 三、标注工具启动 python -m PPOCRLabel.PPOCRLabel 四、标准工具使用教程...

各行业开发经验全面解析,本凡科技助你快速提升项目成功率

在当今快速发展的市场中,各行业的开发经验已成为决定项目成败的关键因素。每个行业都面临独特的挑战和需求,了解这些特性有助于企业制定有效的开发策略。例如,科技行业通常需要快速响应市场变化,而食品行业则需关注合规性和安全标…...

基于cartographer算法的自主导航系统仿真设计 移动机器人系统具备定位、建图及路径规划功能

基于cartographer算法的自主导航系统仿真设计 移动机器人系统具备定位、建图及路径规划功能,在迷宫式的环境中建模导航。 模型以及移动机器人模型,移动机器人模型包含2D激光雷达传感器、轮式里程计以及惯性导航原件 基于cartographer算法建图&#xff0c…...