vlambda博客
学习文章列表

读书笔记《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-5-design-patterns》第 5 章 了解 Bean 生命周期和使用的模式

如上图所示,每个 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:
        ApplicationContext context = new    
        AnnotationConfigApplicationContext(AppConfig.class); 
  • For XML configuration, the implementation is as follows:
        ApplicationContext context = new  
        ClassPathXmlApplicationContext("applicationContext.xml"); 

上述代码中,Spring使用AnnotationConfigApplicationContext类加载Java配置文件,使用ClassPathXmlApplicationContext加载XML配置文件Spring 容器的类。对于所有类型的配置,Spring 的行为都是相同的。您在应用程序中使用什么配置样式并不重要。下图显示了这个阶段究竟发生了什么:

读书笔记《spring-5-design-patterns》第 5 章 了解 Bean 生命周期和使用的模式

如上图所示,初始化阶段分为以下两个步骤:

  • Load bean definitions
  • Initialize bean instances

加载 bean 定义

在这一步中,所有的配置文件——@Configuration 类或 XML 文件——都被处理。对于基于注解的配置,所有使用 @Components 注解的类都会被扫描以加载 bean 定义。解析所有 XML 文件,并将 bean 定义添加到 BeanFactory。每个 bean 都在其 id索引。 Spring 提供了多个 BeanFactoryPostProcessor bean,因此,调用它来解决运行时依赖关系,例如从外部属性文件中读取值。在 Spring 应用程序中,BeanFactoryPostProcessor 可以修改任何 bean 的定义。下图描述了此步骤:

读书笔记《spring-5-design-patterns》第 5 章 了解 Bean 生命周期和使用的模式

如上图所示,Spring首先加载bean的定义,然后对部分bean调用BeanFactoryProcessor来相应地修改其定义。让我们看一个例子。我们有两个配置文件——AppConfig.javaInfraConfig.java,定义如下:

  • Following is the AppConfig.java file:
        @Configuration 
        public class AppConfig { 
          @Bean 
          public TransferService transferService(){ ... } 
          @Bean 
          public AccountRepository accountRepository(DataSource 
          dataSource){ ... } 
        } 
  • Following is the InfraConfig.java file:
        @Configuration 
        public class InfraConfig { 
          @Bean 
          public DataSource dataSource () { ... } 
        } 

这些 Java 配置文件由 ApplicationContext 加载到容器中,并以其 id 为索引,如下图所示:

读书笔记《spring-5-design-patterns》第 5 章 了解 Bean 生命周期和使用的模式

在最后一个图中,Spring bean 在其 ID 下被索引到 Spring 的 BeanFactory 中,然后,该 BeanFactory 对象作为BeanFactoryPostProcessorpostProcess() 方法的参数。 BeanFactoryPostProcessor可以修改一些bean的bean定义;这取决于开发人员提供的 bean 配置。让我们看看 BeanFactoryPostProcessor 是如何工作的,以及如何在我们的应用程序中覆盖它:

  1. BeanFactoryPostProcessor works on the bean definitions or the configuration metadata of the bean before the beans are actually created.
  2. Spring provides several useful implementations of BeanFactoryPostProcessor, such as reading properties and registering a custom scope.
  3. You can write your own implementation of the BeanFactoryPostProcessor interface.
  4. If you define a BeanFactoryPostProcessor in one container, it will only be applied to the bean definitions in that container.

以下是 BeanFactoryPostProcessor 的代码片段:

    public interface BeanFactoryPostProcessor { 
      public void postProcessBeanFactory
        (ConfigurableListableBeanFactory 
        beanFactory); 
    } 

现在让我们看看 BeanFactoryPostProcessor 扩展点的以下示例:

读取外部属性文件(database.properties )

在这里,我们将使用 DataSource bean 来配置数据库值,例如 usernamepassworddb urldriver,如下:

    jdbc.driver=org.hsqldb.jdbcDriver 
    jdbc.url=jdbc:hsqldb:hsql://production:9002 
    jdbc.username=doj 
    jdbc.password=doj@123 

