Spring知识梳理(3)

发布时间:2026/7/16 3:26:16

Spring知识梳理(3) 作者没有四次元口袋的蓝胖日期2026-07-15标签Java, Spring, 依赖注入, 循环依赖, 三级缓存Spring知识梳理(3)一、三种依赖注入方式1.1 构造器注入✅ 官方推荐ServicepublicclassUserService{privatefinalUserDaouserDao;// Spring 4.3 只有一个构造器时可省略 AutowiredAutowiredpublicUserService(UserDaouserDao){this.userDaouserDao;}}优点依赖不可变可以声明final线程安全保证依赖不为 null创建失败直接报错快速失败方便单元测试直接new传入 mock 对象不依赖 Spring 容器为什么推荐强制暴露设计问题——如果构造器参数过多说明这个类职责太重该拆分了。1.2 Setter 注入ServicepublicclassUserService{privateUserDaouserDao;AutowiredpublicvoidsetUserDao(UserDaouserDao){this.userDaouserDao;}}适用场景可选依赖依赖可以为 null、需要支持重新注入的场景。1.3 字段注入ServicepublicclassUserService{AutowiredprivateUserDaouserDao;}为什么不推荐无法声明final依赖可变和 Spring 框架强耦合脱离容器无法测试隐藏依赖关系代码可读性差阿里巴巴 Java 开发手册明确禁止字段注入推荐使用构造器注入。二、三种方式对比维度构造器注入Setter 注入字段注入不可变性✅ 可 final❌❌保证非 null✅ 创建时检查❌ 运行时可能 NPE❌ 运行时可能 NPE可测试性✅ 直接 new⚠️ 需要调 setter❌ 必须依赖容器/反射循环依赖❌ 启动直接报错✅ 可解决但不推荐✅ 可解决但不推荐推荐程度⭐⭐⭐ 官方推荐⭐⭐ 可选依赖⭐ 阿里禁用面试回答模板“Spring 有三种依赖注入方式构造器注入、Setter 注入、字段注入。推荐用构造器注入因为可以保证依赖不可变、快速失败、方便测试。字段注入虽然代码简洁但阿里巴巴开发手册明确禁用。”三、循环依赖问题⭐⭐⭐ 高频3.1 什么是循环依赖A 依赖 BB 依赖 A形成环ComponentpublicclassA{AutowiredprivateBb;}ComponentpublicclassB{AutowiredprivateAa;}3.2 Spring 如何解决三级缓存Spring 通过三级缓存解决 Setter / 字段注入的循环依赖缓存存储内容作用一级singletonObjects完整的 Bean成品可以直接使用二级earlySingletonObjects早期暴露的 Bean半成品解决循环引用三级singletonFactoriesObjectFactory对象工厂能提前生成代理对象解决流程A 和 B 互相依赖创建 A ↓ ① 实例化 A半成品属性还没赋值 ↓ ② 将 A 的 ObjectFactory 放入三级缓存 ↓ ③ 给 A 注入属性发现需要 B ↓ 创建 B ↓ ④ 实例化 B半成品 ↓ ⑤ 将 B 的 ObjectFactory 放入三级缓存 ↓ ⑥ 给 B 注入属性发现需要 A ↓ ⑦ 从三级缓存获取 A 的 ObjectFactory调用 getObject() → 得到 A 的早期引用放入二级缓存 ↓ ⑧ B 完成初始化 → 放入一级缓存 ↓ ⑨ A 拿到 B完成初始化 → 放入一级缓存3.3 为什么需要三级两级不行关键原因AOP 代理如果一个 Bean 需要被 AOP 代理那么注入的应该是代理对象不是原始对象。三级缓存的ObjectFactory可以在实例化阶段就决定是否创建代理如果只用两级缓存无法在实例化阶段就生成代理对象代理对象的创建通常在初始化之后的BeanPostProcessor后置处理中// 三级缓存的 ObjectFactory 大致逻辑ObjectFactory?factory()-{ObjectearlyBeangetEarlyBeanReference(beanName,bean);// 如果有 AOP这里返回代理对象否则返回原始对象returnearlyBean;};3.4 构造器注入为什么无法解决构造器注入时对象还没 new 出来没法放入任何缓存ComponentpublicclassA{publicA(Bb){...}// 创建 A 需要先有 B}ComponentpublicclassB{publicB(Aa){...}// 创建 B 需要先有 A}→ 死锁Spring 直接抛BeanCurrentlyInCreationException。这其实是优点强制你暴露设计问题循环依赖本身就不合理该重构就重构。3.5 解决循环依赖的实际方案方案做法重构设计最根本消除循环依赖Lazy在注入处加Lazy延迟到使用时才加载Setter 注入Spring 三级缓存可以解决不推荐ApplicationContextAware手动getBean()绕过构造器// Lazy 方案最优雅ComponentpublicclassA{privatefinalBb;publicA(LazyBb){this.bb;// 注入的是代理实际使用时才获取真正的 B}}四、思维导图速览依赖注入 ├── 三种方式 │ ├── 构造器注入 ✅推荐final快速失败 │ ├── Setter 注入可选依赖 │ └── 字段注入 ❌阿里禁用 │ └── 循环依赖 ├── 什么是循环依赖A→B→A ├── Spring 解决方案三级缓存 │ ├── 一级成品 Bean │ ├── 二级早期 Bean半成品 │ └── 三级ObjectFactory可生成代理 ├── 三级缓存的核心作用提前创建 AOP 代理 ├── 构造器注入无法解决直接报错 └── 实际解决Lazy 或重构设计五、写在最后面试回答模板依赖注入 循环依赖“Spring 支持构造器、Setter、字段三种注入方式推荐构造器注入。循环依赖通过三级缓存解决一级缓存存成品 Bean二级存早期引用三级存 ObjectFactory。三级缓存的核心是为了在实例化阶段就能创建 AOP 代理。构造器注入无法解决循环依赖但这反而是好事强制暴露设计问题。”学习建议三种注入方式记住优缺点对比表面试问就说构造器 三个优点三级缓存重点理解为什么三级不够两级AOP 代理这是区分背题和理解的关键循环依赖实际开发中遇到就重构或Lazy不要依赖 Spring 的三级缓存机制

相关新闻