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 提供的回调方法。文章内容根据源码阅读获取,欢迎大家评论指导。


目录
相关文章
|
5月前
|
安全 Java 应用服务中间件
Spring Boot + Java 21:内存减少 60%,启动速度提高 30% — 零代码
通过调整三个JVM和Spring Boot配置开关,无需重写代码即可显著优化Java应用性能:内存减少60%,启动速度提升30%。适用于所有在JVM上运行API的生产团队,低成本实现高效能。
658 3
|
4月前
|
安全 前端开发 Java
《深入理解Spring》:现代Java开发的核心框架
Spring自2003年诞生以来,已成为Java企业级开发的基石,凭借IoC、AOP、声明式编程等核心特性,极大简化了开发复杂度。本系列将深入解析Spring框架核心原理及Spring Boot、Cloud、Security等生态组件,助力开发者构建高效、可扩展的应用体系。(238字)
|
5月前
|
人工智能 Java API
构建基于Java的AI智能体:使用LangChain4j与Spring AI实现RAG应用
当大模型需要处理私有、实时的数据时,检索增强生成(RAG)技术成为了核心解决方案。本文深入探讨如何在Java生态中构建具备RAG能力的AI智能体。我们将介绍新兴的Spring AI项目与成熟的LangChain4j框架,详细演示如何从零开始构建一个能够查询私有知识库的智能问答系统。内容涵盖文档加载与分块、向量数据库集成、语义检索以及与大模型的最终合成,并提供完整的代码实现,为Java开发者开启构建复杂AI智能体的大门。
2767 58
|
4月前
|
消息中间件 缓存 Java
Spring框架优化:提高Java应用的性能与适应性
以上方法均旨在综合考虑Java Spring 应该程序设计原则, 数据库交互, 编码实践和系统架构布局等多角度因素, 旨在达到高效稳定运转目标同时也易于未来扩展.
304 8
|
5月前
|
监控 Java 数据库
从零学 Dropwizard:手把手搭轻量 Java 微服务,告别 Spring 臃肿
Dropwizard 整合 Jetty、Jersey 等成熟组件,开箱即用,无需复杂配置。轻量高效,启动快,资源占用少,内置监控、健康检查与安全防护,搭配 Docker 部署便捷,是构建生产级 Java 微服务的极简利器。
494 3
|
4月前
|
机器学习/深度学习 人工智能 监控
Java与AI模型部署:构建企业级模型服务与生命周期管理平台
随着企业AI模型数量的快速增长,模型部署与生命周期管理成为确保AI应用稳定运行的关键。本文深入探讨如何使用Java生态构建一个企业级的模型服务平台,实现模型的版本控制、A/B测试、灰度发布、监控与回滚。通过集成Spring Boot、Kubernetes、MLflow和监控工具,我们将展示如何构建一个高可用、可扩展的模型服务架构,为大规模AI应用提供坚实的运维基础。
380 0
|
6月前
|
前端开发 Java 开发者
Java新手指南:在Spring MVC中使用查询字符串与参数
通过结合实际的需求和业务逻辑,开发者可以灵活地利用这些机制,为用户提供更丰富而高效的Web应用体验。
214 15
|
7月前
|
JSON 前端开发 Java
Java新手指南:如何在Spring MVC中处理请求参数
处理Spring MVC中的请求参数是通过控制器方法中的注解来完成的。这些注解包括 `@RequestParam`, `@PathVariable`, `@ModelAttribute`, `@RequestBody`, `@RequestHeader`, `@Valid`, 和 `@RequestMapping`。使用这些注解可以轻松从HTTP请求中提取所需信息,例如URL参数、表单数据或者JSON请求体,并将其转换成Java对象以供进一步处理。
576 17
|
7月前
|
安全 Java 微服务
Java 最新技术和框架实操:涵盖 JDK 21 新特性与 Spring Security 6.x 安全框架搭建
本文系统整理了Java最新技术与主流框架实操内容,涵盖Java 17+新特性(如模式匹配、文本块、记录类)、Spring Boot 3微服务开发、响应式编程(WebFlux)、容器化部署(Docker+K8s)、测试与CI/CD实践,附完整代码示例和学习资源推荐,助你构建现代Java全栈开发能力。
830 1
|
8月前
|
IDE Java 数据库连接
解决Java环境中无法识别org.mybatis.spring.annotation.MapperScan的问题。
祝你好运,在这场MyBatis的魔法冒险中获得胜利!记住,魔法书(官方文档)永远是你最好的朋友。
729 18