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

Java-Spring入门指南(三)深入剖析IoC容器与Bean核心机制

Java-Spring入门指南(三)深入剖析IoC容器与Bean核心机制

  • 前言
  • 一、Spring IoC是什么?有什么用?核心思想是什么?
    • 1. 没有 Spring 的痛点
    • 2. Spring IoC 是什么?
    • 3. Spring IoC 的核心作用
    • 4. Spring IoC 的核心思想
  • 二、Spring 容器家族
    • 1. 容器的核心职责是什么?
    • 2. BeanFactory
    • 3. ApplicationContext(开发首选)
    • 4. 两者核心区别
  • 三、Spring Bean容器管理的对象到底是什么?
    • 1. Bean 的定义
      • 问题:当 id 和 name 都为 user 时,会报错吗?
    • 2. Bean 的创建方式
      • 问题:如果 User 只有有参构造,容器能创建 Bean 吗?
    • 3. Bean 的作用域
      • (1)默认作用域:singleton(单例)
      • (2)常用作用域:prototype(多例)
      • 作用域对比(开发常用)
    • 4. Spring Bean生命周期
      • 4.1 单例Bean(scope="singleton")
        • (1)核心流程(5步)
      • 1.2 多例Bean(scope="prototype"):容器“只创建不销毁”
          • (1)核心差异
    • 二、Bean配置继承:告别重复配置
      • 2.1 核心场景
      • 2.2 实战:父Bean与子Bean配置
        • 1. beans.xml(父Bean + 子Bean)
        • 2. 测试方法


前言

  • 在上一篇博客中,我们成功用 IDEA 搭建了第一个 Spring 系统:从配置 Maven 依赖、编写 beans.xml,到通过容器获取 User 对象——但操作完成后,你大概率会有这些疑问:
    • “Spring 帮我创建对象的背后,IoC 到底是个什么东西?它解决了什么本质问题?”
    • BeanFactoryApplicationContext 都是容器,到底有啥区别?为啥推荐用后者?”
    • scope="prototype" 加不加,对象居然不一样?init-methoddestroy-method 又是怎么生效的?”
  • 所以这一篇,我们不再只做“操作步骤”,而是从“问题本质”出发,把 IoC 思想、容器特性、Bean 核心机制这些“底层逻辑”讲透,让你不仅会用 Spring,更懂 Spring。

我的个人主页,欢迎来阅读我的其他文章
https://blog.csdn.net/2402_83322742?spm=1011.2415.3001.5343
我的Java-Spring入门指南知识文章专栏
欢迎来阅读指出不足
https://blog.csdn.net/2402_83322742/category_13040333.html?spm=1001.2014.3001.5482

在这里插入图片描述


一、Spring IoC是什么?有什么用?核心思想是什么?

在讲 IoC 之前,我们先回到“没有 Spring”的场景——你会发现,IoC 不是凭空出现的,而是为了解决传统 Java 开发的许多问题。

1. 没有 Spring 的痛点

假设我们要写一个“用户使用手机”的功能,传统 Java 代码会这样写:

在这里插入图片描述


// 定义Phone接口
public interface Phone {void call();
}// 用户类
public class User {// 声明Phone类型的属性,不自己创建对象private Phone phone;// 提供setter方法,让Spring注入Phone对象public void setPhone(Phone phone) {this.phone = phone;}public void usePhone() {phone.call(); // 调用手机的打电话功能}
}// 3. 测试
public class Test {public static void main(String[] args) {User user = new User();user.usePhone(); // 输出:用苹果手机打电话}
}

在这里插入图片描述

看起来没问题,但如果需求变了——“把苹果手机换成华为手机”,你要改多少地方?

  1. 新建 HuaweiPhone 类;
  2. User 类里的 new Phone()new HuaweiPhone()
  3. 如果还有 User2User3 也用了 Phone,所有用户类都要改!

核心问题:对象的创建和依赖“绑死”在代码里

——用户(User)要自己创建依赖的对象(Phone),一旦依赖的对象变了,所有用到它的类都要修改,这就是“高耦合”。

2. Spring IoC 是什么?

IoC 的全称是 Inversion of Control(控制反转),直白理解就是:

原来由你(代码)控制“创建对象、管理依赖”,现在把这个“控制权”反转给 Spring 容器——让容器帮你创建对象、维护对象之间的依赖关系

用上面的“用户用手机”例子,Spring 会怎么处理?

