读书笔记《spring-5-design-patterns》第 5 章 了解 Bean 生命周期和使用的模式
第 5 章 了解 Bean 生命周期和使用的模式
在上一章中,您看到了 Spring 如何在容器中创建 bean。您还学习了如何使用 XML、Java 和 Annotation 配置依赖注入模式。在本章中,我们将更详细地介绍,除了注入 bean 和 Spring 应用程序中的依赖项配置。在这里,您将探索容器中 bean 的生命和范围,并了解 Spring 容器如何使用 XML、Annotation 和 Java 对已定义的 Spring bean 配置进行工作。 Spring 不仅允许我们控制 DI 模式的各种配置和要注入到从特定 bean 定义创建的对象中的依赖值,还可以控制从特定 bean 定义创建的 bean 的生命和范围。
在我写这一章的时候,我两岁半的儿子 Arnav 来找我,开始在我的手机上玩电子游戏。他穿着一件 T 恤,上面有一句有趣的名言,这些台词描述了他的一整天。台词是这样的——我完美的一天:起床、玩电子游戏、吃饭、玩电子游戏、吃饭、玩电子游戏和睡觉。
实际上,这些台词完美地反映了他每天的生命周期,他醒来,玩耍,吃饭,再玩耍,最后,睡觉。通过这个例子,我只是想证明一切都有生命周期。我们可以讨论蝴蝶、星星、青蛙或植物的生命周期。但是让我们来谈谈更有趣的事情——一个 bean 的生命周期!
Spring 容器中的每个 bean 都有一个生命周期和它自己的范围。 Spring 容器管理 Spring 应用程序中 bean 的生命周期。我们可以通过使用 Spring 感知接口在某些阶段对其进行自定义。本章将讨论容器中 bean 的生命周期,以及如何在其生命周期的各个阶段使用设计模式对其进行管理。在本章结束时,您将对 bean 生命周期及其在容器中的各个阶段有一个大致的了解。您还将了解 Spring 中的多种 bean 范围。本章将涵盖以下几点:
- The Spring bean life cycle, and its phases, which are listed as follows:
- The initialization phase
- The Use phase
- The destruction phase
- Spring callbacks
- Understanding bean scopes
- Singleton pattern
- Prototype pattern
- Custom scopes
- Other bean scopes
现在让我们花点时间看看 Spring 是如何在 Spring 应用程序中管理一个 bean 从创建到销毁的生命周期的
Spring bean 生命周期及其阶段
在 Spring 应用程序中,术语生命周期适用 任何应用程序类——独立 Java、Spring Boot 应用程序或集成/系统测试。此外,生命周期适用于所有三种依赖注入样式——XML、注释和 Java 配置。您可以根据业务目标定义 bean 的配置。但是 Spring 创建这些 bean 并管理 Spring bean 的生命周期。 Spring 通过 ApplicationContext
在 Java 或 XML 中加载 bean 配置。加载这些 bean 后,Spring 容器会根据您的配置处理这些 bean 的创建和实例化。让我们将 Spring 应用程序的生命周期分为三个阶段,如下所示:
- The initialization phase
- The Use phase
- The destruction phase
请参考下图:
如上图所示,每个 Spring bean 在完整的生命周期中都会经历这三个阶段。每个阶段都有一些要为每个 Spring bean 执行的操作(取决于配置)。 Spring 适合管理您的应用程序生命周期。它在所有三个阶段都发挥着重要作用。
现在让我们花点时间看看 Spring 在第一个初始化阶段是如何工作的。
初始化阶段
在这个阶段,首先 Spring 加载任何样式 XML、注释和 Java 配置的所有 configuration 文件。这个阶段准备豆子以供使用。在此阶段完成之前,该应用程序不可用。这个阶段实际上是创建应用程序服务以供使用,并将系统资源分配给 bean。 Spring 提供 ApplicationContext
来加载 bean 配置;一旦创建了应用程序上下文,初始化阶段就完成了。让我们看看 Spring 如何以 Java 或 XML 加载配置文件。
从配置创建应用程序上下文
Spring 提供了多种 ApplicationContext
的实现来加载各种风格的配置文件。下面列出了这些:
- For Java configuration, the following is used:
- For XML configuration, the implementation is as follows:
上述代码中,Spring使用AnnotationConfigApplicationContext
类加载Java配置文件,使用ClassPathXmlApplicationContext
加载XML配置文件Spring 容器的类。对于所有类型的配置,Spring 的行为都是相同的。您在应用程序中使用什么配置样式并不重要。下图显示了这个阶段究竟发生了什么:
如上图所示,初始化阶段分为以下两个步骤:
- Load bean definitions
- Initialize bean instances
加载 bean 定义
在这一步中,所有的配置文件——@Configuration
类或 XML 文件——都被处理。对于基于注解的配置,所有使用 @Components
注解的类都会被扫描以加载 bean 定义。解析所有 XML 文件,并将 bean 定义添加到 BeanFactory
。每个 bean 都在其 id
下索引。 Spring 提供了多个 BeanFactoryPostProcessor
bean,因此,调用它来解决运行时依赖关系,例如从外部属性文件中读取值。在 Spring 应用程序中,BeanFactoryPostProcessor
可以修改任何 bean 的定义。下图描述了此步骤:
如上图所示,Spring首先加载bean的定义,然后对部分bean调用BeanFactoryProcessor
来相应地修改其定义。让我们看一个例子。我们有两个配置文件——AppConfig.java
和InfraConfig.java
,定义如下:
- Following is the
AppConfig.java
file:
- Following is the
InfraConfig.java
file:
这些 Java 配置文件由 ApplicationContext 加载到容器中,并以其 id
为索引,如下图所示:
在最后一个图中,Spring bean 在其 ID 下被索引到 Spring 的 BeanFactory
中,然后,该 BeanFactory
对象作为BeanFactoryPostProcessor
的 postProcess()
方法的参数。 BeanFactoryPostProcessor
可以修改一些bean的bean定义;这取决于开发人员提供的 bean 配置。让我们看看 BeanFactoryPostProcessor
是如何工作的,以及如何在我们的应用程序中覆盖它:
BeanFactoryPostProcessor
works on the bean definitions or the configuration metadata of the bean before the beans are actually created.- Spring provides several useful implementations of
BeanFactoryPostProcessor
, such as reading properties and registering a custom scope. - You can write your own implementation of the
BeanFactoryPostProcessor
interface. - If you define a
BeanFactoryPostProcessor
in one container, it will only be applied to the bean definitions in that container.
以下是 BeanFactoryPostProcessor
的代码片段:
现在让我们看看 BeanFactoryPostProcessor
扩展点的以下示例:
读取外部属性文件(database.properties
)
在这里,我们将使用 DataSource
bean 来配置数据库值,例如 username
、password
、db url
、driver
,如下:
以下是配置文件中的DataSource
bean定义:
那么,在前面的代码中,我们如何解析 @Value
和 ${..}
变量呢?我们需要一个 PropertySourcesPlaceholderConfigurer
来评估它们。这是一个 BeanFactoryPostProcessor
。如果您使用 XML 配置,
命名空间会为您创建一个 PropertySourcesPlaceholderConfigurer
。
加载 bean 定义在加载配置文件时是一次性的过程,但 bean 实例的初始化阶段是针对容器中的每个 bean 执行的。让我们看一下应用程序中bean实例的初始化。
初始化 bean 实例
将 bean definitions 加载到 BeanFactory
后,Spring IoC 容器为应用程序实例化 bean;下图显示了处理流程:
如上图所示,对容器中的每个 bean 执行 bean 初始化步骤。我们可以将bean的创建过程总结如下:
- Each bean is eagerly instantiated by default. It is created in the right order with its dependencies injected unless marked as lazy.
- Spring provides multiple
BeanPostProcessor
, so, each bean goes through a post-processing phase such asBeanFactoryPostProcessor
, which can modify the bean definition. However, theBeanPostProcessor
can change the instance of the bean. - After execution of this phase, the bean is fully initialized and ready for use. It is tracked by its
id
till the context is destroyed, except for the prototype beans.
在下一节中,我们将讨论如何使用 BeanPostProcessor
自定义 Spring 容器。
使用 BeanPostProcessor 自定义 bean
BeanPostProcessor
是 Spring 中一个重要的 extension 点。它可以以任何方式修改 bean 实例。它用于启用强大的功能,例如 AOP 代理。您可以在应用程序中编写自己的 BeanPostProcessor
来创建自定义 后处理器
——该类必须实现 BeanPostProcessor
接口。 Spring 提供了几种 BeanPostProcessor
的实现。在Spring中,BeanPostProcessor
接口有两个回调方法,如下:
您可以实现 BeanPostProcessor
接口的这两个方法,为 bean 实例化、依赖解析等提供您自己的自定义逻辑。您可以配置多个 BeanPostProcessor
实现以将自定义逻辑添加到 Spring 容器。您还可以通过设置 order 属性来管理这些 BeanPostProcessor
的执行顺序。 BeanPostProcessor
在 Spring 容器实例化 bean 之后对 Spring bean 实例起作用。 BeanPostProcessor
的范围在 Spring 容器内,这意味着在一个容器中定义的 bean 不会由 BeanPostProcessor< /code> 在另一个容器中定义。
Spring 应用程序中的任何类都在容器中注册为 post-processor
;它由 Spring 容器为每个 bean 实例创建。并且Spring容器在容器初始化方法(初始化Bean的afterPropertiesSet()
和bean的之前调用
方法)。它还在任何 bean 初始化回调之后调用 postProcessBeforeInitialization()
方法class="literal">initpostProcessAfterInitialization()
方法。 Spring AOP 使用 post-processor
来提供代理包装逻辑(代理设计模式),尽管我们可以使用 post-处理器
。
Spring 的 ApplicationContext
会自动检测那些实现了 BeanPostProcessor
接口的 bean,并将这些 bean 注册为 post -处理器
。这些 bean 在创建任何其他 bean 时被调用。让我们探索以下
BeanPostProcessor
的示例。
让我们创建一个自定义 bean post-processor
,如下所示:
这个例子说明了基本用法,这里这个例子显示了一个 post-processor
为每个注册到容器的 bean 将字符串打印到系统控制台。这个 MyBeanPostProcessor
类用 @Component
注释,这意味着这个类与应用程序上下文中的其他 bean 类相同,现在运行以下演示班级。请参考以下代码:
这是我们将在控制台上得到的输出:
正如您在前面的输出中看到的,为 Spring 容器中的每个 bean 方法打印了两个回调方法的字符串。 Spring为一些特定的特性提供了很多预实现的BeanPostProcessor
,如下:
RequiredAnnotationBeanPostProcessor
AutowiredAnnotationBeanPostProcessor
CommonAnnotationBeanPostProcessor
PersistenceAnnotationBeanPostProcessor
XML 配置中的命名空间 <context:annotation-config/>
在同一个应用程序上下文中启用多个 post-processor
它在其中被定义。
现在让我们继续下一节,看看如何使用 BeanPostProcessor
启用 Initializer 扩展点。
Initializer 扩展点
bean post-processor
的这种特殊情况导致 init
(@PostConstruct
) 方法被调用。在内部,Spring 使用了几个 BeanPostProcessors (BPPs) CommonAnnotationBeanPostProcessor
到 enable 初始化。下图说明了 initializer 和 BPP 之间的关系。
现在让我们看一下 XML 中 Initializer 扩展点的以下示例:
命名空间
显式启用许多 post-processor
,让我们看看下面的 XML 配置文件:
在前面的配置代码中,您可以看到我已经定义了一些 bean,其中一个 bean accountRepository
存储库具有 init
bean标签的方法属性;这个属性有一个值,populateCache
。这不过是 accountRepository
bean 的 initializer
方法。如果
命名空间。我们来看
,则容器在 bean 初始化时调用它>JdbcAccountRepository
类,如下所示:
在Java配置中,我们可以使用@Bean
注解的initMethod
属性如下:
在基于注解的配置中,我们可以使用 JSR-250 注解,@PostConstruct
为如下:
我们已经看到了 bean 生命周期的第一阶段,Spring 使用基于 XML、Java 和 Annotation 的配置加载 bean 定义,之后,Spring 容器在 Spring 应用程序中以正确的顺序初始化每个 bean .下图概述了配置生命周期的第一阶段:
最后一张图显示了由 ApplicationContext
的相应实现加载的任何样式 XML、Annotation 或 Java 中的 Spring bean 元数据。所有 XML 文件都被解析,并与 bean 定义一起加载。在 Annotation 配置中,Spring 扫描所有组件,并加载 bean 定义。在 Java 配置中,Spring 读取所有 @Bean
方法来加载 bean 定义。加载完所有样式配置的bean定义后,BeanFactoryPostProcessor
进入画面修改部分bean的定义,然后容器实例化这些bean。最后,BeanPostProcessor
作用于 bean,它可以修改和改变 bean 对象。这是初始化阶段。现在让我们看看 bean 在其生命周期中的下一个使用阶段。
bean 的使用阶段
在 Spring 应用程序中,所有 Spring bean 99.99% 的时间都花在这个阶段。如果初始化阶段成功完成,则 Spring bean 进入此阶段。在这里,bean 被客户端用作应用程序服务。这些 bean 处理客户端请求,并执行 application 行为。在使用阶段,让我们看看如何在使用它的应用程序中调用从上下文获得的bean。请参考以下代码:
假设 return
服务返回一个原始对象,那么就直接调用它;这里没什么特别的。但是如果你的 bean 已经被包裹在一个代理中,那么事情就会变得更加有趣。让我们探索下图以更清楚地理解这一点:
在上图中,可以看到通过Proxy
类调用service
方法;它由专用的 BeanPostProcessor
在 init
阶段创建。它将您的 bean 包装在一个动态代理中,从而透明地为您的 bean 添加行为。它是装饰器设计模式和代理设计模式的实现。
让我们看看 Spring 如何在 Spring 应用程序中为您的 bean 创建代理。
使用代理在 Spring 中实现装饰器和代理模式
Spring 在 Spring 应用程序中使用两种类型的代理。以下是 Spring 使用的 proxies 的 kind:
- JDK Proxy: This is also known as a dynamic proxy. Its API is built into the JDK. For this proxy, the
Java
interface is required. - CGLib Proxy: This is NOT built into JDK. However, it is included in Spring JARS, and is used when the interface is not available. It cannot be applied to final classes or methods.
让我们在下图中查看两个代理的功能:
这就是 Spring Bean 生命周期的使用阶段。现在让我们进入生命周期的下一个阶段,即销毁阶段。
bean 的销毁阶段
在这个阶段,Spring 释放应用服务获取的所有系统resource。这些有资格进行垃圾收集。当您关闭应用程序上下文时,destruction 阶段完成。让我们看看这个阶段的以下代码行:
在上面的代码中,你觉得我们在这个阶段调用 applicationContext.close()
方法会发生什么?发生的过程如下:
- Any bean implementing the
org.springframework.beans.factory.DisposableBean
interface gets a callback from the container when it is destroyed. TheDisposableBean
interface specifies a single method:
- The
bean
instances are destroyed if instructed to call theirdestroy
methods. Beans must have adestroy
method defined, that is, ano-arg
method returningvoid
. - The context then destroys itself, and this context is not usable again.
- Only GC actually destroys objects and remember, it is called only when the
ApplicationContext
/JVM exit normally. It is not called for prototype beans.
让我们看看如何使用 XML 配置来实现它:
在配置中,accountRepository
bean 有一个名为 clearCache
的 destroy
方法:
让我们看一下与 Java 相同的配置。在Java配置中,我们可以使用@Bean
注解的destroyMethod
属性如下:
我们可以使用注释来做同样的事情。注释需要 annotation-config
或使用 <context:component-scan ... />
,如下所示
:
您现在已经看到了 Spring bean 生命周期的所有阶段。在初始化阶段,Bean Post Processors 用于初始化和代理。在使用阶段,Spring bean 使用代理的魔力。最后,在销毁阶段,它允许应用程序干净地终止。
现在您已经了解了 bean 的生命周期,让我们了解一下 bean 作用域,以及如何在 Spring 容器中创建自定义 bean 作用域。
了解 bean 范围
在 Spring 中,每个 bean 在容器中都有一个 scope。您不仅可以控制 bean 元数据及其生命周期,还可以控制该 bean 的范围。您可以创建 bean 的自定义范围,并将其注册到容器中。您可以通过使用基于 XML、Annotations 或 Java 的配置的 bean 定义来配置 bean 的范围。
Spring 应用程序上下文创建所有bean通过使用单例范围.这意味着,每次它总是同一个 bean;它被注入另一个 bean 或被其他服务调用多少次都没关系。由于这种单例行为,作用域降低了实例化的成本。它适用于应用程序中的无状态对象。
在 Spring 应用程序中,有时需要保存一些不安全的对象的状态以供重用。对于这样的需求,将 bean 作用域声明为单例是不安全的,因为以后重用时可能会导致意想不到的问题。 Spring 为此类需求提供了另一个范围,称为 Spring bean 的原型范围。
Spring定义了几个可以创建bean的范围,如下所示:
单例 bean 范围
在 Spring 中,任何具有单例 scope 的 bean 都只有一个为应用程序上下文创建的 bean 实例,它是为整个应用程序定义的。这是 Spring 容器的默认行为。但它不同于 Gang of Four (GoF) 模式书。在 Java 中,单例意味着 JVM 中每个 Classloader 的每个对象。但在 Spring 中,它意味着每个 Spring IoC 容器每个 bean 定义的每个 instance bean。这在下图中解释:
正如您在上图中所见,该对象的同一实例由 bean 定义 accountRepository
定义,注入到同一 IoC 容器中的其他协作 bean。 Spring 将所有单例 bean 实例存储在缓存中,所有协作 bean 获取缓存返回的该对象的依赖关系。
原型 bean 作用域
在 Spring 中,使用原型 scope 定义的任何 bean 都会在每次将 bean 注入其他协作 bean 时创建一个 bean 实例。下面的 figure 说明了 Spring 原型范围:
如上图所示,accountRepository
类被配置为原型 bean,每次将该 bean 注入其他 bean 时,容器都会创建一个全新的实例。
会话 bean 范围
仅在 web 环境中为 每个 用户会话创建一个新实例。
考虑以下 bean 定义的 XML 配置:
请求 bean 范围
一个新的 instance 仅在 Web 环境中为 每个 请求创建一次。
考虑以下 bean 定义的 XML 配置:
Spring中的其他范围
Spring还有其他more专门的作用域,如下:
- WebSocket scope
- Refresh scope
- Thread scope (defined, but not registered by default)
Spring 还支持为 bean 创建您自己的自定义范围。我们将在下一节讨论这个问题。
自定义范围
我们可以创建任何 bean 的自定义范围,并将此范围注册到 application 上下文中。让我们看看如何通过以下示例创建自定义 bean 范围。
创建自定义范围
为了在 Spring IoC 容器中创建 customer 范围,Spring 提供了 org.springframework.beans.factory.config.Scope
接口。您必须实现此接口才能创建自己的自定义范围。看看下面的 MyThreadScope
类作为 Spring IoC 容器中的自定义范围:
在上述代码中,我们重写了 Scope
接口的多个方法,如下所示:
- Object get(String name, ObjectFactory objectFactory): This method returns the object from the underlying scope
- Object remove(String name): This method removes the object from the underlying scope
- void registerDestructionCallback(String name, Runnable destructionCallback): This method registers the destruction callbacks, and is executed when the specified object with this custom scope is destroyed
现在让我们看看如何在 Spring IoC 容器中注册这个自定义范围,以及如何在 Spring 应用程序中使用它。
您可以使用 CustomScopeConfigurer
类以声明方式向 Spring IoC 容器注册此自定义 bean 范围,如下所示:
正如您在前面的配置文件中看到的那样,我已经使用 CustomScopeConfigurer
myThreadScope 的自定义 bean 范围> 类。我使用的这个自定义范围通过 XML 配置中 bean 标记的范围属性类似于单例或原型范围。
概括
读完本章,你现在应该对容器中的 Spring bean 生命周期以及容器中的几种 bean 作用域有了一个很好的了解。您现在知道容器中的 Spring bean 生命周期分为三个阶段。首先是初始化阶段。在这个阶段,Spring 从 XML、Java 或 Annotation 配置中加载 bean 定义。加载这些 bean 后,容器构造每个 bean,并在该 bean 上应用后处理逻辑。
接下来是使用阶段,在此阶段 Spring bean 已准备好使用,Spring 展示了代理模式的魔力。
最后,最后一个阶段是销毁阶段。在这个阶段,当应用调用 Spring 的 ApplicationContext
的 close()
方法时,容器会调用每个bean释放资源。
在 Spring 中,你不仅可以控制 bean 的生命周期,还可以控制 bean 在容器中的作用域。 Spring IoC 容器中 bean 的默认范围是 Singleton,但您可以使用 XML 中 bean 标记的 scope 属性或 使用 bean 定义其他范围原型来覆盖默认范围Java 中的@Scope
注释。您还可以创建自己的自定义范围,并将其注册到容器中。
现在我们转向本书的神奇篇章,即 Spring Aspect-Oriented Programming (<强>AOP)。就像依赖注入有助于将组件与它们协作的其他组件分离一样,AOP 有助于将应用程序组件与跨越应用程序中多个组件的任务分离。让我们继续下一章,介绍使用代理和装饰器设计模式的 Spring Aspect Oriented Programming。