Java 面试必备的 Spring Bean 生命周期总结

简介: 前言Spring 作为 IOC 容器,管理的对象称之为 bean,Java 对象在 ClassLoader 中有自己的创建和清理过程,那么 Spring Bean 在容器中也有自己的生命周期。

前言


Spring 作为 IOC 容器,管理的对象称之为 bean,Java 对象在 ClassLoader 中有自己的创建和清理过程,那么 Spring Bean 在容器中也有自己的生命周期。Spring Bean 的生命周期包括从诞生到销毁的整个过程,可以说,理解了 Spring Bean 的声明周期就理解了 Spring 容器对 bean 的管理。理解 Spring Bean 生命周期不仅便于我们在日常使用的对 Spring Bean 进行扩展,而且 Spring Bean 生命周期在面试中也是经常问到的一个考点。Spring Bean 生命周期各阶段遍布 BeanFactory 的各处,前面的文章中已经零零散散的对 Spring Bean 进行了介绍,下面围绕其生命周期进行分析。


Spring Bean 生命周期


Bean 元信息配置阶段


Spring 最早支持在 xml 中配置 Bean 的元信息,比较少用的还有 properties、groovy,这些 bean 元信息的配置都处于资源文件中,由于需要大量手工配置,因此目前使用比较多的是注解,配合类扫描使我们不必将配置元信息限制在某一文件中。


xml 文件配置 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="com.zzuhkp.MyService"></bean>
</beans>


和 xml 配置等价的注解配置如下。


@Configuration
public class Config {
    @Bean
    public MyService myService() {
        return new MyService();
    }
}


Bean 元信息解析阶段


bean 配置元信息只是记录了 bean 的相关信息,创建 bean 时需要先转换为统一的表示形式,在 Spring 中使用 BeanDefinition 类表示,这和 Java 类在 JVM 中使用 Class 类表示是类似的。关于 BeanDefinition 更深入的理解可参见 《掌握 Spring 必须知道的 BeanDefinition》。


对于 xml、properties、groovy 等保存 bean 元信息的文件,Spring 需要读取,对于注解的解析 Spring 同样需要读取 class 文件,因此 Spring 对资源进行了统一抽象,主要的类是 Resource 和 ResourceLoader,参见《Spring 资源管理 (Resource)》。


对于保存 bean 元信息的资源文件,只是其文件内容不同,因此 Spring 进行了抽象,参见下面的类图。


image.png


AbstractBeanDefinitionReader 将各种资源转换为 Resource,其子类分别从 Resource 中解析 bean,XmlBeanDefinitionReader 用于解析 xml 文件、PropertiesBeanDefinitionReader 用于解析 properties 文件,GroovyBeanDefinitionReader 用于解析 groovy 文件,使用这种方式可以很方便的进行扩展。


对于注解 Spring 使用 AnnotatedBeanDefinitionReader 进行解析,和其他解析资源文件的 BeanDefinitionReader 不同,它并未实现任何接口,直接父类就是 Object ,这是因为它处理的元数据位于遍布于不同的 class 文件中。


通过 API 手动解析 bean 元信息的示例如下所示。


public class App {
    public static void main(String[] args) {
    BeanDefinitionRegistry beanDefinitionRegistry = new DefaultListableBeanFactory();
        // 解析 xml 中的 bean 元信息
        BeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanDefinitionRegistry);
        beanDefinitionReader.loadBeanDefinitions("application.xml");
        // 解析注解中的 bean 元信息
        AnnotatedBeanDefinitionReader annotatedBeanDefinitionReader = new AnnotatedBeanDefinitionReader(beanDefinitionRegistry);
        annotatedBeanDefinitionReader.registerBean(Config.class);
    }
}


Bean 注册阶段


bean 注册是指将表示 bean 元信息的 BeanDefinition 保存在 spring 中,Spring 注册 BeanDefinition 的类为 BeanDefinitionRegistry ,使用 BeanDefinitionReader 加载或者注册 bean 的时候便会通过 BeanDefinitionRegistry#registerBeanDefinition 方法注册 BeanDefinition,Spring 内部 BeanDefinitionRegistry 的实现是 DefaultListableBeanFactory,使用了 一个 Map<String, BeanDefinition> 保存 BeanDefinition,其中键为 bean 的名称。


BeanDefinition 合并阶段