  1. beans.xml 里定义 PhoneUser 的 Bean(告诉容器要创建什么对象):
     <!-- 定义华为手机对象 --><bean id="huaweiPhone" class="org.example.test.HuaweiPhone"></bean><!-- 定义用户对象,依赖手机对象 --><bean id="user" class="org.example.test.User"><!-- 告诉容器:给User的phone属性赋值,值是id为huaweiPhone的Bean --><property name="phone" ref="huaweiPhone"/></bean>
  2. User 类不用自己 new Phone 了,只需声明Phone类型的属性,不自己创建对象
// 用户类
public class User {// 声明Phone类型的属性,不自己创建对象private Phone phone;// 提供setter方法,让Spring注入Phone对象public void setPhone(Phone phone) {this.phone = phone;}public void usePhone() {phone.call(); // 调用手机的打电话功能}
}
  1. 从容器获取 User 对象直接用:
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
    User user = context.getBean("user", User.class);
    user.usePhone(); // 输出:用华为手机打电话
    

如果要换手机,只需要改 beans.xml 里的 refapplePhone——User 类一行代码都不用动!这就是 IoC 的魔力。

3. Spring IoC 的核心作用

作用传统开发(无Spring)Spring IoC(有Spring)
对象创建手动 new,写死在代码里容器创建,配置在 beans.xml
依赖管理自己维护依赖(如 User 自己 new Phone)容器注入依赖(ref 配置)
需求变更改所有用到该对象的类(高耦合)只改配置文件(低耦合)
对象复用每次用都要 new(浪费资源)容器管理单例(默认),直接复用

4. Spring IoC 的核心思想

IoC 的思想可以用一句话概括:“不要找我,我会找你”

  • 传统开发:你(User)需要 Phone,就自己去找(new Phone());
  • Spring IoC:你(User)只需要告诉容器“我需要 Phone”(声明 private Phone phone + 提供 setPhone),容器会主动把 Phone 送到你手里(注入)。

面试题:说一下 Spring 的核心思想?
答:Spring 的核心思想是 IoC(控制反转)DI(依赖注入,IoC 的具体实现)。IoC 指将对象的创建、依赖管理控制权从代码反转给容器;DI 指容器在创建对象时,自动将其依赖的对象注入进来,两者共同实现了“解耦”,让代码更灵活、更易维护。


二、Spring 容器家族

上一篇我们用了 ClassPathXmlApplicationContext,但 Spring 还有个更基础的容器 BeanFactory——它们都是 IoC 容器,但定位完全不同。

1. 容器的核心职责是什么?

不管是 BeanFactory 还是 ApplicationContext,核心职责都是两件事:

  1. 加载配置文件(如 beans.xml);
  2. 创建并管理 Bean(根据配置创建对象、注入依赖、销毁对象)。

2. BeanFactory

BeanFactory 是 Spring 容器的 顶层接口(最基础的容器),定义了容器的核心功能(如 getBean())。它的特点是:

  • 延迟加载 Bean:只有调用 getBean() 时,才会创建 Bean 对象(不调用就不创建);
  • 功能简单:只提供“创建 Bean”“获取 Bean”的基础功能,没有额外特性(如国际化、事件发布);
  • 适合场景:资源有限的环境(如嵌入式设备、手机端),追求最小化开销。

代码示例(了解即可,很少用)

// 用 BeanFactory 加载配置文件(需要传入资源路径)
Resource resource = new ClassPathResource("beans.xml");
BeanFactory factory = new XmlBeanFactory(resource);// 此时 Bean 还没创建,直到调用 getBean()
User user = factory.getBean("user", User.class); // 这一步才创建 User 对象
System.out.println(user);

3. ApplicationContext(开发首选)

ApplicationContextBeanFactory子接口,继承了 BeanFactory 的所有功能,还增加了很多实用特性。它的特点是:

