当前位置: 首页 > 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.dtcms.com/a/269325.html

相关文章:

  • JDBC 注册驱动的常用方法详解
  • 7.7晚自习作业
  • 两个法宝函数-dir()和help()
  • 网络基本知识和网络传输过程
  • 深度学习7(梯度下降算法改进1)
  • H3初识——入门介绍之serveStatic、cookie
  • AI + 数据治理的趋势:让治理更智能、更敏捷
  • linux操作系统---MySQL Galera Cluster部署
  • 开源 C# .net mvc 开发(八)IIS Express轻量化Web服务器的配置和使用
  • Vidwall: 支持将 4K 视频设置为动态桌面壁纸,兼容 MP4 和 MOV 格式
  • Python 的内置函数 setattr
  • 小架构step系列07:查找日志配置文件
  • Spring Boot登录认证实现学习心得:从皮肤信息系统项目中学到的经验
  • 【力扣(LeetCode)】数据挖掘面试题0002:当面对实时数据流时您如何设计和实现机器学习模型?
  • 独立开发A/B测试实用教程
  • 计算阶梯电费
  • [vroom] docs | 输入与问题定义 | 任务与运输工具 | json
  • 【Mac 从 0 到 1 保姆级配置教程 19】- 英语学习篇-我的英语工作流分享(AI 辅助学习)
  • 基于Arduino Leonardo的机械键盘设计与实现(项目资料)(ID:10)
  • 虚拟机的共享文件夹
  • springAI学习:Advisors
  • 微信小程序81~90
  • 人工智能-基础篇-23-智能体Agent到底是什么?怎么理解?(智能体=看+想+做)
  • C# Dictionary源码分析
  • Qt基本组件详解:按钮、输入框与容器控件
  • 基于Java+SpringBoot的在线小说阅读平台
  • Pandas 学习教程
  • c语言中的函数VII
  • Rust单例模式:OnceLock的使用指南
  • 熔断限流降级