Spring 为了简化以及复用 xml 中的配置,为 bean 标签提供了一个 parent 的属性,可以用于指定 BeanDefinition 的父 BeanDefinition 的名称,这样就会继承其父 BeanDefinition 中的相关配置,并且子 BeanDefinition 可以覆盖父 BeanDefinition 中的相同配置,这和 Java 中的继承思想是相同的。AbstractBeanFactory#getMergedBeanDefinition 方法用于获取合并后的 BeanDefinition,使用 RootBeanDefinition 表示。


示例如下。


<?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 id="parentService" class="com.zzuhkp.ParentService">
        <property name="name" value="zzuhkp"/>
    </bean>
    <bean class="com.zzuhkp.MyService" parent="parentService">
        <property name="age" value="26"/>    
    </bean>
</beans>


配置后 MyService 将自动配置 ParentService 的 name 属性,注意 parent 并不要求一定是 bean 对应类的父类。


Bean 实例化阶段


Bean 的实例化阶段又分为实例化前、实例化中和实例化后三个小阶段,这是出于扩展的考虑,通过预留扩展点便于使用者自定义实例化的逻辑,具体如下。


Bean 实例化前阶段


Spring 预留 BeanPostProcessor 接口对初始化前和初始化后的 bean 进行处理,同时提供了 AbstractBeanFactory#addBeanPostProcessor 方法用于向 BeanFactory 中添加 BeanPostProcessor。


Spring 1.2 时,为了对 Bean 的实例化阶段进行扩展添加了继承 BeanPostProcessor 接口的 InstantiationAwareBeanPostProcessor 接口,在 bean 实例化前将调用 其#postProcessBeforeInstantiation 方法,方法定义如下。


public interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor {
  default Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
    return null;
  }
}


方法传入了 bean 对应的 Class 以及名称,返回了一个 Object 类型的对象,如果返回不为 null,则 表示使用返回的实例作为 bean,此时将忽略构造方法或者工厂方法创建 bean。


Bean 实例化中阶段


如果 Spring BeanFactory 中没有添加 InstantiationAwareBeanPostProcessor ,那么就会走正常的流程实例化 bean,此时需要根据参数解析出需要调用的构造方法或者工厂方法。不管是构造方法还是工厂方法最后都会委托到 InstantiationStrategy#instantiate,默认的 InstantiationStrategy 实现是 CglibSubclassingInstantiationStrategy,可用于创建 bean 时通过 Cglib 生成代理。实例化 bean 的具体代码,感兴趣的小伙伴可参见 AbstractAutowireCapableBeanFactory#createBeanInstance。


Bean 实例化后阶段


与 bean 实例化前的阶段相对应,InstantiationAwareBeanPostProcessor 接口同样提供了 bean 实例化后进行回调的方法#postProcessAfterInstantiation,不管 Spring bean 是通过 InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation 方法进行实例化,还是通过正常的流程进行实例化,都会调用该接口,该接口定义如下。


public interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor {
  default boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
    return true;
  }
}


该方法接收 bean 对象和 bean 的名称,返回了一个 boolean ,如果为 false ,则显式设置的属性或者通过 autowrie 设置的自动装配都将忽略。利用该方法,可以在 bean 实例化后设置一些其他的属性值。


Bean 属性赋值阶段


Bean 属性赋值阶段是将显式设置的依赖或者自动注入的依赖设置到 bean 对象中。


Spring 在属性赋值前同样预留了扩展点,InstantiationAwareBeanPostProcessor 接口提供了方法#postProcessPropertyValues ,该方法在将属性赋值给 bean 对象之前调用,可用于再次手动设置属性,在 Spring 5.1 时被标记为废弃,与此同时提供了一个方法#postProcessProperties。这两个方法的定义如下。


public interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor {
  default PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName)
      throws BeansException {
    return null;
  }
  default PropertyValues postProcessPropertyValues(
      PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException {
    return pvs;
  }
}


这两个方法都可以接收到表示 bean 属性的 PropertyValues ,#postProcessProperties 方法默认返回 null,此时将调用 #postProcessPropertyValues 方法,#postProcessPropertyValues 方法默认返回方法参数中的 PropertyValues 值,如果返回 null 将跳过属性的设置。


属性赋值前的回调之后随后就会通过反射正常的设置属性。


