spirng的bean的生命周期,以及为什么这么设计

一、一句话速记(先建立整体感)

实例化 → 属性填充 → Aware接口 → 初始化前 → 初始化 → 初始化后 → 使用 → 销毁

二、完整生命周期流程图(11步)

text

  1. 实例化(new对象,在堆里分配空间)
  2. 属性填充(setter注入、@Autowired等)
  3. BeanNameAware(setBeanName)
  4. BeanClassLoaderAware(setBeanClassLoader)
  5. BeanFactoryAware(setBeanFactory)
  6. EnvironmentAware(setEnvironment,可选)
  7. BeanPostProcessor.postProcessBeforeInitialization(初始化前)
  8. InitializingBean.afterPropertiesSet
  9. 自定义 init-method(@PostConstruct或xml指定的init方法)
  10. BeanPostProcessor.postProcessAfterInitialization(初始化后)

    【此时Bean已就绪,可以被应用程序使用】
  11. 容器关闭时销毁:
    • @PreDestroy
    • DisposableBean.destroy
    • 自定义 destroy-method
      三、每个阶段的详细说明(面试重点)
      阶段1:实例化(Instantiation)
      做了什么:通过反射或CGLIB创建对象,在堆中分配内存

特征:此时属性还是null,依赖还没注入

相关扩展点:InstantiationAwareBeanPostProcessor

阶段2:属性填充(Populate)

做了什么:自动注入依赖(byName/byType/注解)

核心处理:解析@Autowired、@Value、@Resource

循环依赖处理:此时会暴露半成品对象到二级/三级缓存

阶段3:Aware接口(感知容器)

顺序固定:BeanNameAware → BeanClassLoaderAware → BeanFactoryAware → 其他Aware

作用:让Bean知道自己的一些元信息(bean名称、工厂等)

阶段4:BeanPostProcessor前置处理

方法:postProcessBeforeInitialization

典型应用:@PostConstruct的解析、ApplicationContextAware(在此时处理)

阶段5:初始化(真正干活)

顺序:

InitializingBean.afterPropertiesSet()

自定义init方法(@Bean(initMethod="xxx") 或xml配置)

注意:@PostConstruct实际上是在前置处理器中执行的,严格来说在afterPropertiesSet之前

阶段6:BeanPostProcessor后置处理

方法:postProcessAfterInitialization

典型应用:AOP动态代理就在这里生成

如果Bean需要被代理(比如加了@Transactional、@Async),此时返回的是代理对象

阶段7:使用

从容器中getBean()获取到的就是完整可用的Bean

阶段8:销毁

容器关闭(close()或registerShutdownHook())

顺序:

@PreDestroy

DisposableBean.destroy()

自定义destroy方法

四、完整示例代码(帮你加深记忆)

java

@Component

public class LifecycleBean implements BeanNameAware, BeanFactoryAware,

InitializingBean, DisposableBean {

复制代码
public LifecycleBean() {
    System.out.println("1. 实例化");
}

@Autowired
private SomeService someService;
// 2. 属性填充(自动注入)

@Override
public void setBeanName(String name) {
    System.out.println("3. BeanNameAware: " + name);
}

@Override
public void setBeanFactory(BeanFactory beanFactory) {
    System.out.println("5. BeanFactoryAware");
}

@PostConstruct
public void postConstruct() {
    System.out.println("7. @PostConstruct(在初始化前处理器中执行)");
}

@Override
public void afterPropertiesSet() {
    System.out.println("8. InitializingBean.afterPropertiesSet");
}

@Bean(initMethod = "customInit")
public void customInit() {
    System.out.println("9. 自定义init-method");
}

@PreDestroy
public void preDestroy() {
    System.out.println("11. @PreDestroy");
}

@Override
public void destroy() {
    System.out.println("12. DisposableBean.destroy");
}

}

五、面试常见追问与应对

Q1:BeanPostProcessor和InitializingBean的关系?

BeanPostProcessor是全局处理器,作用于所有Bean

InitializingBean是单个Bean的初始化回调

执行顺序:postProcessBeforeInitialization → afterPropertiesSet → 自定义init → postProcessAfterInitialization

Q2:AOP代理在生命周期哪一步生成?

答: 在postProcessAfterInitialization中。AbstractAutoProxyCreator(BeanPostProcessor实现)判断是否需要代理,如果需要则返回代理对象替换原Bean。

Q3:@PostConstruct为什么比afterPropertiesSet早?