  • 预初始化 Bean:容器启动时(加载 beans.xml 后),会主动创建所有 单例 Bean(默认 scope="singleton"
  • 功能丰富:支持国际化、事件发布、加载多个配置文件等;
  • 适合场景:企业级开发(Web 应用、后台系统),是开发中的“首选容器”。

常用实现类

实现类作用上一篇示例代码
ClassPathXmlApplicationContext从类路径(src/main/resources)加载配置new ClassPathXmlApplicationContext("beans.xml")
FileSystemXmlApplicationContext从文件系统绝对路径加载配置new FileSystemXmlApplicationContext("E:\\xxx\\beans.xml")

4. 两者核心区别

对比维度BeanFactoryApplicationContext
加载时机延迟加载(getBean 时创建 Bean)预加载(容器启动时创建单例 Bean)
功能范围基础功能(getBean、containsBean)基础功能 + 增强功能(国际化、事件)
启动速度快(只加载配置,不创建 Bean)慢(加载配置 + 创建单例 Bean)
内存占用小(按需创建 Bean)大(预创建单例 Bean)
开发推荐度极低(仅了解)极高(企业级开发首选)

为什么开发不用 BeanFactory?
答:预初始化虽然启动慢,但能在容器启动时就发现配置错误(如 class 路径写错、setter 方法缺失)——如果用 BeanFactory,到调用 getBean() 时才报错,可能线上运行很久才发现问题,风险更高。


三、Spring Bean容器管理的对象到底是什么?

你可能会问:“Bean 不就是 Java 对象吗?为什么要叫 Bean?”——其实 Bean 是“被 Spring 容器管理的 Java 对象”,它的创建、生命周期、作用域都由容器控制,和普通 Java 对象不一样。

1. Bean 的定义

上一篇的 beans.xml 里,<bean> 标签就是 Bean 的“说明书”,告诉容器:“你要创建哪个类的对象,怎么配置它”。

<bean id="user" class="org.example.pojo.User" scope="singleton" init-method="init" destroy-method="destroy"><property name="name" value="Bob"/><property name="age" value="18"/>
</bean>
核心属性作用注意点
idBean 的唯一标识(容器中不能重复)只能有一个,如 id="user"
classBean 对应的 Java 类(全限定类名)必须有无参构造(容器默认用无参构造创建)
nameBean 的别名(可多个,用逗号分隔)name="user1,user2",可和 id 共存
scopeBean 的作用域(单例/多例)默认 singleton(单例),可选 prototype(多例)
init-methodBean 初始化后执行的方法方法名自定义,如 init-method="init"
destroy-methodBean 销毁前执行的方法只对单例 Bean 生效(多例 Bean 容器不销毁)

问题:当 id 和 name 都为 user 时,会报错吗?

比如你写了这样的配置:

<bean id="user" class="org.example.pojo.User"/>
<bean name="user" class="org.example.pojo.User"/>

会报错! 因为 idname 都是 Bean 的标识,容器中不允许“标识重复”——不管是 id 还是 name,只要值一样,就会抛出“Bean 名称重复”的异常。

2. Bean 的创建方式

容器创建 Bean 最常用的方式是 “默认无参构造器”

问题:如果 User 只有有参构造,容器能创建 Bean 吗?

比如 User 类这样写:

public class User {private String name;private int age;// 只有有参构造,没有无参构造public User(String name, int age) {this.name = name;this.age = age;}
}

此时直接用 <bean id="user" class="org.example.pojo.User"/> 会报错——容器找不到无参构造。
解决方案:在 beans.xml 里用 <constructor-arg> 配置有参构造的参数:

<bean id="user" class="org.example.pojo.User"><!-- 对应 User(String name, int age) 的参数 --><constructor-arg value="Bob" index="0"/> <!-- index=0 表示第一个参数 --><constructor-arg value="18" index="1"/>  <!-- index=1 表示第二个参数 -->
</bean>

这样容器会调用有参构造 new User("Bob", 18) 创建对象。

3. Bean 的作用域

假如我们的 test1 方法里有这样的代码:
在这里插入图片描述

@Test
public void test1() {FileSystemXmlApplicationContext context = new FileSystemXmlApplicationContext("E:\\xxx\\beans.xml");User user = context.getBean("user1", User.class);User user1 = context.getBean("user1", User.class);System.out.println(user == user1); // 输出 false
}

为什么 user == user1false?因为 user1scope="prototype"——这就要讲 Bean 的两种核心作用域:

在这里插入图片描述

(1)默认作用域:singleton(单例)

  • 含义:容器中只创建一个 Bean 对象,所有 getBean() 拿到的都是同一个对象;
  • 生命周期:容器启动时创建,容器关闭时销毁;
  • 代码验证
    <bean id="user" class="org.example.pojo.User"/> <!-- 默认 scope="singleton" -->
    
    User user = context.getBean("user", User.class);
    User user2 = context.getBean("user", User.class);
    System.out.println(user == user2); // 输出 true(同一个对象)
    

(2)常用作用域:prototype(多例)

  • 含义:每次调用 getBean() 时,容器都会创建一个新的 Bean 对象;
  • 生命周期:容器只负责创建,不负责销毁(用完后由 JVM 垃圾回收);
  • 代码验证
    <bean id="user1" class="org.example.pojo.User" scope="prototype"/>
    
    User user = context.getBean("user1", User.class);
    User user1 = context.getBean("user1", User.class);
    System.out.println(user == user1); // 输出 false(两个不同对象)
    

作用域对比(开发常用)

作用域容器创建时机容器销毁时机适用场景
singleton容器启动时容器关闭时无状态对象(如 Service、Dao)
prototype调用 getBean()不主动销毁(JVM 回收)有状态对象(如 User、Order)

4. Spring Bean生命周期

Bean的生命周期,简单说就是“Bean从创建到销毁的全过程”。但不同作用域(scope)的Bean,生命周期差异很大,我们重点讲最常用的单例(singleton)多例(prototype)

4.1 单例Bean(scope=“singleton”)

单例Bean是Spring默认的作用域,容器启动时创建,容器关闭时销毁,整个容器中只有一个实例。

(1)核心流程(5步)
  1. 实例化:容器启动 → 调用Bean的无参构造创建对象;
  2. 属性注入:通过setter方法给Bean的属性赋值;
  3. 初始化:调用配置中指定的init-method方法;
  4. 使用:通过getBean()获取实例,执行业务逻辑;
  5. 销毁:容器关闭 → 调用配置中指定的destroy-method方法。

1.2 多例Bean(scope=“prototype”):容器“只创建不销毁”

多例Bean每次调用getBean()时才创建实例,且容器不管理销毁(用完后由JVM垃圾回收)。

(1)核心差异
  • 实例化/注入/初始化:仅在getBean()时执行(而非容器启动);
  • 销毁:容器不调用destroy-method,JVM回收。

二、Bean配置继承:告别重复配置

Spring的“Bean继承”不是Java类的继承(如Student extends User),而是配置信息的复用——子Bean可以继承父Bean的属性、初始化方法等配置,还能覆盖父配置。

2.1 核心场景

比如多个Bean都需要设置age=18,如果每个Bean都写一遍<property name="age" value="18"/>,会很冗余。此时用“配置继承”就能复用这行配置。

2.2 实战:父Bean与子Bean配置

1. beans.xml(父Bean + 子Bean)
<!-- 父Bean:定义通用配置(可被继承) -->
<bean id="userParent" class="org.example.pojo.User"><property name="age" value="18"/> <!-- 所有子Bean默认继承这个age --><property name="name" value="默认名称"/> <!-- 子Bean可覆盖 -->
</bean><!-- 子Bean1:继承父Bean,覆盖name属性 -->
<bean id="student" class="org.example.pojo.User"parent="userParent"> <!-- 指定父Bean的id --><property name="name" value="张三"/> <!-- 覆盖父Bean的name -->
</bean><!-- 子Bean2:继承父Bean,不覆盖任何属性 -->
<bean id="teacher" class="org.example.pojo.User"parent="userParent"/>
2. 测试方法
@Test
public void testBeanInheritance() {ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");User student = context.getBean("student", User.class);User teacher = context.getBean("teacher", User.class);System.out.println("学生Bean:" + student); // 继承age=18,覆盖name=张三System.out.println("老师Bean:" + teacher); // 完全继承父配置context.close();
}

在这里插入图片描述


我的个人主页,欢迎来阅读我的其他文章
https://blog.csdn.net/2402_83322742?spm=1011.2415.3001.5343
我的Java-Spring入门指南知识文章专栏
欢迎来阅读指出不足
https://blog.csdn.net/2402_83322742/category_13040333.html?spm=1001.2014.3001.5482

非常感谢您的阅读,喜欢的话记得三连哦

在这里插入图片描述

http://www.xdnf.cn/news/1485901.html

相关文章:

  • 基于 Django+Vue3 的 AI 海报生成平台开发博客(海报模块专项)
  • HTTPS协议——对于HTTP的协议的加密
  • 架构进阶——解读121页IT规划咨询项目规划报告【附全文阅读】
  • HarmonyOS 应用开发深度解析:掌握 ArkTS 声明式 UI 与现代化状态管理
  • 大数据(非结构化数据,Spark,MongoDB)
  • 《沈南鹏传 - 做最擅长的事》(下篇)读书笔记
  • Gitlab 配置自定义 clone 地址
  • 【面试向】边缘计算基础介绍
  • Java全栈开发面试实录:从基础到高阶技术深度解析
  • Oracle到金仓数据库信创改造迁移实施规划方案(下篇)
  • 【mysql】SQL自连接:什么时候需要,什么时候不需要?
  • 【C++】类与对象(下)
  • Java 大视界 -- Java 大数据机器学习模型在金融市场风险评估与投资组合优化中的应用(407)
  • Redis(48)Redis哨兵的优点和缺点是什么?
  • 如何在 DevOps 管道中实现 AI?
  • Wan2.2-S2V - 音频驱动图像生成电影级质量的数字人视频 ComfyUI工作流 支持50系显卡 一键整合包下载
  • VS2017安装Qt插件
  • 【C++详解】C++ 智能指针:使用场景、实现原理与内存泄漏防治
  • 苹果 FoundationModels 秘典侠客行:隐私为先的端侧 AI 江湖
  • 联邦学习+边缘计算结合
  • Python进阶编程:文件操作、系统命令与函数设计完全指南
  • 梅花易数:从入门到精通
  • LLM面试基础(一)
  • 【Beetle RP2350】人体运动感应警报系统
  • LeetCode 522.最长特殊序列2
  • 【数据结构入门】排序算法(3):了解快速排序
  • Linux环境下配置visual code
  • 【iOS】多界面传值
  • 灾难性遗忘:神经网络持续学习的核心挑战与解决方案
  • CSS(展示效果)