Bean的生命周期

在前 3 节中已讲过 IOC 的基本知识点,相信读者已经能够在基本的开发中运用这些知识点。但是为了更好地理解自己写的代码,或者让自己成为更加高级的工程师,还需要理解 Bean 的生命周期。

Bean 的生命周期主要包括四个部分:Bean 的定义、初始化、生存期、销毁。每个部分 Spring 都提供了扩展点,方便用户根据自己的需求加入扩展逻辑,这也是继续介绍的主要原因。

下面主要介绍两个知识点,一是 Bean 的定义,二是 Spring Bean 的生命周期。

Bean的初始化过程

在开始讲解之前,先看初始化 Bean 的一个流程,如图4.7所示。

image 2024 03 31 15 55 09 199
Figure 1. 图4.7 Bean的初始化

首先,使用扫描注解的方式找到需要加载的信息,这个是资源的定位过程。然后,容器依赖工具类 BeanDefinitionReader 对加载的配置信息进行解析,并将解析后的带有 Bean 定义的必要信息编写为相应的 BeanDefinition,再把 BeanDefinition 注册到 BeanDifinitionRegistry,这是 Bean 定义的过程。

之后,将 Bean 的定义发布到 IOC 容器中。当请求方通过容器的 getBean 方法明确地请求某个对象,或者因依赖关系容器需要隐式地调用 getBean 方法时,就会触发 Bean 初始化。容器会先检查对象之前是否已经初始化,如果没有会根据注册的 BeanDefinition 初始化请求对象,并为其注入依赖。

以上就是初始化 Bean 的流程,为了更好地理解,下面使用实例进行说明。首先,修改 Boss 类,代码如下所示。

package com.springBoot.ioc.pojo.impl;

@Component("boss")
public class Boss implements People {
   private Animal animal=null;
   //构造函数
   public Boss(){
      System.out.println("Boss的构造函数初始化");
   }
   @Override
   public void use() {
      animal.eat();
   }
   //依赖注入animal
   @Override
   @Autowired
   @Qualifier("cat")
   public void setAnimal(Animal animal) {
      this.animal=animal;
      System.out.println("依赖注入animal");
   }
}

上面的代码中,添加了构造函数,以及改进了 Animal 注入的方式。现在代码中有三个重要的部分,构造函数、功能函数、依赖注入方法,在后面会依次讲解。然后,开始测试,代码如下所示。

package com.springBoot.ioc.test;
/**
  * @Desciption IOC实例测试类
  */
public class IocTestDemo {
      private static final Logger log=LoggerFactory. getLogger(IocTestDemo.class);
   public static void main(String[] args) {
      ApplicationContext context=new AnnotationConfigApplicationContext(MySpringBootConfig.class);
      People boss=context.getBean(Boss.class);
      boss.use();
   }
}

下面,先设置一个断点,以便调试,结果如图4.8所示。

image 2024 03 31 15 57 14 352
Figure 2. 图4.8 执行结果

在图4.8中,有两个地方被框出来,说明虽然还没有运行 getBean 方法,但完成了对 Bean 的初始化以及依赖注入。

那么能不能在使用时,再进行初始化与依赖注入?这肯定没问题,在下一小节用实例说明,现在先让我们的断点程序继续运行下去。效果如下所示。

Boss的构造函数初始化
……
- Autowiring by type from bean name 'boss' to bean named 'cat'
依赖注入animal
……
22:23:35.557[main]DEBUGorg.springframework.beans.factory.support. DefaultListableBeanFactory - Returning cached instance of singleton bean 'boss'
猫吃鱼

在执行的结果中可以看到,首先通过构造函数来创建 Bean 的实例,然后进行依赖注入(setter 方法设置属性,这种方式比直接使用 @Autowired 方便观察),最后使用 Bean 实例。

Bean的延迟初始化