因为@PostConstruct是在CommonAnnotationBeanPostProcessor(前置处理器)中执行的,时机在afterPropertiesSet之前。

Q4:循环依赖中生命周期有什么特殊?

提前暴露半成品到三级缓存,跳过部分Aware和初始化,等依赖注入完成后再继续。

六、面试终极回答模板(背下来)

Spring Bean的生命周期从实例化开始,通过反射创建对象后,进行属性填充(依赖注入),然后执行Aware接口(BeanNameAware、BeanFactoryAware等),接着进入初始化阶段:先执行BeanPostProcessor的前置处理,再执行InitializingBean的afterPropertiesSet,然后是自定义init方法,最后执行BeanPostProcessor的后置处理(AOP代理在此生成)。此时Bean完成初始化,可供使用。容器关闭时,依次执行@PreDestroy、DisposableBean的destroy和自定义destroy方法。

七、记忆口诀

实例填充感知前,初始化和后置连,用完销毁三回调

实例 = 实例化

填充 = 属性填充

感知 = Aware

前 = 初始化前处理器

初始化 = afterPropertiesSet + init-method

后置 = 初始化后处理器

销毁三回调 = @PreDestroy + DisposableBean + destroy-method

为什么要把初始化过程拆成"前-中-后"三个阶段

让我用一个饭店做菜的类比来解释,然后再说技术原因。

一、用做菜类比(先建立直觉)

假设你要做一道"清蒸鲈鱼":

阶段 做菜过程 Spring Bean 为什么这么分
实例化 从冰箱拿出鲈鱼(生的) new对象,属性为null 先有原材料
属性填充 加葱姜、淋料酒 注入依赖 准备配料
前置处理 检查鱼是否新鲜、要不要改刀 @PostConstruct 初始化前的准备工作
初始化 上锅蒸(核心烹饪) afterPropertiesSet + init 真正干活
后置处理 出锅后淋热油、加香菜 AOP生成代理 加工增强

关键理解

  • @PostConstruct = 蒸之前的腌制/检查
  • afterPropertiesSet = 蒸的过程
  • AOP代理 = 蒸完之后加的浇头(不是鱼本身)

二、为什么需要"前置处理"(阶段4)?

核心原因:有些初始化工作必须在afterPropertiesSet之前完成

java 复制代码
@Component
public class UserService {
    
    @PostConstruct
    public void init() {
        // 这个检查应该在业务初始化之前执行
        System.out.println("1. 检查配置是否完整");
        System.out.println("2. 预加载缓存数据");
    }
    
    @Bean(initMethod = "start")
    public void afterPropertiesSet() {
        // 业务真正的初始化
        System.out.println("3. 开始处理业务逻辑");
    }
}

设计意图

  • @PostConstruct准备性工作(检查资源、预热缓存)
  • afterPropertiesSet业务初始化(真正干活)
  • 如果把准备工作和业务逻辑混在一起,代码会混乱

典型场景

场景 为什么必须在之前
@PostConstruct 需要在依赖注入完成后、业务初始化前做一些校验或预热
ApplicationContextAware 需要在初始化之前让Bean拿到容器引用(因为初始化可能要用到容器)

三、为什么需要"后置处理"(阶段6)?

核心原因:AOP代理必须在原对象初始化完成后才能创建

java 复制代码
@Service
@Transactional
public class OrderService {
    public void createOrder() {
        // 业务逻辑
    }
}

Spring需要创建一个代理对象 来管理事务。问题来了:什么时候创建这个代理?

不能太早

  • 如果实例化时就创建代理,原对象的属性还没注入(依赖是null)
  • 如果属性填充时创建代理,原对象还没初始化(业务逻辑未就绪)

必须在最后

  • 原对象已经完全准备好了(属性注入完成、初始化方法执行完)
  • 此时用动态代理包装原对象,返回代理对象

代码示例理解

java 复制代码
// Spring内部大致逻辑(简化)
Object bean = 实例化();      // 原始OrderService对象
填充属性(bean);              // 注入依赖
初始化前处理(bean);          // @PostConstruct
初始化(bean);                // afterPropertiesSet
// 关键:此时bean已经是一个完整的OrderService对象

// 检查是否需要AOP
if (需要代理(bean)) {
    bean = 创建代理(bean);   // 返回Proxy对象
}
初始化后处理(bean);          // 其他后置处理器

return bean;  // 可能返回代理对象

四、三个阶段的核心区别(面试重点)

