默认情况下,所有的 Spring Bean 都是单例的,也就是说在整个 Spring 应用中, Bean 的实例只有一个。
我们可以在 <bean> 元素中添加 scope 属性来配置 Spring Bean 的作用范围。例如,如果每次获取 Bean 时,都需要一个新的 Bean 实例,那么应该将 Bean 的 scope 属性定义为 prototype,如果 Spring 需要每次都返回一个相同的 Bean 实例,则应将 Bean 的 scope 属性定义为 singleton。
Spring 5 共提供了 6 种 scope 作用域,如下表。
作用范围 | 描述 |
---|---|
singleton | 默认值,单例模式,表示在 Spring 容器中只有一个 Bean 实例 |
prototype | 原型模式,表示每次通过 Spring 容器获取 Bean 时,容器都会创建一个新的 Bean 实例。 |
request | 每次 HTTP 请求,容器都会创建一个 Bean 实例。该作用域只在当前 HTTP Request 内有效。 |
session | 同一个 HTTP Session 共享一个 Bean 实例,不同的 Session 使用不同的 Bean 实例。该作用域仅在当前 HTTP Session 内有效。 |
application | 同一个 Web 应用共享一个 Bean 实例,该作用域在当前 ServletContext 内有效。 与 singleton 类似,但 singleton 表示每个 IoC 容器中仅有一个 Bean 实例,而一个 Web 应用中可能会存在多个 IoC 容器,但一个 Web 应用只会有一个 ServletContext,也可以说 application 才是 Web 应用中货真价实的单例模式。 |
websocket | websocket 的作用域是 WebSocket ,即在整个 WebSocket 中有效。 |
注意:在以上 6 种 Bean 作用域中,除了 singleton 和 prototype 可以直接在常规的 Spring IoC 容器(例如 ClassPathXmlApplicationContext)中使用外,剩下的都只能在基于 Web 的 ApplicationContext 实现(例如 XmlWebApplicationContext)中才能使用,否则就会抛出一个 IllegalStateException 的异常。
================================================================
在传统的 Java 应用中,Bean 的生命周期很简单,使用 Java 关键字 new 进行 Bean 的实例化后,这个 Bean 就可以使用了。
一旦这个 Bean 长期不被使用,Java 自动进行垃圾回收。
相比之下,Spring 中 Bean 的生命周期较复杂,大致可以分为以下 5 个阶段:
- Bean 的实例化
- Bean 属性赋值
- Bean 的初始化
- Bean 的使用
- Bean 的销毁
Spring 根据 Bean 的作用域来选择 Bean 的管理方式,
- 对于 singleton 作用域的 Bean 来说,Spring IoC 容器能够精确地控制 Bean 何时被创建、何时初始化完成以及何时被销毁;
- 对于 prototype 作用域的 Bean 来说,Spring IoC 容器只负责创建,然后就将 Bean 的实例交给客户端代码管理,Spring IoC 容器将不再跟踪其生命周期。
Spring 生命周期流程
Spring Bean 的完整生命周期从创建 Spring IoC 容器开始,直到最终 Spring IoC 容器销毁 Bean 为止,其具体流程如下图所示。
Bean 生命周期的整个执行过程描述如下。
- Spring 启动,查找并加载需要被 Spring 管理的 Bean,对 Bean 进行实例化。
- 对 Bean 进行属性注入。
- 如果 Bean 实现了 BeanNameAware 接口,则 Spring 调用 Bean 的 setBeanName() 方法传入当前 Bean 的 id 值。
- 如果 Bean 实现了 BeanFactoryAware 接口,则 Spring 调用 setBeanFactory() 方法传入当前工厂实例的引用。
- 如果 Bean 实现了 ApplicationContextAware 接口,则 Spring 调用 setApplicationContext() 方法传入当前 ApplicationContext 实例的引用。
- 如果 Bean 实现了 BeanPostProcessor 接口,则 Spring 调用该接口的预初始化方法 postProcessBeforeInitialzation() 对 Bean 进行加工操作,此处非常重要,Spring 的 AOP 就是利用它实现的。
- 如果 Bean 实现了 InitializingBean 接口,则 Spring 将调用 afterPropertiesSet() 方法。
- 如果在配置文件中通过 init-method 属性指定了初始化方法,则调用该初始化方法。
- 如果 BeanPostProcessor 和 Bean 关联,则 Spring 将调用该接口的初始化方法 postProcessAfterInitialization()。此时,Bean 已经可以被应用系统使用了。
- 如果在 <bean> 中指定了该 Bean 的作用域为 singleton,则将该 Bean 放入 Spring IoC 的缓存池中,触发 Spring 对该 Bean 的生命周期管理;如果在 <bean> 中指定了该 Bean 的作用域为 prototype,则将该 Bean 交给调用者,调用者管理该 Bean 的生命周期,Spring 不再管理该 Bean。
- 如果 Bean 实现了 DisposableBean 接口,则 Spring 会调用 destory() 方法销毁 Bean;如果在配置文件中通过 destory-method 属性指定了 Bean 的销毁方法,则 Spring 将调用该方法对 Bean 进行销毁。
自定义 Bean 的生命周期
我们可以在 Spring Bean 生命周期的某个特定时刻,指定一些生命周期回调方法完成一些自定义的操作,对 Bean 的生命周期进行管理。
Bean 的生命周期回调方法主要有两种:
- 初始化回调方法:在 Spring Bean 被初始化后调用,执行一些自定义的回调操作。
- 销毁回调方法:在 Spring Bean 被销毁前调用,执行一些自定义的回调操作。
我们可以通过以下 3 种方式自定义 Bean 的生命周期回调方法:
- 通过接口实现
- 通过 XML 配置实现
- 使用注解实现
如果一个 Bean 中有多种生命周期回调方法时,优先级顺序为:注解 > 接口 > XML 配置。
通过接口实现
我们可以在 Spring Bean 的 Java 类中,通过实现 InitializingBean 和 DisposableBean 接口,指定 Bean 的生命周期回调方法。
回调方式 | 接口 | 方法 | 说明 |
---|---|---|---|
初始化回调 | InitializingBean | afterPropertiesSet() | 指定初始化回调方法,这个方法会在 Spring Bean 被初始化后被调用,执行一些自定义的回调操作。 |
销毁回调 | DisposableBean | destroy() | 指定销毁回调方法,这个方法会在 Spring Bean 被销毁前被调用,执行一些自定义的回调操作。 |
注意:通常情况下,我们不建议通过这种方式指定生命周期回调方法,这是由于这种方式会导致代码的耦合性过高。
通过 XML 配置实现
我们还可以在 Spring 的 XML 配置中,通过 <bean> 元素中的 init-method 和 destory-method 属性,指定 Bean 的生命周期回调方法。
XML 配置属性 | 描述 |
---|---|
init-method | 指定初始化回调方法,这个方法会在 Spring Bean 被初始化后被调用,执行一些自定义的回调操作。 |
destory-method | 指定销毁回调方法,这个方法会在 Spring Bean 被销毁前被调用,执行一些自定义的回调操作。 |
使用注解实现
我们还可以通过 JSR-250 的 @PostConstruct 和 @PreDestroy 注解,指定 Bean 的生命周期回调方法。
注解 | 描述 |
---|---|
@PostConstruct | 指定初始化回调方法,这个方法会在 Spring Bean 被初始化后被调用,执行一些自定义的回调操作。 |
@PreDestroy | 指定销毁回调方法,这个方法会在 Spring Bean 被销毁前被调用,执行一些自定义的回调操作。 |