深入理解 Spring IoC 容器与 Bean 生命周期

举报
yd_94907100 发表于 2026/10/31 21:59:37 2026/10/31
【摘要】 从 refresh() 到销毁,完整拆解 Bean 的一生与关键扩展点。

写过 @Service、@Autowired 的人很多,能说清一个 Bean 从实例化到销毁经历了哪些回调的人不多。本文沿着 Spring Framework 6.x 的源码调用链,把 IoC 容器的启动流程、BeanDefinition 的作用、Bean 生命周期的各个阶段串起来,并指出每个阶段对应的扩展点。适合已经会用 Spring、想进一步理解容器行为的中高级后端开发阅读。

一、IoC 解决的是什么问题

没有容器时,对象的依赖由调用方自己创建:

OrderService orderService = new OrderServiceImpl(
        new JdbcOrderDao(new HikariDataSource(config)));

问题不在写法啰嗦,而在耦合:OrderServiceImpl 被迫知道 JdbcOrderDao 的构造细节,替换实现、加缓存、加事务都要改调用方代码。

IoC(Inversion of Control,控制反转)把"创建对象、装配依赖、管理生命周期"这三件事从业务代码中抽走,交给容器。依赖注入(DI)是 IoC 最常见的落地方式:对象只声明"我需要什么",容器负责"给你什么"。

Spring 的容器本质上是两个接口的组合:

  • BeanFactory:最底层的容器,提供 getBean()、containsBean() 等能力,采用懒加载。
  • ApplicationContext:在 BeanFactory 之上叠加了事件发布、国际化、资源加载、自动注册 BeanPostProcessor 等能力,启动时预实例化所有单例。

日常使用的 AnnotationConfigApplicationContext、ClassPathXmlApplicationContext,以及 Spring Boot 的 AnnotationConfigServletWebServerApplicationContext,都是 ApplicationContext 的实现。

二、容器启动:refresh() 的十二步

AbstractApplicationContext#refresh() 是容器启动的总纲,Spring Boot 的 SpringApplication.run() 最终也会走到这里。它的核心步骤如下:

  1. prepareRefresh():记录启动时间、初始化 Environment、校验必需属性。
  2. obtainFreshBeanFactory():创建或刷新 DefaultListableBeanFactory,并加载 BeanDefinition。
  3. prepareBeanFactory():注册 ApplicationContextAwareProcessor、ApplicationListenerDetector 等内置后置处理器,注册可解析的依赖(如 BeanFactory、ApplicationContext)。
  4. postProcessBeanFactory():留给子类扩展,例如 Web 容器注册 ServletRequest 的作用域。
  5. invokeBeanFactoryPostProcessors():执行所有 BeanFactoryPostProcessor,其中最关键的是 ConfigurationClassPostProcessor,它负责解析 @Configuration、@ComponentScan、@Import、@Bean。
  6. registerBeanPostProcessors():把 BeanPostProcessor 按 PriorityOrdered、Ordered、无序三档注册进容器。
  7. initMessageSource():初始化国际化消息源。
  8. initApplicationEventMulticaster():初始化事件广播器。
  9. onRefresh():模板方法,Web 容器在这里创建内嵌 Tomcat。
  10. registerListeners():注册监听器并补发早期事件。
  11. finishBeanFactoryInitialization():预实例化所有非懒加载单例,这是 Bean 生命周期真正开始的地方。
  12. finishRefresh():发布 ContextRefreshedEvent,启动 LifecycleProcessor。

第 5 步和第 6 步的顺序不能颠倒:BeanFactoryPostProcessor 修改的是"图纸"(BeanDefinition),BeanPostProcessor 修改的是"成品"(Bean 实例)。图纸必须先于成品确定。

三、BeanDefinition:Bean 的图纸

容器不会直接反射 Class 来创建对象,中间隔着一层 BeanDefinition。它记录了 beanClassName、scope、lazyInit、dependsOn、primary、initMethodName、destroyMethodName、propertyValues、autowireMode 等元数据。

DefaultListableBeanFactory 用两个结构保存这些信息:

private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);
private volatile List<String> beanDefinitionNames = new ArrayList<>(256);

beanDefinitionMap 负责按名称查找,beanDefinitionNames 保证注册顺序,后者直接影响同类型多 Bean 时 @Autowired 的候选顺序和 @Order 之外的默认行为。

常见的 BeanDefinition 实现有 GenericBeanDefinition(通用)、RootBeanDefinition(合并后的最终形态)、ScannedGenericBeanDefinition(@ComponentScan 扫描产生)、AnnotatedGenericBeanDefinition(@Import 或手动注册产生)。合并父 Bean 定义后得到 RootBeanDefinition,后续实例化都基于它。

四、Bean 生命周期的关键调用链