在图4.8的断点效果中,可以看到没运行 getBean 之前,就完成了依赖注入。如果我们想在使用时再进行依赖注入,也可以实现,即在使用注解 @ComponentScan 标注时设置属性 lazyInit。

在默认情况下,lazyInit=false,现在只需要将 false 改为 true 即可。代码如下所示。

package com.springBoot.ioc.config;
/**
  * @Desciption 配置文件
  */
@Configuration
@ComponentScan(basePackages="com.springBoot.ioc.*",
        excludeFilters = {@ComponentScan.Filter(type = FilterType. ANNOTATION,classes = Controller.class)},lazyInit = true)
public class MySpringBootConfig{
}

在上面的代码中可以看到有一个属性 “lazyInit=true”,也就是说在扫描时,将这个 Bean 定义为延迟初始化。这里就不演示,只要在 getBean 方法的前面设置一个断点,运行测试类,就会在日志中发现 Boss 类中的三段代码没有执行。

Bean的生命周期

在4.4.1小节中有一个 Bean 初始化的过程流程图,它是 Bean 生命周期的一个部分,总体来说不难理解,但它不能进行太多的自定义配置。下面我们来看一个完整的生命周期的流程,如图4.9所示。图中有一些方法、接口在自定义中可以根据需要进行扩展。

image 2024 03 31 16 00 01 776
Figure 3. 图4.9 Bean的生命周期

图4.9是一个完整的 Bean 的生命周期,从一个 Bean 的初始化到 Destory,能够实现生命周期中所有的可以实现的接口。

其中,BeanPostProcessor 接口是针对所有的 Bean 而言。为什么要有这个?因为要在 Spring 容器中完成 Bean 初始化、配置以及其他初始化方法的前后需要一些逻辑处理,这时就需要定义一个或多个 BeanPostProcessor 接口实现类,装配到 Spring IOC 容器中。BeanPostProcessor 接口的两个方法分别是 postProcessBeforeInitialization 与 postProcessAfterInitialization。

首先,改造 Boss 类,让这个类实现 Bean 生命周期所有可以实现的接口,但暂时不实现 BeanPostProcessor 接口。全部实现的好处是在输出时,可以了解 Bean 执行的先后顺序。代码如下所示。

package com.springBoot.ioc.pojo.impl;
/**
  * Bean实现生命周期中所有的可以实现的接口,方便看Bean的生命周期
  */
@Component(value="boss")
public class Boss implements People,BeanNameAware,BeanFactoryAware, ApplicationContextAware,InitializingBean,DisposableBean {
   private Animal animal=null;
   //构造函数
   public Boss(){
      System.out.println("Boss的构造函数初始化");
   }
   //功能函数
   @Override
   public void use() {
      animal.eat();
   }
   //依赖注入animal
   @Autowired
   @Qualifier("cat")
   public void setAnimal(Animal animal) {
      this.animal=animal;
      System.out.println("依赖注入animal");
   }
   //BeanNameAware接口需要实现的方法
   @Override
   public void setBeanName(String name) {
        System.out.println(this.getClass().getSimpleName()+" 调用BeanNameAware接口中的setBeanName方法");
   }
   //BeanFactoryAware接口需要实现的方法
   @Override
   public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        System.out.println(this.getClass().getSimpleName()+" 调用BeanFactoryAware接口中的setBeanFactory方法");
   }
   //ApplicationContextAware接口需要实现的方法
   @Override
   public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        System.out.println(this.getClass().getSimpleName()+" 调用ApplicationContextAware接口中的setApplicationContext方法");
   }
   //InitializingBean接口需要实现的方法
   @Override
   public void afterPropertiesSet() throws Exception {
        System.out.println(this.getClass().getSimpleName()+" 调用InitializingBean接口中的afterPropertiesSet方法");
   }
   //DisposableBean接口需要实现的方法
   @Override
   public void destroy() throws Exception {
        System.out.println(this.getClass().getSimpleName()+" 调用DisposableBean接口中的Destory方法");
   }
   //PostConstruct注解
   @PostConstruct
   public void init(){
        System.out.println(this.getClass().getSimpleName()+" 调用@PostConstruct中的init方法");
   }
   //PreDestory注解
   @PreDestory
   public void preDestroy(){
        System.out.println(this.getClass().getSimpleName()+" 调用@PreDestory中的preDestory方法");
   }
}