以下是配置文件中的DataSource bean定义:

    @Configuration 
    @PropertySource ( "classpath:/config/database.properties" ) 
    public class InfraConfig { 
     @Bean 
     public DataSource dataSource( 
     @Value("${jdbc.driver}") String driver, 
     @Value("${jdbc.url}") String url, 
     @Value("${jdbc.user}") String user, 
     @Value("${jdbc.password}") String pwd) { 
       DataSource ds = new BasicDataSource(); 
       ds.setDriverClassName( driver); 
       ds.setUrl( url); 
       ds.setUser( user); 
       ds.setPassword( pwd )); 
       return ds; 
    } 
   } 

那么,在前面的代码中,我们如何解析 @Value${..} 变量呢?我们需要一个 PropertySourcesPlaceholderConfigurer 来评估它们。这是一个 BeanFactoryPostProcessor。如果您使用 XML 配置, 命名空间会为您创建一个 PropertySourcesPlaceholderConfigurer

加载 bean 定义在加载配置文件时是一次性的过程,但 bean 实例的初始化阶段是针对容器中的每个 bean 执行的。让我们看一下应用程序中bean实例的初始化。

初始化 bean 实例

将 bean definitions 加载到 BeanFactory 后,Spring IoC 容器为应用程序实例化 bean;下图显示了处理流程:

读书笔记《spring-5-design-patterns》第 5 章 了解 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 as BeanFactoryPostProcessor, which can modify the bean definition. However, the BeanPostProcessor 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接口有两个回调方法,如下:

    public interface BeanPostProcessor { 
      Object postProcessBeforeInitialization(Object bean, String 
      beanName) throws BeansException; 
      Object postProcessAfterInitialization(Object bean, String 
      beanName) throws BeansException; 
    } 

您可以实现 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的之前调用postProcessBeforeInitialization()方法class="literal">init 方法)。它还在任何 bean 初始化回调之后调用 postProcessAfterInitialization() 方法。 Spring AOP 使用 post-processor 来提供代理包装逻辑(代理设计模式),尽管我们可以使用 post-处理器

Spring 的 ApplicationContext 会自动检测那些实现了 BeanPostProcessor 接口的 bean,并将这些 bean 注册为 post -处理器 。这些 bean 在创建任何其他 bean 时被调用。让我们探索以下 BeanPostProcessor 的示例。

