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
到底是个什么东西?它解决了什么本质问题?” - “
BeanFactory
和ApplicationContext
都是容器,到底有啥区别?为啥推荐用后者?” - “
scope="prototype"
加不加,对象居然不一样?init-method
和destroy-method
又是怎么生效的?”
- “Spring 帮我创建对象的背后,
- 所以这一篇,我们不再只做“操作步骤”,而是从“问题本质”出发,把 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(); // 输出:用苹果手机打电话}
}
看起来没问题,但如果需求变了——“把苹果手机换成华为手机”,你要改多少地方?
- 新建
HuaweiPhone
类; - 改
User
类里的new Phone()
为new HuaweiPhone()
; - 如果还有
User2
、User3
也用了Phone
,所有用户类都要改!
核心问题:对象的创建和依赖“绑死”在代码里
——用户(
User
)要自己创建依赖的对象(Phone
),一旦依赖的对象变了,所有用到它的类都要修改,这就是“高耦合”。
2. Spring IoC 是什么?
IoC 的全称是 Inversion of Control(控制反转),直白理解就是:
原来由你(代码)控制“创建对象、管理依赖”,现在把这个“控制权”反转给 Spring 容器——让容器帮你创建对象、维护对象之间的依赖关系。
用上面的“用户用手机”例子,Spring 会怎么处理?
- 在
beans.xml
里定义Phone
和User
的 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>
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(); // 调用手机的打电话功能}
}
- 从容器获取
User
对象直接用:ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); User user = context.getBean("user", User.class); user.usePhone(); // 输出:用华为手机打电话
如果要换手机,只需要改 beans.xml
里的 ref
为 applePhone
——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
,核心职责都是两件事:
- 加载配置文件(如
beans.xml
); - 创建并管理 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(开发首选)
ApplicationContext
是 BeanFactory
的 子接口,继承了 BeanFactory
的所有功能,还增加了很多实用特性。它的特点是:
- 预初始化 Bean:容器启动时(加载
beans.xml
后),会主动创建所有 单例 Bean(默认scope="singleton"
) - 功能丰富:支持国际化、事件发布、加载多个配置文件等;
- 适合场景:企业级开发(Web 应用、后台系统),是开发中的“首选容器”。
常用实现类:
实现类 | 作用 | 上一篇示例代码 |
---|---|---|
ClassPathXmlApplicationContext | 从类路径(src/main/resources )加载配置 | new ClassPathXmlApplicationContext("beans.xml") |
FileSystemXmlApplicationContext | 从文件系统绝对路径加载配置 | new FileSystemXmlApplicationContext("E:\\xxx\\beans.xml") |
4. 两者核心区别
对比维度 | BeanFactory | ApplicationContext |
---|---|---|
加载时机 | 延迟加载(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>
核心属性 | 作用 | 注意点 |
---|---|---|
id | Bean 的唯一标识(容器中不能重复) | 只能有一个,如 id="user" |
class | Bean 对应的 Java 类(全限定类名) | 必须有无参构造(容器默认用无参构造创建) |
name | Bean 的别名(可多个,用逗号分隔) | 如 name="user1,user2" ,可和 id 共存 |
scope | Bean 的作用域(单例/多例) | 默认 singleton (单例),可选 prototype (多例) |
init-method | Bean 初始化后执行的方法 | 方法名自定义,如 init-method="init" |
destroy-method | Bean 销毁前执行的方法 | 只对单例 Bean 生效(多例 Bean 容器不销毁) |
问题:当 id 和 name 都为 user 时,会报错吗?
比如你写了这样的配置:
<bean id="user" class="org.example.pojo.User"/>
<bean name="user" class="org.example.pojo.User"/>
会报错! 因为 id
和 name
都是 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 == user1
是 false
?因为 user1
的 scope="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步)
- 实例化:容器启动 → 调用Bean的无参构造创建对象;
- 属性注入:通过setter方法给Bean的属性赋值;
- 初始化:调用配置中指定的
init-method
方法; - 使用:通过
getBean()
获取实例,执行业务逻辑; - 销毁:容器关闭 → 调用配置中指定的
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
非常感谢您的阅读,喜欢的话记得三连哦 |