阶段 执行时机 典型操作 对Bean的影响
前置处理 初始化方法之前 @PostConstruct、Aware接口 不能改变Bean的"本质"
初始化 正中间 afterPropertiesSet、init-method 业务初始化,不改变Bean类型
后置处理 初始化之后 AOP代理@Async代理 可以替换Bean对象

关键差异

  • 前置处理:只能对原对象进行操作(设置属性、调用方法)
  • 后置处理:可以替换原对象(返回代理对象)

五、为什么要这样设计?三个设计原则

1. 单一职责原则

  • 实例化只管创建对象
  • 属性填充只管注入依赖
  • 初始化只管业务准备
  • 后置处理管增强(AOP)
  • 每个阶段职责清晰

2. 开闭原则(对扩展开放)

  • 如果你想要在所有Bean初始化之前做点什么 → 实现BeanPostProcessor(前置)
  • 如果你想要在所有Bean初始化之后做点什么 → 实现BeanPostProcessor(后置)
  • 不需要修改Spring源码

3. 模板方法模式

Spring定义好了生命周期骨架,留出扩展点,让开发者按需插入逻辑。

六、完整时序图(帮助理解)

复制代码
时间轴 →
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

[实例化]        →   原对象 = new OrderService()  (null, null)
                      ↓
[属性填充]       →   orderService.userDao = userDao
                      ↓
[前置处理]       →   @PostConstruct → 检查配置
                      ↓
[初始化]        →   afterPropertiesSet → 加载数据
                      ↓
[后置处理]       →   创建CGLIB代理 → 包装原对象
                      ↓
[返回]          →   返回代理对象(不是原对象了!)

七、验证代码(亲自跑一下)

java 复制代码
@Component
public class DemoBean {
    
    @PostConstruct
    public void postConstruct() {
        System.out.println("1. @PostConstruct: " + this.getClass().getSimpleName());
    }
    
    @Override
    public void afterPropertiesSet() {
        System.out.println("2. afterPropertiesSet: " + this.getClass().getSimpleName());
    }
    
    @Bean(initMethod = "init")
    public void init() {
        System.out.println("3. init-method: " + this.getClass().getSimpleName());
    }
}

// 加上AOP
@Aspect
@Component
public class LogAspect {
    @Around("@annotation(org.springframework.transaction.annotation.Transactional)")
    public Object around(ProceedingJoinPoint pjp) {
        System.out.println("代理执行中...");
        return pjp.proceed();
    }
}

输出类似:

复制代码
1. @PostConstruct: DemoBean
2. afterPropertiesSet: DemoBean$$EnhancerByCGLIB  ← 已经是代理了
3. init-method: DemoBean$$EnhancerByCGLIB

注意:代理之后,后两个方法是在代理对象上执行的。

八、面试回答模板

Spring把初始化分为三个阶段:

  1. 前置处理 :在业务初始化之前执行,典型如@PostConstruct,用于资源检查、预热等准备工作,因为这类操作必须在核心业务逻辑之前完成。
  2. 初始化 :业务真正的初始化,如afterPropertiesSet,此时依赖已经注入完毕。
  3. 后置处理 :在初始化之后执行,最重要的是AOP代理生成。因为AOP需要基于一个完整的原对象创建代理,既不能太早(依赖没注入),也不能太晚(需要在返回容器前完成)。代理生成后返回的是增强后的代理对象,不是原对象。

一句话总结 :前置做准备工作,中间做业务初始化,后置做增强代理------这是开闭原则单一职责的体现,让每个扩展点职责清晰。

相关推荐
三水不滴4 小时前
SpringAI + SpringDoc + Knife4j 构建企业级智能问卷系统
经验分享·spring boot·笔记·后端·spring
952367 小时前
Spring IoC&DI
java·数据库·spring
云烟成雨TD7 小时前
Spring AI Alibaba 1.x 系列【39】四大多智能体(Multi-agent)架构
java·人工智能·spring
危桥带雨8 小时前
FLASH代码部分
java·后端·spring
java1234_小锋8 小时前
Spring AI 2.0 vs LangChain4j,怎么选?
spring·springai·langchain4j
それども9 小时前
Spring Bean 注入的优先级顺序
java·数据库·sql·spring
启山智软10 小时前
企业如何选择适合自己的电商系统技术架构?(实操落地版)
java·spring·架构·开源·商城开发
空中海10 小时前
Nginx 知识体系 · 下篇:高级与实战
运维·nginx·spring
子非鱼@Itfuture11 小时前
ThreadLocal 是什么?如何用?以及最佳使用场景
java·开发语言·spring