为了演示 BeanPostProcessor 的接口,另写一个 Bean 进行扫描。代码如下所示。

package com.springBoot.ioc.pojo;

@Component
public class PostProcessBeanDemo implements BeanPostProcessor {
   @Override
      public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
          System.out.println(this.getClass().getSimpleName()+" 调用BeanPostProcessor接口中的postProcessBeforeInitialization方法");
      return bean;
   }
   @Override
      public Object postProcessAfterInitialization(Object bean,
String beanName) throws BeansException {
        System.out.println(this.getClass().getSimpleName()+" 调用BeanPostProcessor接口中的postProcessAfterInitialization方法");
      return bean;
   }
}

这样,我们就可以进行测试看效果了。以前的测试类中没有 close,不能看到 Bean 的销毁过程,所以对测试类稍做修改,代码如下所示。

public class IocTestDemo {
   private static final Logger log=LoggerFactory.getLogger(IocTestDemo. class);
   public static void main(String[] args) {
      ApplicationContext context=new AnnotationConfigApplicationCo ntext(MySpringBootConfig.class);
      People boss=context.getBean(Boss.class);
      boss.use();
      ((AnnotationConfigApplicationContext) context).close();
   }
}

最后,把程序运行起来,这里删除了一些日志,只留下输出内容,效果如下所示。

Boss的构造函数初始化
依赖注入animal
Boss调用BeanNameAware接口中的setBeanName方法
Boss调用BeanFactoryAware接口中的setBeanFactory方法
Boss调用ApplicationContextAware接口中的setApplicationContext方法
PostProcessBeanDemo调用BeanPostProcessor接口中的postProcessBeforeInitialization方法
Boss调用@PostConstruct中的init方法
Boss调用InitializingBean接口中的afterPropertiesSet方法
猫吃鱼
Boss调用@PreDestory中的preDestory方法
Boss调用DisposableBean接口中的Destory方法

对于这个结果,结合流程图做一些解释,其实这里输出的结果正好是对应流程中的每一个步骤。

  1. 进行初始化,输出的语句是 “Boss的构造函数初始化”。

  2. 依赖注入,输出语句是 “依赖注入animal”。

  3. 调用接口中 setBeanName 方法,因为是实现了 BeanNameAware 接口。

  4. 调用接口中的 setBeanFactory 方法,因为实现了 BeanFactoryAware 接口。

  5. 调用接口中的 setApplicationContext 方法,因为实现了 ApplicationContextAware 接口。

  6. 调用 BeanPostProcessor 接口中的 postProcessBeforeInitialization 方法。

  7. 调用被 @PostConstruct 注解过的方法,在 Boss 类中被注解的方法是 init。

  8. 调用 InitializingBean 接口中的 afterPropertiesSet 方法。

  9. 调用功能函数。

  10. 调用 @PreDestory 注解的 preDestory 方法。

  11. 调用 DisposableBean 接口中的 Destory 方法。

在生命周期内容中,需再解释一点,在控制台上可以看到 “Boss调用ApplicationContextAware 接口中的 setApplicationContext 方法”,说明 IOC 容器实现了 ApplicationContext 接口,为了更加直观,我们可以看图 4.3 进行理解。因为 IOC 容器不一定实现 ApplicationContext 接口,而只是简单实现 BeanFactory 接口。但是从图中可以发现, AnotationConfigContext 实现了 ApplicationContext 接口。所以在 Spring Boot 中,Bean 实现 ApplicationContextAware 接口后,里面的方法一定会执行。