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

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


目录
相关文章
|
1月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
76 2
|
25天前
|
Java 开发者 微服务
Spring Boot 入门:简化 Java Web 开发的强大工具
Spring Boot 是一个开源的 Java 基础框架,用于创建独立、生产级别的基于Spring框架的应用程序。它旨在简化Spring应用的初始搭建以及开发过程。
46 6
Spring Boot 入门:简化 Java Web 开发的强大工具
|
26天前
|
Java 程序员
Java社招面试题:& 和 && 的区别,HR的套路险些让我翻车!
小米,29岁程序员,分享了一次面试经历,详细解析了Java中&和&&的区别及应用场景,展示了扎实的基础知识和良好的应变能力,最终成功获得Offer。
66 14
|
1月前
|
存储 缓存 算法
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!
本文介绍了多线程环境下的几个关键概念,包括时间片、超线程、上下文切换及其影响因素,以及线程调度的两种方式——抢占式调度和协同式调度。文章还讨论了减少上下文切换次数以提高多线程程序效率的方法,如无锁并发编程、使用CAS算法等,并提出了合理的线程数量配置策略,以平衡CPU利用率和线程切换开销。
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!
|
1月前
|
Java 编译器 程序员
Java面试高频题:用最优解法算出2乘以8!
本文探讨了面试中一个看似简单的数学问题——如何高效计算2×8。从直接使用乘法、位运算优化、编译器优化、加法实现到大整数场景下的处理,全面解析了不同方法的原理和适用场景,帮助读者深入理解计算效率优化的重要性。
35 6
|
1月前
|
Java 数据库连接 API
Spring 框架的介绍(Java EE 学习笔记02)
Spring是一个由Rod Johnson开发的轻量级Java SE/EE一站式开源框架,旨在解决Java EE应用中的多种问题。它采用非侵入式设计,通过IoC和AOP技术简化了Java应用的开发流程,降低了组件间的耦合度,支持事务管理和多种框架的无缝集成,极大提升了开发效率和代码质量。Spring 5引入了响应式编程等新特性,进一步增强了框架的功能性和灵活性。
49 0
|
XML JSON 前端开发
Spring 面试 7 大问题,你顶得住不?
Spring 面试 7 大问题,你顶得住不?
233 0
Spring 面试 7 大问题,你顶得住不?
|
XML Java 数据格式
Spring 面试问题 TOP 50
Spring Framework 现在几乎已成为 Java Web 开发的标配框架。那么,作为 Java 程序员,你对 Spring 的主要技术点又掌握了多少呢?不妨用本文的问题来检测一下。
1327 0
|
XML Java 数据格式
Spring面试基本问题(1)
1、什么是Spring框架?Spring框架有哪些主要模块? Spring框架是一个为Java应用程序的开发提供了综合、广泛的基础性支持的Java平台。Spring帮助开发者解决了开发中基础性的问题,使得开发人员可以专注于应用程序的开发。
1957 0
|
2月前
|
人工智能 自然语言处理 前端开发
SpringBoot + 通义千问 + 自定义React组件:支持EventStream数据解析的技术实践
【10月更文挑战第7天】在现代Web开发中,集成多种技术栈以实现复杂的功能需求已成为常态。本文将详细介绍如何使用SpringBoot作为后端框架,结合阿里巴巴的通义千问(一个强大的自然语言处理服务),并通过自定义React组件来支持服务器发送事件(SSE, Server-Sent Events)的EventStream数据解析。这一组合不仅能够实现高效的实时通信,还能利用AI技术提升用户体验。
244 2