让我们创建一个自定义 bean post-processor,如下所示:

    package com.packt.patterninspring.chapter5.bankapp.bpp; 
    import org.springframework.beans.BeansException; 
    import org.springframework.beans.factory.config.BeanPostProcessor; 
    import org.springframework.stereotype.Component; 
    @Component 
    public class MyBeanPostProcessor implements 
    BeanPostProcessor { 
      @Override 
      public Object postProcessBeforeInitialization
      (Object bean, String beanName) throws BeansException { 
        System.out.println("In After bean Initialization 
        method. Bean name is "+beanName); 
        return bean; 
      } 
      public Object postProcessAfterInitialization(Object bean, String  
      beanName) throws BeansException { 
        System.out.println("In Before bean Initialization method. Bean 
        name is "+beanName); 
        return bean; 
        } 
   }  

这个例子说明了基本用法,这里这个例子显示了一个 post-processor 为每个注册到容器的 bean 将字符串打印到系统控制台。这个 MyBeanPostProcessor 类用 @Component 注释,这意味着这个类与应用程序上下文中的其他 bean 类相同,现在运行以下演示班级。请参考以下代码:

    public class BeanLifeCycleDemo { 
      public static void main(String[] args) { 
        ConfigurableApplicationContext applicationContext = new 
        AnnotationConfigApplicationContext(AppConfig.class); 
        applicationContext.close(); 
      } 
    }

这是我们将在控制台上得到的输出:

读书笔记《spring-5-design-patterns》第 5 章 了解 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) CommonAnnotationBeanPostProcessorenable 初始化。下图说明了 initializer 和 BPP 之间的关系。

读书笔记《spring-5-design-patterns》第 5 章 了解 Bean 生命周期和使用的模式

现在让我们看一下 XML 中 Initializer 扩展点的以下示例:

命名空间 显式启用许多 post-processor,让我们看看下面的 XML 配置文件:

    <?xml version="1.0" encoding="UTF-8"?> 
    <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:c="http://www.springframework.org/schema/c" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/ spring-context-4.3.xsd"> 
     <context:annotation-config/> 
     <bean id="transferService" class="com.packt.patterninspring.chapter5. bankapp.service.TransferService"/> 
     <bean id="accountRepository" class="com.packt.patterninspring.chapter5. bankapp.repository.JdbcAccountRepository" init-method="populateCache"/> 
    </beans> 

在前面的配置代码中,您可以看到我已经定义了一些 bean,其中一个 bean accountRepository 存储库具有 init bean标签的方法属性;这个属性有一个值,populateCache。这不过是 accountRepository bean 的 initializer 方法。如果 post-processor ,则容器在 bean 初始化时调用它> 命名空间。我们来看JdbcAccountRepository类,如下所示:

    package com.packt.patterninspring.chapter5.bankapp.repository; 
    import com.packt.patterninspring.chapter5.bankapp.model.Account; 
    import com.packt.patterninspring.chapter5.bankapp.model.Amount; 
    import com.packt.patterninspring.chapter5.
    bankapp.repository.AccountRepository; 
    public class JdbcAccountRepository implements AccountRepository { 
      @Override 
      public Account findByAccountId(Long accountId) { 
        return new Account(accountId, "Arnav Rajput", new  
        Amount(3000.0)); 
    } 
    void populateCache(){ 
      System.out.println("Called populateCache() method"); 
    } 
   }

在Java配置中,我们可以使用@Bean注解的initMethod属性如下:

    @Bean(initMethod = "populateCache") 
    public AccountRepository accountRepository(){ 
      return new JdbcAccountRepository(); 
    }

在基于注解的配置中,我们可以使用 JSR-250 注解,@PostConstruct 为如下:

    @PostConstruct 
    void populateCache(){ 
      System.out.println("Called populateCache() method"); 
    } 

我们已经看到了 bean 生命周期的第一阶段,Spring 使用基于 XML、Java 和 Annotation 的配置加载 bean 定义,之后,Spring 容器在 Spring 应用程序中以正确的顺序初始化每个 bean .下图概述了配置生命周期的第一阶段:

读书笔记《spring-5-design-patterns》第 5 章 了解 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。请参考以下代码:

    //Get or create application context from somewhere 
    ApplicationContext applicationContext = new    
    AnnotationConfigApplicationContext(AppConfig.class); 
 
    // Lookup the entry point into the application 
    TransferService transferService =    
    context.getBean(TransferService.class); 
    // and use it 
    transferService.transfer("A", "B", 3000.1); 

假设 return 服务返回一个原始对象,那么就直接调用它;这里没什么特别的。但是如果你的 bean 已经被包裹在一个代理中,那么事情就会变得更加有趣。让我们探索下图以更清楚地理解这一点:

读书笔记《spring-5-design-patterns》第 5 章 了解 Bean 生命周期和使用的模式

在上图中,可以看到通过Proxy类调用service方法;它由专用的 BeanPostProcessorinit 阶段创建。它将您的 bean 包装在一个动态代理中,从而透明地为您的 bean 添加行为。它是装饰器设计模式和代理设计模式的实现。

让我们看看 Spring 如何在 Spring 应用程序中为您的 bean 创建代理。

使用代理在 Spring 中实现装饰器和代理模式

Spring 在 Spring 应用程序中使用两种类型的代理。以下是 Spring 使用的 proxieskind

  • 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-5-design-patterns》第 5 章 了解 Bean 生命周期和使用的模式

这就是 Spring Bean 生命周期的使用阶段。现在让我们进入生命周期的下一个阶段,即销毁阶段。

bean 的销毁阶段

在这个阶段,Spring 释放应用服务获取的所有系统resource。这些有资格进行垃圾收集。当您关闭应用程序上下文时,destruction 阶段完成。让我们看看这个阶段的以下代码行:

    //Any implementation of application context 
 
    ConfigurableApplicationContext applicationContext = new   
    AnnotationConfigApplicationContext(AppConfig.class); 
 
    // Destroy the application by closing application context. 
    applicationContext.close(); 

在上面的代码中,你觉得我们在这个阶段调用 applicationContext.close() 方法会发生什么?发生的过程如下:

  • Any bean implementing the org.springframework.beans.factory.DisposableBean interface gets a callback from the container when it is destroyed. The DisposableBean interface specifies a single method:
        void destroy() throws Exception; 
  • The bean instances are destroyed if instructed to call their destroy methods. Beans must have a destroy method defined, that is, a no-arg method returning void.
  • 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 配置来实现它:

    <?xml version="1.0" encoding="UTF-8"?> 
    <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:c="http://www.springframework.org/schema/c" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context- 4.3.xsd"> 
      <context:annotation-config/> 
      <bean id="transferService" class="com.packt.patterninspring.chapter5. bankapp.service.TransferService"/> 
      <bean id="accountRepository" class="com.packt.patterninspring.chapter5. bankapp.repository.JdbcAccountRepository" destroy-method="clearCache"/> 
   </beans> 

在配置中,accountRepository bean 有一个名为 clearCachedestroy 方法:

    package com.packt.patterninspring.chapter5.bankapp.repository; 
    import com.packt.patterninspring.chapter5.bankapp.model.Account; 
    import com.packt.patterninspring.chapter5.bankapp.model.Amount; 
    import com.packt.patterninspring.chapter5.bankapp.
      repository.AccountRepository; 
    public class JdbcAccountRepository implements AccountRepository { 
     @Override 
    public Account findByAccountId(Long accountId) { 
      return new Account(accountId, "Arnav Rajput", new
      Amount(3000.0)); 
    } 
    void clearCache(){ 
      System.out.println("Called clearCache() method"); 
    } 
   } 

让我们看一下与 Java 相同的配置。在Java配置中,我们可以使用@Bean注解的destroyMethod属性如下:

    @Bean (destroyMethod="clearCache") 
    public AccountRepository accountRepository() { 
      return new JdbcAccountRepository(); 
    } 

我们可以使用注释来做同样的事情。注释需要 annotation-config 或使用 <context:component-scan ... />,如下所示

    public class JdbcAccountRepository { 
      @PreDestroy 
      void clearCache() { 
        // close files, connections... 
        // remove external resources... 
      } 
    } 

您现在已经看到了 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。这在下图中解释

读书笔记《spring-5-design-patterns》第 5 章 了解 Bean 生命周期和使用的模式

正如您在上图中所见,该对象的同一实例由 bean 定义 accountRepository 定义,注入到同一 IoC 容器中的其他协作 bean。 Spring 将所有单例 bean 实例存储在缓存中,所有协作 bean 获取缓存返回的该对象的依赖关系。

原型 bean 作用域

在 Spring 中,使用原型 scope 定义的任何 bean 都会在每次将 bean 注入其他协作 bean 时创建一个 bean 实例。下面的 figure 说明了 Spring 原型范围:

读书笔记《spring-5-design-patterns》第 5 章 了解 Bean 生命周期和使用的模式

如上图所示,accountRepository 类被配置为原型 bean,每次将该 bean 注入其他 bean 时,容器都会创建一个全新的实例。

会话 bean 范围

仅在 web 环境中为 每个 用户会话创建一个新实例。

考虑以下 bean 定义的 XML 配置:

    <bean id="..." class="..." scope="session"/> 

请求 bean 范围

一个新的 instance 仅在 Web 环境中为 每个 请求创建一次。

考虑以下 bean 定义的 XML 配置:

    <bean id="..." class="..." scope="request"/>

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 容器中的自定义范围:

    package com.packt.patterninspring.chapter5.bankapp.scope; 
    import java.util.HashMap; 
    import java.util.Map; 
    import org.springframework.beans.factory.ObjectFactory; 
    import org.springframework.beans.factory.config.Scope; 
 
    public class MyThreadScope implements Scope { 
      private final ThreadLocal<Object> myThreadScope = new
      ThreadLocal<Object>() { 
        protected Map<String, Object> initialValue() { 
          System.out.println("initialize ThreadLocal"); 
          return new HashMap<String, Object>(); 
         } 
       }; 
    @Override 
    public Object get(String name, ObjectFactory<?> objectFactory) { 
      Map<String, Object> scope = (Map<String, Object>)
      myThreadScope.get(); 
      System.out.println("getting object from scope."); 
      Object object = scope.get(name); 
      if(object == null) { 
        object = objectFactory.getObject(); 
        scope.put(name, object); 
      } 
      return object; 
    } 
    @Override 
    public String getConversationId() { 
      return null; 
    } 
    @Override 
    public void registerDestructionCallback(String name, Runnable
    callback) { 
    
    } 
    @Override 
    public Object remove(String name) { 
      System.out.println("removing object from scope."); 
      @SuppressWarnings("unchecked") 
      Map<String, Object> scope = (Map<String, Object>)
      myThreadScope.get(); 
      return scope.remove(name); 
     } 
     @Override 
     public Object resolveContextualObject(String name) { 
       return null; 
     } 
    } 

在上述代码中,我们重写了 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 范围,如下所示:

    <?xml version="1.0" encoding="UTF-8"?> 
    <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> 
      <bean class="org.springframework.beans.factory. config.CustomScopeConfigurer"> 
      <property name="scopes"> 
        <map> 
          <entry key="myThreadScope"> 
            <bean class="com.packt.patterninspring.chapter5. bankapp.scope.MyThreadScope"/> 
          </entry> 
        </map> 
      </property> 
     </bean> 
     <bean id="myBean" class="com.packt.patterninspring.chapter5. bankapp.bean.MyBean" scope="myThreadScope">  
      <property name="name" value="Dinesh"></property> 
    </bean> 
    </beans> 

正如您在前面的配置文件中看到的那样,我已经使用 CustomScopeConfigurermyThreadScope 的自定义 bean 范围> 类。我使用的这个自定义范围通过 XML 配置中 bean 标记的范围属性类似于单例或原型范围。

概括


读完本章,你现在应该对容器中的 Spring bean 生命周期以及容器中的几种 bean 作用域有了一个很好的了解。您现在知道容器中的 Spring bean 生命周期分为三个阶段。首先是初始化阶段。在这个阶段,Spring 从 XML、Java 或 Annotation 配置中加载 bean 定义。加载这些 bean 后,容器构造每个 bean,并在该 bean 上应用后处理逻辑。

接下来是使用阶段,在此阶段 Spring bean 已准备好使用,Spring 展示了代理模式的魔力。

最后,最后一个阶段是销毁阶段。在这个阶段,当应用调用 Spring 的 ApplicationContextclose() 方法时,容器会调用每个bean释放资源。

在 Spring 中,你不仅可以控制 bean 的生命周期,还可以控制 bean 在容器中的作用域。 Spring IoC 容器中 bean 的默认范围是 Singleton,但您可以使用 XML 中 bean 标记的 scope 属性或 使用 bean 定义其他范围原型来覆盖默认范围Java 中的@Scope 注释。您还可以创建自己的自定义范围,并将其注册到容器中。

现在我们转向本书的神奇篇章,即 Spring Aspect-Oriented Programming (<强>AOP)。就像依赖注入有助于将组件与它们协作的其他组件分离一样,AOP 有助于将应用程序组件与跨越应用程序中多个组件的任务分离。让我们继续下一章,介绍使用代理和装饰器设计模式的 Spring Aspect Oriented Programming。