Spring Bean的生命周期及常见问题

Bean生命周期图

整个生命周期大致分为5个小的阶段:实例化、初始化、注册Destruction回调、Bean的正常使用以及Bean的销毁。

1. Bean生命周期描述

1.1 实例化Bean

  • Spring容器首先创建Bean实例:先确保这个Bean对应的类已经被加载,确保它是public的,然后如果有工厂方法,则直接调用工厂方法创建这个bean,如果没有的话就调用它的构造方法来创建这个Bean。

1.2 添加到三级缓存

  • 创建ObjectFactory放入到三级缓存中。

1.3 设置属性值populateBean

  • Spring容器注入必要的属性到Bean中
  • 当另一个Bean要注入当前Bean时,先从一级缓存中查询是否有完全的初始化的对象,如果没有找到则从二级缓存中查找是否有"半成品"对象,如果还是没有找到则从三级缓存中查找匿名类ObjectFactory,调用其getObject()方法获取到那个提前暴露的引用,并将这个引用放入二级缓存(earlySingletonObjects),同时从三级缓存移除该工厂。

1.4 调用aware接口

  • 如果Bean实现了BeanNameAware、BeanClassLoaderAware、BeanFactoryAware等这些Aware接口,Spring容器会调用它们的方法进行处理
  • BeanNameAware:Bean可以获取到自己在Spring容器中的名字,这对于需要根据Bean的名称进行某些操作的场景很有用。
  • BeanClassLoaderAware:这个接口使Bean能够访问加载它的类加载器,这在需要进行类加载操作时特别有用,例如动态加载类。
  • BeanFactoryAware:通过这个接口可以获取对BeanFactory的引用,获得对BeanFactory的访问权限。

1.5 BeanPostProcessor前置处理

  • 在Bean初始化之前,允许自定义的BeanPostProcessor对Bean实例进行处理,如修改Bean的状态。BeanPostProcessor的postProcessBeforessBeforeInitialization方法会在此时被调用(遍历所有的BeanPostProcessor的实现,执行他的postProcessBeforeInitialization方法)

1.6 InitializingBean接口

  • 提供一个机会,在所有Bean属性设置完成后进行初始化操作。如果Bean实现了InitializingBean接口,afterPropertiesSet方法会被调用

1.7 自定义init方法

  • 提供一种配置方式,在XML配置中指定Bean的初始化方法。如果Bean在配置文件中定义了初始化方法,那么该方法会被调用

1.8 BeanPostProcessor后置处理

  • 在Bean初始化之后,再次允许BeanPostProcessor对Bean进行处理。BeanPostProcessor的postProcessAfterInitialization方法会在此时被调用
  • aop代理对象的创建

1.9 注册销毁Destruction回调

  • 如果Bean实现了DisposableBean接口或在Bean定义中指定了自定义的销毁方法,Spring容器会为这些Bean注册一个销毁回调,确保在容器关闭时能够正确地清理资源

1.10 Bean准备就绪

  • 此时,Bean已完全初始化,可以开始处理应用程序的请求了
  • Bean 初始化完成后会放入 singletonObjects(一级缓存),并进入可用状态。

1.11 容器关闭

1.12 调用销毁方法

  • 当容器关闭时,如果Bean实现了DisposableBean接口,destroy方法会被调用
  • 如果Bean在配置文件中定义了销毁方法,那么该方法会被调用

2. 扩展问题

2.1 构造函数、@PostConstruct、afterPropertiesSet和init-method执行顺序

执行顺序

构造函数 → @PostConstruct → afterPropertiesSet → init-method

  • 构造函数:是在实例化阶段执行的。
  • @PostConstruct:用于在构造函数执行完毕并且依赖注入完成后执行特定的初始化方法。标注在方法上,表示这个方法将在BeanPostProcessor 前置处理阶段被调用。
  • afterPropertiesSet:是Spring的InitializingBean 接口中的方法。如果一个Bean实现了 InitializingBean接口,则会在初始化阶段调用该接口的afterPropertiesSet方法。
  • init-method:可以在Bean初始化完成后调用指定的初始化方法。
java 复制代码
package com.evimage.system.seata.saga.impl.gisSourceFile;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

@Component
public class MyBean implements InitializingBean {
    // 构造函数
    public MyBean() {
        System.out.println("1. MyBean Constructor called");
    }

    // @PostConstruct 方法
    @PostConstruct
    public void postConstruct() {
        System.out.println("2. MyBean @PostConstruct called");
    }

    // InitializingBean 的 afterPropertiesSet 方法
    @Override
    public void afterPropertiesSet() {
        System.out.println("3. MyBean afterPropertiesSet called");
    }

    // init-method 方法
    public void initMethod() {
        System.out.println("4. MyBean init-method called");
    }
}

