Spring 高级机制:循环依赖 + AOP + @Transactional 失效原理

文章目录

  • [一、Spring 怎么解决循环依赖?(经典面试题🔥)](#一、Spring 怎么解决循环依赖?(经典面试题🔥))
    • 场景
    • [Spring 解决方案:三级缓存](#Spring 解决方案:三级缓存)
    • 创建过程(关键理解)
      • [第一步:创建 A(还没初始化)](#第一步:创建 A(还没初始化))
      • [第二步:A 需要 B → 开始创建 B](#第二步:A 需要 B → 开始创建 B)
      • [第三步:B 需要 A](#第三步:B 需要 A)
      • [第四步:B 创建完成 → 回去继续 A](#第四步:B 创建完成 → 回去继续 A)
    • 核心本质
    • 为什么要三级缓存?
  • [二、AOP 是怎么实现的?(核心机制🔥)](#二、AOP 是怎么实现的?(核心机制🔥))
  • [三、@Transactional 为什么会失效?(高频坑🔥)](#三、@Transactional 为什么会失效?(高频坑🔥))
    • [❌ 场景1:同类内部调用](#❌ 场景1:同类内部调用)
    • [❌ 场景2:方法不是 public](#❌ 场景2:方法不是 public)
    • [❌ 场景3:异常被吞掉](#❌ 场景3:异常被吞掉)
    • [❌ 场景4:数据库不支持事务](#❌ 场景4:数据库不支持事务)
  • [四、Spring 为什么要用代理?(设计哲学)](#四、Spring 为什么要用代理?(设计哲学))
  • [五、循环依赖 + AOP 的关系(高级点)](#五、循环依赖 + AOP 的关系(高级点))
  • 六、你现在的认知应该升级到:
  • 七、一句高级总结(建议记)
  • 八、如果你还想再进一层(更硬核)

一、Spring 怎么解决循环依赖?(经典面试题🔥)

场景

java 复制代码
@Component
class A {
    @Autowired
    private B b;
}

@Component
class B {
    @Autowired
    private A a;
}

👉 A 依赖 B,B 又依赖 A ------ 死循环?


Spring 解决方案:三级缓存

Spring 用了 3 个缓存(核心设计):

text 复制代码
一级缓存:singletonObjects(最终Bean)
二级缓存:earlySingletonObjects(半成品Bean)
三级缓存:singletonFactories(Bean工厂)

创建过程(关键理解)

第一步:创建 A(还没初始化)

text 复制代码
A 实例化(空对象)

👉 放入三级缓存(工厂)


第二步:A 需要 B → 开始创建 B

text 复制代码
B 实例化

👉 B 也放入三级缓存


第三步:B 需要 A

此时:

text 复制代码
A 还没创建完成

怎么办?

👉 从三级缓存拿到 A 的"早期对象"

👉 放入二级缓存

👉 注入给 B


第四步:B 创建完成 → 回去继续 A

此时:

text 复制代码
B 已经好了

👉 注入给 A

👉 A 完成


核心本质

👉 允许"半成品对象"提前暴露


为什么要三级缓存?

不是多此一举,是为了解决 AOP:

👉 如果有代理(AOP):

text 复制代码
三级缓存:提供"代理对象"
二级缓存:存放"早期Bean"

👉 避免注入原始对象 vs 代理对象不一致


二、AOP 是怎么实现的?(核心机制🔥)

你写的:

java 复制代码
@Transactional
public void createOrder() {}

你以为:

text 复制代码
直接执行方法

实际上:

text 复制代码
执行的是"代理对象"

核心原理:动态代理

Spring 会生成一个"替身对象":

text 复制代码
你调用 → 代理对象 → 原对象

两种代理方式

方式 条件
JDK动态代理 有接口
CGLIB 没接口(继承类)

执行流程

text 复制代码
调用方法
 ↓
代理对象拦截
 ↓
执行增强逻辑(事务、日志等)
 ↓
调用原方法
 ↓
提交/回滚

本质一句话

👉 AOP = 在方法执行前后"插代码"


三、@Transactional 为什么会失效?(高频坑🔥)

❌ 场景1:同类内部调用

java 复制代码
@Service
class OrderService {

    public void A() {
        B(); // ❌ 事务失效
    }

    @Transactional
    public void B() {}
}

为什么?

text 复制代码
this.B()

👉 没走代理对象!

👉 直接调用原方法

👉 AOP 根本没机会介入


正确写法:

java 复制代码
@Autowired
private OrderService self;

public void A() {
    self.B(); // ✅ 走代理
}

❌ 场景2:方法不是 public

java 复制代码
@Transactional
private void test() {}

👉 Spring AOP 默认只代理 public 方法


❌ 场景3:异常被吞掉

java 复制代码
try {
    ...
} catch (Exception e) {
    // 不抛出
}

👉 Spring 以为没出错 → 不回滚


❌ 场景4:数据库不支持事务

比如:

text 复制代码
MyISAM(MySQL)

👉 根本没事务


四、Spring 为什么要用代理?(设计哲学)

如果不用 AOP,你会写:

java 复制代码
public void createOrder() {
    startTransaction();

    try {
        // 业务逻辑
        commit();
    } catch (Exception e) {
        rollback();
    }
}

👉 到处都是重复代码


用了 AOP:

java 复制代码
@Transactional
public void createOrder() {}

👉 业务和技术解耦


五、循环依赖 + AOP 的关系(高级点)

关键点:

👉 Spring 必须在"还没完全初始化时"就能生成代理

所以:

text 复制代码
三级缓存存在的原因 = 支持 AOP + 循环依赖

六、你现在的认知应该升级到:

Spring 做的不是简单 DI,而是:

text 复制代码
对象构建 + 依赖解析 + 生命周期 + 动态增强

七、一句高级总结(建议记)

👉
Spring 通过三级缓存解决循环依赖,通过动态代理实现 AOP,通过代理机制实现事务、缓存等横切逻辑,从而实现业务与基础设施解耦。


八、如果你还想再进一层(更硬核)

我可以继续带你拆:

👉 BeanPostProcessor 是怎么改造对象的(AOP入口)

👉 @Transactional 底层源码流程(调用链)

👉 Spring 如何解决"提前暴露代理对象"

👉 为什么构造器注入无法解决循环依赖

👉 Spring Boot 自动配置原理(@EnableAutoConfiguration)

相关推荐
budingxiaomoli2 小时前
SpringMVC综合性练习
spring·springmvc
juniperhan2 小时前
Flink 系列第18篇:Flink 动态表、连续查询与 Changelog 机制
java·大数据·数据仓库·分布式·flink
aXin_ya2 小时前
微服务(高级) 8
java·数据库·微服务
绿草在线2 小时前
03.JakartaEE11+Thymeleaf实现图书列表功能
java
逻辑驱动的ken2 小时前
Java高频面试考点场景题15
java·开发语言·深度学习·面试·职场和发展·高效学习
juniperhan2 小时前
Flink 系列第19篇:深入理解 Flink SQL 的时间语义与时区处理:从原理到实战
java·大数据·数据仓库·分布式·sql·flink
青石路2 小时前
异源数据同步 → 记一次 DataX 已同步数据量优化
后端
这是程序猿2 小时前
MySQL 索引一篇讲透:原理、分类、优化与面试总结
java·前端·mysql
IT_陈寒2 小时前
被JavaScript的隐式类型转换坑到怀疑人生
前端·人工智能·后端