深入理解 Spring IoC 容器与 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() 最终也会走到这里。它的核心步骤如下:
prepareRefresh():记录启动时间、初始化Environment、校验必需属性。obtainFreshBeanFactory():创建或刷新DefaultListableBeanFactory,并加载BeanDefinition。prepareBeanFactory():注册ApplicationContextAwareProcessor、ApplicationListenerDetector等内置后置处理器,注册可解析的依赖(如BeanFactory、ApplicationContext)。postProcessBeanFactory():留给子类扩展,例如 Web 容器注册ServletRequest的作用域。invokeBeanFactoryPostProcessors():执行所有BeanFactoryPostProcessor,其中最关键的是ConfigurationClassPostProcessor,它负责解析@Configuration、@ComponentScan、@Import、@Bean。registerBeanPostProcessors():把BeanPostProcessor按PriorityOrdered、Ordered、无序三档注册进容器。initMessageSource():初始化国际化消息源。initApplicationEventMulticaster():初始化事件广播器。onRefresh():模板方法,Web 容器在这里创建内嵌 Tomcat。registerListeners():注册监听器并补发早期事件。finishBeanFactoryInitialization():预实例化所有非懒加载单例,这是 Bean 生命周期真正开始的地方。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 更精确:后者在父子容器场景下会触发多次。
九、常见坑 / 最佳实践
- 在构造器里使用
@Autowired字段。字段注入发生在populateBean,构造器执行时字段还是null。需要构造期依赖就用构造器注入,并让字段声明为final。 - 在
BeanPostProcessor中注入业务 Bean。BPP 会在其他 Bean 创建之前被实例化,此时注入业务 Bean 会触发它提前初始化,导致它错过后续的 BPP 增强。确需使用应通过ObjectProvider延迟获取。 - 误以为原型 Bean 会被销毁。
@Scope("prototype")的对象容器不缓存也不销毁,@PreDestroy不会执行。需要释放资源时使用@Bean(destroyMethod = "...")并在调用方显式关闭,或改为单例。 - 在
@Configuration类上滥用proxyBeanMethods = true。它会让 CGLIB 代理配置类以保证@Bean方法调用返回同一实例,带来启动开销。类内方法不互相调用时应设为false。 - 同名 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。
- 点赞
- 收藏
- 关注作者
评论(0)