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

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
简介: 前言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 提供的回调方法。文章内容根据源码阅读获取,欢迎大家评论指导。


目录
相关文章
|
7天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
22 2
|
12天前
|
存储 算法 Java
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
本文详解自旋锁的概念、优缺点、使用场景及Java实现。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
|
14天前
|
缓存 Java Spring
实战指南:四种调整 Spring Bean 初始化顺序的方案
本文探讨了如何调整 Spring Boot 中 Bean 的初始化顺序,以满足业务需求。文章通过四种方案进行了详细分析: 1. **方案一 (@Order)**:通过 `@Order` 注解设置 Bean 的初始化顺序,但发现 `@PostConstruct` 会影响顺序。 2. **方案二 (SmartInitializingSingleton)**:在所有单例 Bean 初始化后执行额外的初始化工作,但无法精确控制特定 Bean 的顺序。 3. **方案三 (@DependsOn)**:通过 `@DependsOn` 注解指定 Bean 之间的依赖关系,成功实现顺序控制,但耦合性较高。
实战指南:四种调整 Spring Bean 初始化顺序的方案
|
13天前
|
存储 缓存 Java
大厂面试必看!Java基本数据类型和包装类的那些坑
本文介绍了Java中的基本数据类型和包装类,包括整数类型、浮点数类型、字符类型和布尔类型。详细讲解了每种类型的特性和应用场景,并探讨了包装类的引入原因、装箱与拆箱机制以及缓存机制。最后总结了面试中常见的相关考点,帮助读者更好地理解和应对面试中的问题。
39 4
|
14天前
|
存储 Java 程序员
Java基础的灵魂——Object类方法详解(社招面试不踩坑)
本文介绍了Java中`Object`类的几个重要方法,包括`toString`、`equals`、`hashCode`、`finalize`、`clone`、`getClass`、`notify`和`wait`。这些方法是面试中的常考点,掌握它们有助于理解Java对象的行为和实现多线程编程。作者通过具体示例和应用场景,详细解析了每个方法的作用和重写技巧,帮助读者更好地应对面试和技术开发。
54 4
|
13天前
|
安全 Java 测试技术
Java开发必读,谈谈对Spring IOC与AOP的理解
Spring的IOC和AOP机制通过依赖注入和横切关注点的分离,大大提高了代码的模块化和可维护性。IOC使得对象的创建和管理变得灵活可控,降低了对象之间的耦合度;AOP则通过动态代理机制实现了横切关注点的集中管理,减少了重复代码。理解和掌握这两个核心概念,是高效使用Spring框架的关键。希望本文对你深入理解Spring的IOC和AOP有所帮助。
28 0
|
3月前
|
存储 Java
【IO面试题 四】、介绍一下Java的序列化与反序列化
Java的序列化与反序列化允许对象通过实现Serializable接口转换成字节序列并存储或传输,之后可以通过ObjectInputStream和ObjectOutputStream的方法将这些字节序列恢复成对象。
|
1月前
|
算法 Java 数据中心
探讨面试常见问题雪花算法、时钟回拨问题,java中优雅的实现方式
【10月更文挑战第2天】在大数据量系统中,分布式ID生成是一个关键问题。为了保证在分布式环境下生成的ID唯一、有序且高效,业界提出了多种解决方案,其中雪花算法(Snowflake Algorithm)是一种广泛应用的分布式ID生成算法。本文将详细介绍雪花算法的原理、实现及其处理时钟回拨问题的方法,并提供Java代码示例。
72 2
|
1月前
|
JSON 安全 前端开发
第二次面试总结 - 宏汉科技 - Java后端开发
本文是作者对宏汉科技Java后端开发岗位的第二次面试总结,面试结果不理想,主要原因是Java基础知识掌握不牢固,文章详细列出了面试中被问到的技术问题及答案,包括字符串相关函数、抽象类与接口的区别、Java创建线程池的方式、回调函数、函数式接口、反射以及Java中的集合等。
29 0
|
3月前
|
XML 存储 JSON
【IO面试题 六】、 除了Java自带的序列化之外,你还了解哪些序列化工具?
除了Java自带的序列化,常见的序列化工具还包括JSON(如jackson、gson、fastjson)、Protobuf、Thrift和Avro,各具特点,适用于不同的应用场景和性能需求。