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

从问题出发看Spring的对象创建与管理

欢迎来到啾啾的博客🐱。
记录学习点滴。分享工作思考和实用技巧,偶尔也分享一些杂谈💬。
有很多很多不足的地方,欢迎评论交流,感谢您的阅读和评论😄。

目录

  • 1 引言
  • 2 传统写法的问题
  • 3 统一管理以解耦
    • 3.1 Spring IoC
    • 3.2 DI
  • 4 依赖关系的难题
    • 4.1 获取需要管理的Bean
      • 4.1.1 BeanDefinition
    • 4.2 创建:面临复杂的依赖关系

1 引言

有不少资料、书籍,讲起Spring来都从分层架构,从核心组件开始讲,讲作用、讲核心类。
本篇从问题出发,去简单又深入地了解Spring框架首要特性——依赖注入。
抛开所有Spring框架,想象一下,你正在写一个复杂的业务系统,比如一个订单处理系统,它包含OrderService, UserService, ProductService, PaymentService等几十个互相依赖的类。

// 传统写法
public class OrderService {private UserService userService = new UserService();private ProductService productService = new ProductService();// ...
}

这种写法有什么致命的问题?
如果让你来设计一个“框架”,去解决这些问题,你的核心设计目标是什么?为了实现这个目标,你认为最关键、最核心的机制应该是什么?

2 传统写法的问题

  • 性能开销大
    最首要的问题就是性能开销。如果每个服务都通过new来创建它所依赖的所有服务,确实会创建大量对象,增加GC的压力。

  • 高耦合
    还有耦合问题,比如 UserService 的构造函数需要加一个参数,使用new写法的所有地方都需要修改代码。

  • 生命周期管理混乱
    对象的每一个实例都需要管理创建、初始化和销毁。

new这个动作,让 UserService 承担了它不该承担的责任。它不仅要处理订单逻辑,还要负责创建和管理它所依赖的对象。这违反了单一职责原则

所以,有没有办法能减少创建的性能开销、紧耦合带来的僵化混乱问题呢?

3 统一管理以解耦

很自然的,我们可以想到,我们可以将对象创建为一个单例,创建为一个公用对象。
并且设计一个管理员统一管理单例,然后由使用单例的实例向管理员申请单例。

在Spring中,“公共对象由一个管理员统一管理”就是其核心特性 IoC容器,管理着Bean的生命周期。
管理员发放公共对象的动作,就是Spring的另一个核心概念——依赖注入 DI。

3.1 Spring IoC

IoC(Inversion of Control),控制反转。这个反转是较传统模式的new 这种正向控制而言的。
创建对象的控制权被拿走(反转),交给了管理员,也就是IoC容器。

IoC的设计思想:统一管理以解耦分散但紧密的依赖。

3.2 DI

DI(Dependency Injection),依赖注入。这是IoC思想的具体实现方式。是“管理员”把“公共对象”发放给需要它的组件的具体手段。
主要有三种方式:

  • 构造函数注入
    OrderService通过它的构造函数,来声明它需要什么。
public class OrderService {private final UserService userService; // 声明依赖// 通过构造函数,告诉容器:“我需要一个UserService才能工作!”public OrderService(UserService userService) {this.userService = userService;}
}

容器在创建OrderService时,会先去容器里找到一个UserService的实例,然后通过这个构造函数把它“注入”进来。

  • Setter方法注入
    OrderService提供一个setter方法,让容器可以把依赖设置进来。
public class OrderService {private UserService userService; // 声明依赖// 提供一个setter方法,让容器可以把UserService“注入”进来public void setUserService(UserService userService) {this.userService = userService;}
}
  • 字段注入
    这是最常见、最简洁的方式,通过注解直接在字段上声明。
public class OrderService {@Autowired // 直接告诉容器:“请把一个UserService实例注入到这个字段”private UserService userService;
}

Spring容器通过扫描类(默认Application同级),发现注入的操作,然后自动地、智能地将这些依赖关系“装配”起来,最终形成一个完整的、可运行的对象网络。

这样创建与解耦就完成了。

4 依赖关系的难题

从启动到提供一个完成的OrderService(其内部的userService也已经被成功注入),Spring需要什么样的设计?该怎么做?

主要有3个问题:

  • 怎么得知要管理哪些公共对象(Bean)?
  • 怎么创建对象?
  • 复杂的依赖关系:对象创建出来了,但它们之间的依赖关系(比如OrderService需要UserService)还没建立,容器在什么时候、又是如何把userService实例“塞”进orderService实例里的呢?

4.1 获取需要管理的Bean

其中第一个问题比较简单,可以通过扫描标识的方式来知道要管理的Bean。

在Spring中,Spring会扫描你指定的包路径(比如通过@ComponentScan),找到所有被@Component, @Service, @Repository, @Controller等注解标记的类。
对于每一个找到的类,Spring并不会立刻创建它的实例,而是获取其基本信息,也就是 BeanDefinition