Bean 初始化阶段


Spring 初始化是指为了做初始化工作,调用某些初始化的方法。Spring 为 bean 的初始化同样预留了一些扩展点,这些扩展点分别代表着 bean 初始化的不同阶段。


Bean Aware 回调阶段


Spring BeanFactory 在 bean 初始化前首先会调用 Aware 相关的接口,代码位置为 AbstractAutowireCapableBeanFactory#invokeAwareMethods,对于 BeanFactory,会分别调用 BeanNameAware#setBeanName、BeanClassLoaderAware#setBeanClassLoader 以及 BeanFactoryAware#setBeanFactory 方法。


Bean 初始化前阶段


Bean 初始化前 Spring 将首先调用 BeanPostProcessor 的 postProcessBeforeInitialization 方法,方法定义如下。


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


这里将 bean 的实例和名称作为参数,然后返回新的 bean 实例,如果返回的值不为 null,那么返回的值将作为 bean 的实例。


Bean 初始化阶段


Bean 初始化即调用初始化方法,初始化方法的来源有多种。


@PostContruct:这个注解标注的方法将首先被调用,处理该注解的 BeanPostProcessor 为 CommonAnnotationBeanPostProcessor,实际使用了BeanPostProcessor #postProcessBeforeInitialization 回调方法来完成。

InitializingBean#afterPropertiesSet:如果 bean 实现了接口 InitializingBean,随后 Spring 便会调用该接口的 afterPropertiesSet 方法。

自定义初始化方法:最后调用的是在 xml 或者注解中指定的初始化方法,代码位置为 AbstractAutowireCapableBeanFactory#invokeCustomInitMethod。


Bean 初始化后阶段


bean 初始化后阶段与初始化前阶段相对应,Spring 初始化后将调用 BeanPostProcessor 接口的 postProcessAfterInitialization 方法,该方法的定义如下。


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

方法同样接收 bean 实例和 bean 名称,返回的为新使用的 bean 的名称。


Bean 初始化完成阶段


为了在所有的单例 bean 初始化后完成一些工作,Spring 提供了方法 SmartInitializingSingleton#afterSingletonsInstantiated,将我们的 bean 实现此接口即可,Spring 的事件处理 @EventListener 注解便是利用此回调实现。该方法的定义如下。


public interface SmartInitializingSingleton {
  void afterSingletonsInstantiated();
}


Bean 销毁阶段


通常情况我们不会手动对 bean 进行销毁,而是 Spring 应用上下文关闭之后才会 bean 进行自动销毁。Spring 将销毁阶段又划分为销毁前和销毁中阶段。


Bean 销毁前阶段


为了通知到用户 Spring 将对 bean 进行销毁,Spring 同样提供了扩展点,方法为DestructionAwareBeanPostProcessor#postProcessBeforeDestruction,该方法将在 Spring 指定销毁方法前调用,方法定义如下。


public interface DestructionAwareBeanPostProcessor extends BeanPostProcessor {
  void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException;
}


Bean 销毁中阶段


这个阶段是 Spring 真正销毁 bean 的阶段,会调用 bean 的销毁方法。和初始化阶段相呼应,调用的销毁方法从前到后顺序分别如下。


@PreDestroy :该注解同样为 CommonAnnotationBeanPostProcessor 处理,具体利用了 DestructionAwareBeanPostProcessor#postProcessBeforeDestruction。

DisposableBean#destroy:如果 bean 实现了接口 DisposableBean,随后 Spring 会调用该方法执行销毁。

自定义销毁方法:如果在 xml 或者注解中指定了自定义的销毁方法将在最后进行调用。


总结

Spring Bean 生命周期贯穿 Spring 对 bean 的整个管理过程,首先将定义的元信息解析为 BeanDefinition 后注册,然后分别进行实例化、初始化和销毁,中间又涉及到各种小的阶段和 Spring 提供的回调方法。文章内容根据源码阅读获取,欢迎大家评论指导。