@Configuration
class AppConfig {
    @Bean(initMethod = "initMethod")
    public MyBean myBean() {
        return new MyBean();
    }
}

class Main {
    public static void main(String[] args) {
        // 使用 AnnotationConfigApplicationContext 启动 Spring 容器
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
        // 获取 Bean,确保初始化完成
        MyBean myBean = applicationContext.getBean(MyBean.class);
        // 关闭容器
        applicationContext.close();
    }
}

2.2 什么是Spring的三级缓存?

一级缓存(singletonObjects):存放完全初始化好的Bean,成为可用的单例Bean。

二级缓存(earlySingletonObjects):存放提前暴露的 Bean 原始对象 (仅实例化,未进行属性填充),解决普通循环依赖(无 AOP 代理的场景)

三级缓存(singletonFactories):Bean 工厂对象 (ObjectFactory),可生成 Bean 的早期引用 ,解决代理对象的循环依赖(AOP 场景)。

三级缓存介入时机

  • 在步骤 1 (实例化) 之后,步骤 2 (填充属性) 之前: Spring 会立即 将这个仅实例化、未初始化 的原始对象的 ObjectFactory 放入三级缓存 ( singletonFactories ) 。这就是"提前暴露"未完成初始化的 Bean 引用的关键动作。
  • 当另一个 Bean 在它的步骤 2 (填充属性) 过程中需要注入这个 Bean 时: 它会在三级缓存中找到这个 ObjectFactory,调用其 getObject() 方法获取到那个提前暴露的引用(可能是原始对象,也可能是提前创建的代理对象),并将这个引用放入二级缓存 ( earlySingletonObjects ) ,同时从三级缓存移除该工厂。这样依赖方就能拿到一个引用,即使被依赖方还没完全初始化好。

2.3 三级缓存是如何解决循环依赖的问题的?

  • Spring中Bean的创建过程被分成两个步骤,第一步是实例化,第二步是初始化。而Spring之所以可以解决循环依赖就是因为对象的初始化是可以延后的。

2.4 Spring解决循环依赖一定需要三级缓存吗?

  • Spring的三级缓存机制主要解决的是单例(Singleton)作用域Bean之间通过Setter方法或字段(Field)进行依赖注入时产生的循环依赖问题
  • 使用二级缓存也能解决基本的循环依赖问题(依赖AOP代理类除外 ),因为AOP代理对象是在 BeanPostProcessor的后置处理阶段生成的,并不能提前暴露对象。

2.5 为什么三级缓存不能解决构造注入的循环依赖?

  • 三级缓存的设计初衷是处理属性注入 的循环依赖,通过提前暴露Bean的早期引用(在二级或三级缓存中)来解决问题。但构造器注入的依赖要求在实例化阶段就提供完整的依赖对象,而此时依赖的Bean可能尚未开始创建,根本无法放入任何缓存。因此,三级缓存无法解决构造器注入的循环依赖问题。

2.6 @Lazy注解能解决循环依赖吗?

能,但是要看情况,他可以用来解决构造器注入这种方式下的循环依赖。

@Lazy 是Spring框架中的一个注解,用于延迟一个bean的初始化,直到它第一次被使用。在默认情况下,Spring容器会在启动时创建并初始化所有的单例bean。这意味着,即使某个bean直到很晚才被使用,或者可能根本不被使用,它也会在应用启动时被创建。@Lazy 注解就是用来改变这种行为的。

也就是说,当我们使用 @Lazy 注解时,Spring容器会在需要该bean的时候才创建它,而不是在启动时。这意味着如果两个bean互相依赖,可以通过延迟其中一个bean的初始化来打破依赖循环。

相关推荐
AKAMAI5 分钟前
云计算迁移策略:分步框架与优势
后端·云原生·云计算
程序员JerrySUN8 分钟前
深入理解Linux DRM显示子系统:架构、实战项目与关键问题全解析
linux·运维·服务器·面试·职场和发展·架构
ruokkk10 分钟前
eureka如何绕过 LVS 的虚拟 IP(VIP),直接注册服务实例的本机真实 IP
后端
AKAMAI17 分钟前
为何AI推理正推动云计算从集中式向分布式转型
后端·云原生·云计算
程序员鱼皮25 分钟前
学 Java 还是 Go 语言?这事儿很简单!
java·后端·计算机·程序员·开发·编程经验·自学编程
天蓝的那一角37 分钟前
你想要的Lambda第二弹
后端
JohnYan38 分钟前
Bun技术评估 - 06 Redis
redis·后端·bun
写bug写bug40 分钟前
Dubbo中SPI机制的实现原理和优势
java·后端·dubbo
刘白Live44 分钟前
【Java】Git的一些常用命令
git·后端
Barcke1 小时前
AI赋能开发者工具:智能提示词编写与项目管理实践
前端·后端