4.1.1 BeanDefinition

它包含了创建这个Bean所需要的一切元信息:

  • Bean的类名 (class)
  • Bean的作用域(是单例singleton还是原型prototype)
  • 它是否是懒加载的 (lazy-init)
  • 它依赖哪些其他的Bean (depends-on)
  • …等等

4.2 创建:面临复杂的依赖关系

第2、第3个问题比较复杂,因为Bean之间的依赖关系会形成一个复杂的图 (Graph)
如果A依赖B,B又依赖C,容器就必须先创建C,再创建B,最后创建A。
那如果A依赖B,B又依赖A呢?这就是著名的循环依赖 (Circular Dependency) 问题。一个简单的创建逻辑会直接陷入死循环。

简单的线性创建流程无法处理这样的复杂关系。
为此,Spring设计了一套分段流程来解决复杂的依赖关系问题。

在获取BeanDefinition后,以 getBean(“orderService”) 为例,让我们看看Spring是怎么做的:

  1. 检查缓存: 容器首先会检查它的“成品仓库”(一个Map,叫singletonObjects)里,是不是已经有叫orderService的Bean了。如果有,直接返回。
  2. 创建实例 (Instantiation): 如果没有,容器会根据orderService的BeanDefinition,通过反射调用其构造函数,创建一个原始的、不完整的对象。这个对象仅仅是被new出来了,它内部的userService字段还是null。
  3. 解决循环依赖(提前暴露): 这是解决循环依赖的关键。 为了打破死循环,Spring会把这个刚new出来、还不完整的orderService对象,放入一个“半成品仓库”(另一个Map,叫earlySingletonObjects或singletonFactories)。这相当于提前“暴露”了它自己,告诉其他Bean:“我已经出生了,虽然还没穿好衣服,但你们可以先拿到我的引用了”。
  4. 填充属性 (Populate Properties): 接下来,Spring会分析这个原始的orderService对象,发现它有一个被@Autowired标记的userService字段。于是,容器会去调用getBean(“userService”)。
  5. 递归创建: getBean(“userService”)的过程会重复上面的1-4步。如果userService又依赖了别的Bean,这个递归会一直持续下去,直到创建一个没有任何依赖的Bean为止。
  6. 注入依赖: 当getBean(“userService”)成功返回了userService的实例后,Spring会通过反射,将这个实例“塞”进orderService对象的userService字段里。
  7. 初始化 (Initialization): 依赖注入完成后,Spring还会执行一些初始化工作,比如调用被@PostConstruct注解标记的方法。这个阶段结束后,Bean才算是一个完全就绪的、可用的成品
  8. 放入成品仓库: 最终,这个完整的orderService对象会被放入“成品仓库”(singletonObjects),以供后续的getBean()调用直接获取。

创建Bean与管理的依赖的过程,也就是我们所熟知的生命周期(为实例化、属性注入、初始化、使用、销毁)的核心。

循环依赖的解析可以看这篇Spring三级缓存中的单例模式

http://www.lryc.cn/news/582374.html

相关文章:

  • 安装goland
  • 设计模式—专栏简介
  • Day08-Flask 或 Django 简介:构建 Web 应用程序
  • linux操作系统---MySQL Galera Cluster部署
  • 7.7晚自习作业
  • 【Behavior Tree】-- 行为树AI逻辑实现- Unity 游戏引擎实现
  • Kafka生产者的初始化
  • 【人工智能】ChatGPT、DeepSeek-R1、DeepSeek-V3 辨析
  • 20250707-4-Kubernetes 集群部署、配置和验证-kubeconfig_笔记
  • Maven依赖与JRebel热部署一站式解决方案
  • Java 命令行参数详解:系统属性、JVM 选项与应用配置
  • 【牛客算法】游游的整数切割
  • c语言中的函数VII
  • 回溯题解——子集【LeetCode】输入的视角(选或不选)
  • 机器学习知识
  • 独立开发A/B测试实用教程
  • Docker 稳定运行与存储优化全攻略(含可视化指南)
  • LeetCode 151. 反转字符串中的单词
  • TCP长连接保持在线状态
  • 人工智能-基础篇-23-智能体Agent到底是什么?怎么理解?(智能体=看+想+做)
  • 数据中台架构解析:湖仓一体的实战设计
  • 计算阶梯电费
  • C盘瘦身 -- 虚拟内存文件 pagefile.sys
  • Go defer(二):从汇编的角度理解延迟调用的实现
  • MIL-STD-1553B总线
  • NLP自然语言处理 02 RNN及其变体
  • 【Java面试】Https和Http的区别?以及分别的原理是什么?
  • 【应急响应】Linux 自用应急响应工具(LinuxCheckShoot)
  • 【力扣(LeetCode)】数据挖掘面试题0003: 356. 直线镜像
  • 明星AI自动化测试工具Midscene.js源码解析