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的自调用都是在对原始对象说话代理根本听不见。若想听见请等它‘成年’外部调用或自注入。”
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2456790.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!