入口是 AbstractApplicationContext#getBean,它委托给 DefaultListableBeanFactory#doGetBean,再进入 AbstractAutowireCapableBeanFactory#createBean,最终落到 doCreateBean:

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, Object[] args) {
    BeanWrapper instanceWrapper = createBeanInstance(beanName, mbd, args);
    Object bean = instanceWrapper.getWrappedInstance();
    // 提前暴露工厂,用于解决循环依赖
    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    populateBean(beanName, mbd, instanceWrapper);
    Object exposedObject = initializeBean(beanName, bean, mbd);
    registerDisposableBeanIfNecessary(beanName, bean, mbd);
    return exposedObject;
}

4.1 实例化前:postProcessBeforeInstantiation

resolveBeforeInstantiation 会调用 InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation。如果返回非 null,容器直接把它当作 Bean 返回,跳过后续所有步骤。AbstractAutoProxyCreator 正是利用这个口子,在目标对象存在自定义 TargetSource 时提前生成代理。

4.2 实例化:createBeanInstance

createBeanInstance 先通过 SmartInstantiationAwareBeanPostProcessor#determineCandidateConstructors 选出构造器,AutowiredAnnotationBeanPostProcessor 在这里处理 @Autowired 构造器。没有候选构造器时使用无参构造,再由 SimpleInstantiationStrategy 反射实例化;如果 BeanDefinition 指定了 factoryMethod,则走工厂方法。

4.3 属性填充:populateBean

先执行 InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation(返回 false 可跳过属性填充),再执行 postProcessProperties。@Autowired、@Value、@Resource 的注入都发生在这一步,此时对象已经存在,所以字段注入能在构造器执行完之后才赋值。

4.4 初始化:initializeBean

invokeAwareMethods(beanName, bean);                       // BeanNameAware 等
applyBeanPostProcessorsBeforeInitialization(bean, name);  // @PostConstruct
invokeInitMethods(beanName, bean, mbd);                   // afterPropertiesSet / init-method
applyBeanPostProcessorsAfterInitialization(bean, name);   // AOP 代理

@PostConstruct 由 CommonAnnotationBeanPostProcessor 在 BeforeInitialization 阶段调用,InitializingBean#afterPropertiesSet 紧随其后,最后才是 XML 或 @Bean(initMethod) 指定的方法。执行顺序是:@PostConstruct → afterPropertiesSet → init-method。

AfterInitialization 阶段返回的对象会替换原对象,AOP 代理就在 AbstractAutoProxyCreator#wrapIfNecessary 中生成。这也是为什么在 @PostConstruct 里拿到的 this 还不是代理对象。

4.5 销毁

只有单例 Bean 会被容器登记销毁。DisposableBeanAdapter 依次调用 @PreDestroy、DisposableBean#destroy、destroy-method。原型 Bean 的销毁完全交给调用方,容器不会跟踪。

五、作用域与延迟初始化

@Scope 决定 Bean 的实例数量和存活范围。Spring 内置五种作用域:

  • singleton:默认值,容器内唯一实例,随容器创建和销毁。
  • prototype:每次 getBean 都创建新实例,容器只负责创建不负责销毁。
  • request:每个 HTTP 请求一个实例,仅在 Web 环境可用。
  • session:每个 HTTP Session 一个实例。
  • application:每个 ServletContext 一个实例。

request、session 作用域的实现依赖 RequestContextHolder 中的 ThreadLocal,因此在异步线程、定时任务中直接注入会抛 ScopeNotActiveException(Spring 6 的新异常类型,替代了旧版含糊的 IllegalStateException)。

单例 Bean 注入原型 Bean 时,容器只在创建单例时注入一次,之后拿到的永远是同一个原型对象。要让每次调用都取到新实例,需要使用方法注入:

@Component
public abstract class OrderProcessor {

    @Lookup
    protected abstract OrderContext createContext();

    public void process(Order order) {
        OrderContext ctx = createContext();   // 每次调用都返回新实例
        ctx.handle(order);
    }
}

@Lookup 由 AutowiredAnnotationBeanPostProcessor 通过 CGLIB 生成子类实现,本质是覆写该方法并在内部调用 beanFactory.getBean。另一种等价写法是实现 ObjectProvider<OrderContext>,调用 getObject() 获取新实例,可读性更好且不依赖 CGLIB。

@Lazy 标注在注入点上时,注入的是一个延迟解析的代理,首次调用方法才真正 getBean,可以用来缓解启动耗时和循环依赖,但会让类型判断失真,getClass() 返回的是代理类而非目标类。

六、BeanFactoryPostProcessor 与 BeanPostProcessor

| 维度 | BeanFactoryPostProcessor | BeanPostProcessor |

| --- | --- | --- |

| 作用对象 | BeanDefinition | Bean 实例 |

