Bean 是什么?
在 Spring 中,Bean 是指被 Spring IoC(控制反转)容器管理的对象。通常情况下,Bean 是指应用程序中的对象,它们是由 Spring 容器在启动时自动创建并管理的。
Spring 的核心思想就是 IoC(控制反转),即将对象的创建和管理交给容器,而不是由程序员手动去创建和管理这些对象。在 Spring 中,容器会根据配置的类(可以是 Java 配置、XML 配置等)来实例化、配置和管理这些对象(Bean)。
是否是所有对象都是 Bean?
并不是所有对象都可以是 Spring 的 Bean。只有那些被 Spring IoC 容器管理的对象才是 Bean。通常,这些 Bean 是由 Spring 容器根据配置类、XML 配置文件等自动创建的对象,而不是我们手动创建的普通对象。
Bean 的作用域
Spring 提供了多种作用域来控制 Bean 的生命周期和可见范围。以下是常见的几种作用域:
-
单例(Singleton):
- 这是 Spring 中的默认作用域。
- Spring IoC 容器只会创建一个 Bean 实例,并且所有请求该 Bean 的地方都会返回相同的实例。
- 适用于大多数场景,比如数据库连接池等。
-
原型(Prototype):
- 每次请求该 Bean 时,Spring 都会创建一个新的 Bean 实例。
- 适用于每次请求需要不同状态的场景,比如临时的对象。
-
请求(Request):
- 这种作用域仅在 web 应用中有效。
- 每次 HTTP 请求都会创建一个新的 Bean 实例。
- 适用于在一次请求周期内需要一个特定 Bean 实例的场景。
-
会话(Session):
- 这种作用域也仅在 web 应用中有效。
- 每个 HTTP 会话都有一个独立的 Bean 实例。
- 适用于需要在整个会话周期内维持状态的场景。
-
应用(Application):
- 这种作用域也仅在 web 应用中有效。
- 所有的 Bean 在整个 Web 应用中共享同一个实例。
-
WebSocket:
- 每个 WebSocket 会话都有一个独立的 Bean 实例。
- 适用于 WebSocket 应用中,需要在 WebSocket 生命周期内维持状态的场景。
Bean 的生命周期
Spring Bean 的生命周期是指 Bean 从创建到销毁的整个过程。主要步骤如下:
-
实例化 Bean:
- Spring 容器通过构造器或工厂方法创建 Bean 实例。
-
设置 Bean 的属性:
- Spring 会将配置的属性注入到 Bean 中(依赖注入),这些属性可能是其他 Bean 的引用。
-
检查 Aware 接口并设置相关依赖:
- 如果 Bean 实现了
BeanNameAware
或BeanFactoryAware
接口,容器会调用相应的方法来注入相关信息(如 Bean 的名称,容器的引用等)。
- 如果 Bean 实现了
-
BeanPostProcessor 的处理:
BeanPostProcessor
是一个用于修改 Bean 的回调接口。在属性注入之后,Spring 会调用所有注册的BeanPostProcessor
的postProcessBeforeInitialization
方法。
-
初始化 Bean:
- 如果 Bean 实现了
InitializingBean
接口,Spring 会调用afterPropertiesSet
方法。 - 如果 Bean 定义了
init-method
,Spring 也会调用这个方法。
- 如果 Bean 实现了
-
BeanPostProcessor 第二次调用:
- 在初始化之后,Spring 会再次调用所有注册的
BeanPostProcessor
的postProcessAfterInitialization
方法。
- 在初始化之后,Spring 会再次调用所有注册的
-
使用 Bean:
- 这时 Bean 已经准备好,可以被应用程序使用。
-
销毁 Bean:
- 当容器关闭时,如果 Bean 实现了
DisposableBean
接口,Spring 会调用其destroy
方法。 - 如果 Bean 定义了
destroy-method
,Spring 也会调用这个方法。
- 当容器关闭时,如果 Bean 实现了
Spring 循环依赖问题及解决方案
循环依赖是什么?
循环依赖是指两个或多个 Bean 相互依赖,形成了一个闭环。例如,Bean A 依赖 Bean B,而 Bean B 又依赖 Bean A,这就构成了一个循环依赖。
如何检测循环依赖?
Spring 会在创建 Bean 时为每个 Bean 打标。如果在创建过程中,某个 Bean 还没有完全初始化并且再次请求该 Bean,容器就会检测到循环依赖。
如何解决循环依赖?
-
构造器注入不支持循环依赖:
- 由于 Spring 需要完全实例化 Bean 才能注入构造函数的依赖,因此构造器注入不能解决循环依赖。
-
字段注入或 Setter 注入解决循环依赖:
- Spring 使用三级缓存来解决循环依赖问题:
- 一级缓存:存放 Bean 的原始实例。
- 二级缓存:存放已经创建并且属性已设置的 Bean。
- 三级缓存:存放尚未设置属性的 Bean。
当 Spring 创建 Bean 时,首先创建一个原始实例,将其放入一级缓存。当 Bean 的属性需要注入其他 Bean 时,Spring 会检查二级缓存,若该 Bean 还未准备好,则创建并放入三级缓存。属性注入完成后,Bean 会放入二级缓存。
- Spring 使用三级缓存来解决循环依赖问题:
-
使用
@Lazy
注解:- 使用
@Lazy
注解可以延迟 Bean 的加载,只有在真正需要时才会创建 Bean,这也能避免一些循环依赖问题。
- 使用
通过这些机制,Spring 能够解决大部分常见的循环依赖问题。