目录
相关文章
|
4月前
|
机器学习/深度学习 人工智能 监控
Java与AI模型部署:构建企业级模型服务与生命周期管理平台
随着企业AI模型数量的快速增长,模型部署与生命周期管理成为确保AI应用稳定运行的关键。本文深入探讨如何使用Java生态构建一个企业级的模型服务平台,实现模型的版本控制、A/B测试、灰度发布、监控与回滚。通过集成Spring Boot、Kubernetes、MLflow和监控工具,我们将展示如何构建一个高可用、可扩展的模型服务架构,为大规模AI应用提供坚实的运维基础。
374 0
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
441 2
|
11月前
|
存储 监控 安全
重学Java基础篇—类的生命周期深度解析
本文全面解析了Java类的生命周期,涵盖加载、验证、准备、解析、初始化、使用及卸载七个关键阶段。通过分阶段执行机制详解(如加载阶段的触发条件与技术实现),结合方法调用机制、内存回收保护等使用阶段特性,以及卸载条件和特殊场景处理,帮助开发者深入理解JVM运作原理。同时,文章探讨了性能优化建议、典型异常处理及新一代JVM特性(如元空间与模块化系统)。总结中强调安全优先、延迟加载与动态扩展的设计思想,并提供开发建议与进阶方向,助力解决性能调优、内存泄漏排查及框架设计等问题。
494 5
|
Java API 调度
Java 线程的生命周期
在JDK 1.5之前,线程的生命周期包括五种状态:新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡(Dead)。JDK 1.5及之后增加了三种阻塞状态,共六种状态:新建、可运行、终止、锁阻塞、计时等待和无限等待。这些状态描述了线程在操作系统和JVM中的不同阶段。
402 4
Java 线程的生命周期
|
Java 关系型数据库 数据库
京东面试:聊聊Spring事务?Spring事务的10种失效场景?加入型传播和嵌套型传播有什么区别?
45岁老架构师尼恩分享了Spring事务的核心知识点,包括事务的两种管理方式(编程式和声明式)、@Transactional注解的五大属性(transactionManager、propagation、isolation、timeout、readOnly、rollbackFor)、事务的七种传播行为、事务隔离级别及其与数据库隔离级别的关系,以及Spring事务的10种失效场景。尼恩还强调了面试中如何给出高质量答案,推荐阅读《尼恩Java面试宝典PDF》以提升面试表现。更多技术资料可在公众号【技术自由圈】获取。
|
缓存 前端开发 Java
【Java面试题汇总】Spring,SpringBoot,SpringMVC,Mybatis,JavaWeb篇(2023版)
Soring Boot的起步依赖、启动流程、自动装配、常用的注解、Spring MVC的执行流程、对MVC的理解、RestFull风格、为什么service层要写接口、MyBatis的缓存机制、$和#有什么区别、resultType和resultMap区别、cookie和session的区别是什么?session的工作原理
【Java面试题汇总】Spring,SpringBoot,SpringMVC,Mybatis,JavaWeb篇(2023版)
|
缓存 Java 数据库
【Java面试题汇总】Spring篇(2023版)
IoC、DI、aop、事务、为什么不建议@Transactional、事务传播级别、@Autowired和@Resource注解的区别、BeanFactory和FactoryBean的区别、Bean的作用域,以及默认的作用域、Bean的生命周期、循环依赖、三级缓存、
【Java面试题汇总】Spring篇(2023版)
|
Java 调度
Java一个线程的生命周期详解
Java中,一个线程的生命周期分为五个阶段:NEW(新建),RUNNABLE(可运行),BLOCKED(阻塞),WAITING(等待),TERMINATED(终止)。线程创建后处于新建状态,调用start方法进入可运行状态,执行中可能因等待资源进入阻塞或等待状态,正常完成或异常终止后进入终止状态。各状态间可相互转换,构成线程的生命周期。
293 9
|
架构师 Java 开发者
得物面试:Springboot自动装配机制是什么?如何控制一个bean 是否加载,使用什么注解?
在40岁老架构师尼恩的读者交流群中,近期多位读者成功获得了知名互联网企业的面试机会,如得物、阿里、滴滴等。然而,面对“Spring Boot自动装配机制”等核心面试题,部分读者因准备不足而未能顺利通过。为此,尼恩团队将系统化梳理和总结这一主题,帮助大家全面提升技术水平,让面试官“爱到不能自已”。
得物面试:Springboot自动装配机制是什么?如何控制一个bean 是否加载,使用什么注解?
|
设计模式 缓存 Java
面试题:谈谈Spring用到了哪些设计模式?
面试题:谈谈Spring用到了哪些设计模式?
197 2