| 执行时机 | refresh() 第 5 步 | 每个 Bean 初始化前后 |

| 典型实现 | ConfigurationClassPostProcessor、PropertySourcesPlaceholderConfigurer | AutowiredAnnotationBeanPostProcessor、AbstractAutoProxyCreator |

| 能否 @Autowired | 不能(此时 BPP 尚未注册) | 可以,但容易引发过早初始化 |

七、Aware 接口与常用扩展点

Aware 系列接口让 Bean 主动索取容器能力。invokeAwareMethods 只处理三个最基础的:

private void invokeAwareMethods(String beanName, Object bean) {
    if (bean instanceof Aware) {
        if (bean instanceof BeanNameAware beanNameAware) {
            beanNameAware.setBeanName(beanName);
        }
        if (bean instanceof BeanClassLoaderAware beanClassLoaderAware) {
            ClassLoader bcl = getBeanClassLoader();
            if (bcl != null) {
                beanClassLoaderAware.setBeanClassLoader(bcl);
            }
        }
        if (bean instanceof BeanFactoryAware beanFactoryAware) {
            beanFactoryAware.setBeanFactory(AbstractAutowireCapableBeanFactory.this);
        }
    }
}

ApplicationContextAware、EnvironmentAware、ResourceLoaderAware 由 ApplicationContextAwareProcessor 在 BeforeInitialization 阶段回调,所以它们的执行时机晚于 BeanNameAware,但都早于 @PostConstruct。

能拿到容器能力不代表应该拿。ApplicationContextAware 会让业务类直接依赖 Spring API,单元测试必须启动容器,属于典型的框架侵入。需要按名称取 Bean 时优先用 ObjectProvider<T> 或 @Qualifier,需要读配置时优先用 @Value 或 @ConfigurationProperties。

八、常用扩展点一览

| 扩展点 | 触发时机 | 典型用途 |

| --- | --- | --- |

| BeanDefinitionRegistryPostProcessor | 所有 BeanDefinition 加载后、其他 BFPP 之前 | 动态注册 Bean |

| BeanFactoryPostProcessor | Bean 实例化前 | 修改占位符、调整定义 |

| InstantiationAwareBeanPostProcessor | 实例化前后、属性填充前后 | 自定义注入逻辑 |

| BeanPostProcessor | 初始化前后 | 代理、注解处理 |

| SmartInitializingSingleton | 所有单例创建完成后 | 缓存预热、注册回调 |

| ApplicationListener | 容器事件发布时 | 启动后初始化、优雅关闭 |

| SmartLifecycle | 容器启动最后阶段与关闭时 | 启停后台线程 |

SmartInitializingSingleton#afterSingletonsInstantiated 常被用来做"所有 Bean 都就绪后"的一次性动作,比监听 ContextRefreshedEvent 更精确:后者在父子容器场景下会触发多次。

九、常见坑 / 最佳实践

  1. 在构造器里使用 @Autowired 字段。字段注入发生在 populateBean,构造器执行时字段还是 null。需要构造期依赖就用构造器注入,并让字段声明为 final。
  2. 在 BeanPostProcessor 中注入业务 Bean。BPP 会在其他 Bean 创建之前被实例化,此时注入业务 Bean 会触发它提前初始化,导致它错过后续的 BPP 增强。确需使用应通过 ObjectProvider 延迟获取。
  3. 误以为原型 Bean 会被销毁。@Scope("prototype") 的对象容器不缓存也不销毁,@PreDestroy 不会执行。需要释放资源时使用 @Bean(destroyMethod = "...") 并在调用方显式关闭,或改为单例。
  4. 在 @Configuration 类上滥用 proxyBeanMethods = true。它会让 CGLIB 代理配置类以保证 @Bean 方法调用返回同一实例,带来启动开销。类内方法不互相调用时应设为 false。
  5. 同名 Bean 静默覆盖。Spring Boot 2.1 起 spring.main.allow-bean-definition-overriding 默认为 false,重复注册会直接启动失败。这是好事,不要为了"能跑"把它打开,应排查重复扫描。

十、小结

  • IoC 容器 = BeanFactory(能力)+ ApplicationContext(企业级增强),启动入口是 refresh()。
  • BeanDefinition 是 Bean 的图纸,BeanFactoryPostProcessor 改图纸,BeanPostProcessor 改成品。
  • Bean 生命周期:实例化 → 属性填充 → Aware → 初始化前 → 初始化 → 初始化后(AOP)→ 使用 → 销毁。
  • @PostConstruct 早于 afterPropertiesSet,早于 init-method;三者都在代理生成之前。
  • 只有单例 Bean 由容器负责销毁,原型 Bean 的生命周期归调用方。
  • 单例注入原型只会注入一次,需要每次新实例时用 ObjectProvider 或 @Lookup。
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。