Spring IoC 容器初始化流程与 BeanFactory/ApplicationContext 区别
提出问题
一个 Spring Boot 项目启动时,@ComponentScan 扫了一堆包,@Configuration 类里又注册了各种 Bean,但你有没有想过:Spring 到底是什么时候把这些 Bean 创建出来的?创建顺序是谁决定的?为什么有的项目启动快、有的启动慢?而 BeanFactory 和 ApplicationContext 这两个名字看着差不多,到底有什么区别?
换个角度,如果你在写一个 AI Agent 框架——比如基于 Spring AI 搭建的多 Agent 编排系统——每个 Agent 是一个 @Component,Agent 之间通过 @Autowired 注入 Tool 和 LLM 客户端。如果 IoC 容器初始化顺序不对,Agent A 在初始化时调用了 Agent B 的某个方法,但 Agent B 的依赖还没注入完,直接就 NullPointerException。这不是理论问题,Spring AI 的 ToolCallback 注册顺序就踩过这个坑。
面试官问这个题,表面是问 IoC 容器初始化流程,实际是想看你对 Spring 容器启动背后那 13 步核心方法有没有源码级理解,以及能不能把这份理解用到实际架构设计里。
分析问题
IoC 容器初始化三阶段
Spring IoC 容器的初始化可以概括为三个步骤:定位 → 加载 → 注册。
定位(Resource 定位):Spring 通过 Resource 接口抽象配置源,可以是 XML 文件(ClassPathXmlApplicationContext 的 <bean> 标签)、注解类(AnnotationConfigApplicationContext 的 @Configuration)、或者 Groovy 配置。这一步的核心是告诉容器"去哪里找 Bean 定义"。
加载(BeanDefinition 读取):BeanDefinitionReader 将 Resource 中的配置信息解析为 BeanDefinition 对象。BeanDefinition 包含了类的全限定名、作用域、懒加载标记、属性值、构造函数参数、init/destroy 方法等元信息。需要注意,这一步不做实例化,只是把"怎么建这个 Bean"的说明书存起来。
注册(BeanDefinition 注册):解析好的 BeanDefinition 被注册到 BeanDefinitionRegistry 中,默认实现是 DefaultListableBeanFactory 内部的 ConcurrentHashMap<String, BeanDefinition>。注册完成后,容器里就有了所有 Bean 的"说明书",但还没真正创建对象。
// 手动模拟 IoC 容器的三阶段(简化版)
public class SimpleIoCContainer {
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();
// 1. 定位:加载类路径下的配置文件
public void loadConfig(String configPath) {
// 实际是 ResourceLoader 的职责
}
// 2. 加载:解析配置为 BeanDefinition
public void registerBean(String name, Class<?> clazz, boolean singleton) {
BeanDefinition def = new BeanDefinition(clazz, singleton);
// 3. 注册:存入 Map
beanDefinitionMap.put(name, def);
}
// 懒加载:getBean 时才实例化
public Object getBean(String name) throws Exception {
BeanDefinition def = beanDefinitionMap.get(name);
if (def == null) throw new NoSuchBeanDefinitionException(name);
if (def.isSingleton() && def.getSingletonInstance() != null) {
return def.getSingletonInstance();
}
Object instance = def.getBeanClass().getDeclaredConstructor().newInstance();
if (def.isSingleton()) {
def.setSingletonInstance(instance);
}
return instance;
}
}BeanFactory vs ApplicationContext
这是面试中最容易被混为一谈的问题。两个都是 IoC 容器,但层次和定位完全不同。下面这个表一目了然:
| 维度 | BeanFactory | ApplicationContext |
|---|---|---|
| 接口层级 | 最底层容器接口 | 继承 BeanFactory,扩展企业级能力 |
| 实例化策略 | 懒加载:getBean() 时才创建 | 预加载:refresh() 时创建所有非懒加载单例 |
| 启动速度 | 快,适合资源受限场景(Android/嵌入式) | 慢,但启动即用,运行时无延迟 |
| AOP 支持 | 无自动代理,需手动配置 BeanPostProcessor | 自动注册 BeanPostProcessor,支持 @Aspect |
| 事件机制 | 无 | ApplicationEventPublisher,发布-监听 |
| 国际化 | 无 | MessageSource 接口 |
| 环境抽象 | 无 | EnvironmentCapable(Profile + Property) |
| 资源加载 | 无 | ResourceLoader 统一资源访问 |
| 典型实现 | DefaultListableBeanFactory | AnnotationConfigApplicationContext / ClassPathXmlApplicationContext / XmlWebApplicationContext |
| 典型场景 | 单元测试、轻量集成 | 生产级微服务、Web 应用、Spring Boot |
// BeanFactory 懒加载 vs ApplicationContext 预加载
public class ContainerDemo {
public static void main(String[] args) {
// BeanFactory:懒加载,启动快
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
// 注册 BeanDefinition 后,getBean 时才创建实例
// ApplicationContext:预加载,启动时完成所有单例实例化
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
// 启动时所有非懒加载 bean 已创建完毕
}
}Agent 场景下的选择:如果你的 Agent 框架需要动态加载 Tool(比如用户运行时注册自定义 Tool),用 BeanFactory 更灵活——每次 getBean() 动态创建,不触发全局 refresh。但如果你用 Spring AI 的 ToolCallback 注册机制,它依赖 ApplicationContext 的 BeanPostProcessor 自动扫描 @Tool 注解,那就必须用 ApplicationContext。Spring AI 的 ToolCallback 注册是在 finishBeanFactoryInitialization 阶段通过 SmartInitializingSingleton 触发的——这个后面会讲到。
核心 13 步:AbstractApplicationContext.refresh()
这是 Spring 容器初始化的灵魂方法。AnnotationConfigApplicationContext 的构造器最终会调用 refresh(),而 ClassPathXmlApplicationContext 也一样。refresh() 的 13 步是整个 Spring 框架里最值得读的源码之一。
下面是一个完整的调用时序图(文字描述),帮助你理解每一步的触发关系和生命周期:
时间轴 →
│
├─ [1] prepareRefresh()
│ ├─ 设置容器启动时间 startupDate
│ ├─ 设置 active 标志为 true
│ ├─ 初始化属性源(PropertySources)—— 空实现,子类可覆盖
│ └─ 校验 requiredProperties(如 ${...} 占位符的必需属性是否存在)
│
├─ [2] obtainFreshBeanFactory()
│ ├─ 如果已有 BeanFactory,先 destroyBeans() + closeBeanFactory()
│ ├─ 创建 DefaultListableBeanFactory 实例
│ ├─ 设置序列化 ID(用于 Session 序列化)
│ ├─ customizeBeanFactory() —— 子类可覆盖,设 allowBeanDefinitionOverriding 等
│ └─ loadBeanDefinitions(beanFactory) —— 模板方法,子类实现
│
├─ [3] prepareBeanFactory()
│ ├─ 设置 ClassLoader(默认线程上下文类加载器)
│ ├─ 注册默认 BeanPostProcessor:ApplicationContextAwareProcessor
│ ├─ 注册忽略依赖接口:EnvironmentAware / EmbeddedValueResolverAware / ResourceLoaderAware / ApplicationEventPublisherAware / MessageSourceAware / ApplicationContextAware
│ ├─ 注册特殊 Bean:BeanFactory / ResourceLoader / ApplicationEventPublisher / ApplicationContext
│ ├─ 注册 LoadTimeWeaver(如果开启了 @EnableLoadTimeWeaving)
│ └─ 注册系统属性 bean environment -> systemProperties + systemEnvironment
│
├─ [4] postProcessBeanFactory() —— 空实现,子类扩展
│ └─ 例如:AnnotationConfigEmbeddedWebApplicationContext 在此注册嵌入式 Web 容器相关的 BeanPostProcessor
│
├─ [5] invokeBeanFactoryPostProcessors()
│ ├─ 执行 BeanDefinitionRegistryPostProcessor(优先于 BFPP)
│ │ ├─ PriorityOrdered 级(如 ConfigurationClassPostProcessor)
│ │ ├─ Ordered 级
│ │ └─ 非排序级
│ ├─ 执行 BeanFactoryPostProcessor
│ │ ├─ PriorityOrdered 级(如 PropertySourcesPlaceholderConfigurer)
│ │ ├─ Ordered 级
│ │ └─ 非排序级
│ └─ 注意:这里 @Configuration / @ComponentScan / @Import 注解被解析,产生新的 BeanDefinition
│
├─ [6] registerBeanPostProcessors()
│ ├─ 注册 BeanPostProcessor(按 PriorityOrdered → Ordered → 无序)
│ ├─ 关键:AutowiredAnnotationBeanPostProcessor(@Autowired/@Inject/@Value 注入)
│ ├─ 关键:CommonAnnotationBeanPostProcessor(@PostConstruct/@PreDestroy/@Resource)
│ ├─ 关键:PersistenceAnnotationBeanPostProcessor(@PersistenceUnit/@PersistenceContext,JPA 用)
│ └─ 注册 MergedBeanDefinitionPostProcessor(用于 @EventListener 等方法级元数据)
│
├─ [7] initMessageSource()
│ └─ 初始化 MessageSource bean(国际化),不存在则注册 DelegatingMessageSource
│
├─ [8] initApplicationEventMulticaster()
│ └─ 初始化 ApplicationEventMulticaster,默认 SimpleApplicationEventMulticaster
│
├─ [9] onRefresh() —— 模板方法,子类扩展
│ ├─ EmbeddedWebApplicationContext:创建嵌入式 Tomcat/Jetty/Undertow 容器
│ └─ ReactiveWebServerApplicationContext:创建 Netty WebServer
│
├─ [10] registerListeners()
│ ├─ 注册静态指定的 ApplicationListener
│ └─ 将早期事件(refresh 过程中产生但广播器还没就绪的)发送给已注册的监听器
│
├─ [11] finishBeanFactoryInitialization() —— 最耗时的阶段
│ ├─ 初始化 ConversionService(类型转换,如 String → Date)
│ ├─ 注册 EmbeddedValueResolver(如 @Value("${...}") 的解析器)
│ ├─ 冻结 BeanDefinition 配置(freezeConfiguration())
│ └─ preInstantiateSingletons()
│ ├─ 遍历 beanDefinitionNames(ArrayList,按注册顺序)
│ ├─ 对每个非抽象、非懒加载的单例调用 getBean(beanName)
│ │ └─ getBean 内部递归触发依赖 bean 的创建
│ └─ 所有单例创建完成后,触发 SmartInitializingSingleton.afterSingletonsInstantiated()
│
├─ [12] finishRefresh()
│ ├─ 初始化 LifecycleProcessor(默认 DefaultLifecycleProcessor)
│ ├─ 调用 LifecycleProcessor.onRefresh() —— 启动所有 SmartLifecycle Bean
│ ├─ 发布 ContextRefreshedEvent
│ └─ 注册 JVM shutdown hook(如果已关闭)
│
└─ [13] resetCommonCaches()
└─ 清除反射缓存(ReflectionUtils.clearCache())、注解缓存(AnnotationUtils.clearCache())、
ResolvableType 缓存等关键步骤深度拆解:
第 2 步 obtainFreshBeanFactory()
源码路径:AbstractRefreshableApplicationContext.obtainFreshBeanFactory()
protected final void refreshBeanFactory() throws BeansException {
// 如果已有 BeanFactory,先销毁
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
// 创建 DefaultListableBeanFactory 实例
DefaultListableBeanFactory beanFactory = createBeanFactory();
// 设置序列化 ID(用于 Session 序列化场景)
beanFactory.setSerializationId(getId());
// 自定义 BeanFactory(子类覆盖,如设置是否允许 bean 覆盖、循环引用等)
customizeBeanFactory(beanFactory);
// 加载 BeanDefinition——这是子类实现的模板方法
loadBeanDefinitions(beanFactory);
this.beanFactory = beanFactory;
} catch (IOException ex) {
throw new ApplicationContextException("Failed to load bean definitions", ex);
}
}对于 AnnotationConfigWebApplicationContext,loadBeanDefinitions() 会扫描 @ComponentScan 指定的包路径,解析 @Bean 方法。这里的 DefaultListableBeanFactory 是 BeanFactory 的最终实现,它的 beanDefinitionMap 是 ConcurrentHashMap,但是 beanDefinitionNames 是 ArrayList——这意味着注册顺序决定了 bean 的初始化顺序依赖。
三种容器的 loadBeanDefinitions 加载方式对比:
| 容器实现 | loadBeanDefinitions 加载方式 | 配置源 | 适用场景 |
|---|---|---|---|
AnnotationConfigApplicationContext | AnnotatedBeanDefinitionReader 解析 @Configuration 类,ClassPathBeanDefinitionScanner 扫描 @ComponentScan 包路径 | 注解类 + 扫描路径 | Spring Boot 项目、新项目 |
ClassPathXmlApplicationContext | XmlBeanDefinitionReader 解析 XML 中的 <bean> 标签,支持 <import> 引入其他配置文件 | XML 文件 | 遗留系统、Spring 3.x 以前项目 |
XmlWebApplicationContext | 同上 XmlBeanDefinitionReader,但额外加载 WEB-INF/applicationContext.xml | XML + Web 上下文 | 传统 Servlet 架构的 Web 项目 |
踩坑:一个项目里 @DependsOn 写了 20 多个,就是因为 @Configuration 类之间的加载顺序不可控,最后直接依赖 @DependsOn 硬编码顺序。实际应该拆模块,让 Spring 自己去推断依赖图。在 Agent 场景下,如果 Agent A 依赖 Agent B 的 Tool 注册结果,应该用 @DependsOn("agentB") 显式声明,而不是碰运气等顺序。
第 5 步 invokeBeanFactoryPostProcessors()
这是自定义配置逻辑的最佳切入点。执行所有 BeanFactoryPostProcessor,包括 PropertySourcesPlaceholderConfigurer(处理 ${...} 占位符)和 ConfigurationClassPostProcessor(处理 @Configuration、@ComponentScan、@Import 等注解)。
这里有个坑很多人不知道:BeanFactoryPostProcessor 的执行顺序由 PriorityOrdered → Ordered → 无序 三级,且必须在 Bean 实例化之前执行完。如果在 BeanFactoryPostProcessor 里误用了 @Autowired,那个 bean 已经被提前实例化了,PostProcessor 再改 BeanDefinition 就没用了。
// 自定义 BeanFactoryPostProcessor:动态注册 Bean
@Component
public class DynamicBeanRegistrar implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
throws BeansException {
// 获取 BeanDefinitionRegistry(DefaultListableBeanFactory 实现了它)
if (beanFactory instanceof BeanDefinitionRegistry registry) {
GenericBeanDefinition def = new GenericBeanDefinition();
def.setBeanClass(MyDynamicService.class);
def.getPropertyValues().add("name", "dynamic-bean");
registry.registerBeanDefinition("myDynamicService", def);
System.out.println("动态注册了 myDynamicService");
}
}
}真实场景:有个同事在微服务网关里用 BeanFactoryPostProcessor 扫描所有 @ApiRoute 注解,动态生成路由 Handler 的 BeanDefinition。上线后把一个 @Configuration 类里的 @Value 依赖改成了 @Autowired,结果 PropertySourcesPlaceholderConfigurer 还没执行,${...} 占位符全部没解析,启动直接报错。排查了 2 小时才找到根因——BeanFactoryPostProcessor 里不能依赖任何被 @Value 注入的属性。
Agent 场景延伸:在 Spring AI 框架中,ToolCallback 的注册也是通过 BeanFactoryPostProcessor 实现的。ToolCallbackRegistrar 会在 invokeBeanFactoryPostProcessors 阶段扫描所有标注了 @Tool 的 Bean 方法,为每个方法生成一个 ToolCallback 的 BeanDefinition。如果你在 @Tool 方法里依赖了 @Value("${api.key}"),那这个 Tool 的 BeanDefinition 注册时占位符还没解析——必须等到第 5 步之后,PropertySourcesPlaceholderConfigurer 执行完,@Value 才能生效。这就是为什么 Spring AI 的 @Tool 注解方法里不能用构造函数注入配置值,只能通过 @PostConstruct 或 ApplicationContextAware 延迟获取。
第 6 步 registerBeanPostProcessors()
注册所有 BeanPostProcessor,包括 AutowiredAnnotationBeanPostProcessor(处理 @Autowired/@Inject)、CommonAnnotationBeanPostProcessor(处理 @PostConstruct/@PreDestroy/@Resource)等。这些 PostProcessor 负责在 bean 实例化之后、初始化之前进行干预。
时序关键:第 5 步是 BeanFactoryPostProcessor(操作 BeanDefinition),第 6 步是 BeanPostProcessor(操作 bean 实例)。不能混。面试官喜欢问:如果我想在 Spring 容器启动时对某个 bean 做点手脚,应该用哪个?——改 BeanDefinition 用 BFPP,改 bean 实例用 BPP。
第 11 步 finishBeanFactoryInitialization()
容器启动耗时最长的阶段。遍历所有 BeanDefinition,实例化所有非懒加载的单例 bean。
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
// 初始化 ConversionService(类型转换器)
if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME) &&
beanFactory.isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)) {
beanFactory.setConversionService(
beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class));
}
// 冻结 BeanDefinition 配置——之后不能再修改
beanFactory.freezeConfiguration();
// 实例化所有非懒加载单例
beanFactory.preInstantiateSingletons();
}DefaultListableBeanFactory.preInstantiateSingletons() 的源码:
public void preInstantiateSingletons() throws BeansException {
// 生成一份快照——防止并发修改
List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);
// 按注册顺序遍历,触发 getBean
for (String beanName : beanNames) {
RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
if (isFactoryBean(beanName)) {
// FactoryBean 前缀加 & 获取 FactoryBean 本身
Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
// 如果 FactoryBean 标记了 eagerInit,则立即创建 getObject 产物
if (FactoryBean.class.isAssignableFrom(bean.getClass())) {
getBean(beanName);
}
} else {
getBean(beanName);
}
}
}
// 所有非懒加载单例实例化完成后,触发 SmartInitializingSingleton 回调
for (String beanName : beanNames) {
Object singletonInstance = getSingleton(beanName);
if (singletonInstance instanceof SmartInitializingSingleton) {
((SmartInitializingSingleton) singletonInstance).afterSingletonsInstantiated();
}
}
}关键细节:
beanDefinitionNames是ArrayList,按注册顺序遍历——注册顺序决定初始化顺序getBean()内部会递归触发依赖 bean 的创建,形成拓扑排序SmartInitializingSingleton在所有单例完成后回调,比@PostConstruct更晚——适合做"所有 bean 就绪后"的初始化逻辑
Agent 场景连线:Spring AI 的 ToolCallbackInitializer 实现了 SmartInitializingSingleton。这意味着所有 Agent Bean 和 Tool Bean 都实例化完成后,它才执行 afterSingletonsInstantiated(),扫描所有 Bean 上的 @Tool 注解方法,注册到 Tool 注册表。如果你在某个 Agent 的 @PostConstruct 里就调用 Tool,Tool 还没注册,会返回空列表。 这就是为什么 Spring AI 官方文档建议在 @PostConstruct 里不要调用 ToolCallback 相关方法——要用 SmartInitializingSingleton 或 ApplicationListener<ContextRefreshedEvent> 来延迟初始化。
预加载耗时分析
生产环境启动慢,90% 的瓶颈在 finishBeanFactoryInitialization()。可以用 spring-boot-starter-actuator 的 startup 端点做精确分析:
# application.yml
management:
endpoints:
web:
exposure:
include: startup
endpoint:
startup:
enabled: true# 获取启动耗时分析
curl http://localhost:8080/actuator/startup | jq '.timeline | .[] | select(.duration > 100)'这会输出每个 bean 的创建耗时,超过 100ms 的就是重点优化对象。常见原因:
- 数据库连接池初始化(Druid/HikariCP 首次连接慢)
- Redis 连接初始化
- 大量 @PostConstruct 中执行了远程调用
- 复杂 AOP 代理生成(比如 @Transactional 和 @Cacheable 叠加)
启动优化决策树(面试直接能用):
启动慢 → 是 finishBeanFactoryInitialization 阶段吗?
├─ 是 → 用 actuator/startup 找最慢的 bean
│ ├─ 数据库连接池慢 → 检查网络延迟,考虑 spring.datasource.hikari.initialization-fail-timeout=-1
│ ├─ Redis 连接慢 → @Lazy 懒加载,第一次访问才创建连接
│ ├─ @PostConstruct 有远程调用 → 异步化或用 @EventListener(ApplicationReadyEvent.class)
│ └─ AOP 代理生成复杂 → 减少 @Transactional/@Cacheable 叠加,考虑编译期织入
│
└─ 否 → 是 refresh 之前的阶段慢吗?
├─ @ComponentScan 扫描包太多 → 用 includeFilters 精确定位,别扫整个包
├─ 配置类解析慢 → 减少 @Import 链的深度,用 @EnableXxx 组合注解
└─ 大量 PropertySource 加载 → 合并配置文件,减少 @PropertySource 数量真实踩坑:某次上线,一个模块的 @Configuration 类里 @Bean 了一个 RedisTemplate,但 Redis 服务器跨机房、网络延迟 30ms 还经常丢包。启动时 finishBeanFactoryInitialization 阶段卡了 47 秒。解决方案:把 RedisTemplate 的 @Bean 改成 @Lazy,第一次用到才创建连接。
Agent 冷启动优化:如果系统启动时要加载 10 个 Agent,每个 Agent 在 @PostConstruct 里初始化 LLM 客户端连接(OpenAI / 通义千问 API),10 个 Agent 串行初始化,每个等 2 秒 TLS 握手,启动时间直接飙到 20 秒以上。解决方案:统一用 @Lazy 注解 Agent Bean,或者将 LLM 客户端连接池化,只在第一次调用时创建连接(类似 HikariCP 的 lazyInit)。
面试进阶追问
Q:ApplicationContext 的 refresh() 可以被多次调用吗?什么场景下调用?
可以。refresh() 是线程安全的(synchronized (startupShutdownMonitor)),但调用前必须保证没有其他线程在使用容器。典型场景:动态刷新配置(如 Apollo、Nacos 的配置热更新触发 refresh())。注意:多次调用会销毁旧容器、重建所有 bean,正在执行的请求会拿到过期的 bean 引用。在 Agent 场景下,如果你的 Agent 系统在运行时动态加载了新 Tool,不要直接调 refresh()——那会把所有 Agent 状态重置掉。应该用 BeanDefinitionRegistry.registerBeanDefinition() 配合 ConfigurableListableBeanFactory.registerSingleton() 局部注册,不触发全量重刷。
Q:如果一个 BeanFactoryPostProcessor 依赖了另一个 BeanFactoryPostProcessor,怎么保证执行顺序?
通过 PriorityOrdered 和 Ordered 接口排序。PriorityOrdered 先执行,按 getOrder() 升序;然后是 Ordered,按 getOrder() 升序;最后是无序的。ConfigurationClassPostProcessor 实现了 PriorityOrdered 且 getOrder() 返回 Integer.MIN_VALUE(最高优先级),确保 @Configuration 注解先被解析。
Q:BeanFactory 和 FactoryBean 的区别?
- BeanFactory:IoC 容器,管理 bean 的完整生命周期
- FactoryBean:工厂 bean,当 bean 的创建逻辑比较复杂(比如需要从 JNDI 查找、需要动态代理)时,实现
FactoryBean<T>接口,getObject()返回实际的 bean 实例。Spring 容器里通过&beanName获取 FactoryBean 本身,beanName获取getObject()的产物。MyBatis-Spring的SqlSessionFactoryBean就是典型实现。
Q:Spring 的 refresh() 流程中,哪个扩展点最适合 Agent 框架做自定义初始化?
两个:第 5 步 invokeBeanFactoryPostProcessors 用于动态注册 BeanDefinition(比如根据配置文件动态生成 Agent Bean),第 11 步之后的 SmartInitializingSingleton 用于在所有 bean 就绪后做初始化回调(比如注册 Tool 到 Agent 注册表)。不要用 @PostConstruct 做跨 Agent 的初始化,因为无法保证依赖顺序。
面试回答框架(面试官问"Spring 容器启动流程"时的一分钟回答):
"Spring 容器启动的核心是
refresh()方法,13 个步骤。我按三个阶段来拆解:第一阶段是底层准备(第 1-4 步):设置启动时间、创建 BeanFactory、配置类加载器和 Aware 接口。这是基础设施搭建。
第二阶段是配置处理(第 5-6 步):执行
BeanFactoryPostProcessor解析 @Configuration 和 @ComponentScan,然后注册BeanPostProcessor。这两个顺序不能乱——先改 BeanDefinition,再等实例化后干预 bean。第三阶段是实例化(第 7-12 步):初始化国际化、事件广播器,然后
finishBeanFactoryInitialization是最耗时的——遍历所有非懒加载单例,递归创建依赖。启动完成后发布ContextRefreshedEvent。面试官会追问的点:
BeanFactoryPostProcessor和BeanPostProcessor的区别、SmartInitializingSingleton比@PostConstruct晚、以及 FactoryBean 和 BeanFactory 不是一回事。我在 Agent 框架里遇到过SmartInitializingSingleton的这个顺序坑——Spring AI 的 Tool 注册就是靠它,比@PostConstruct晚,所以@PostConstruct里调 Tool 会拿空列表。"
总结
- IoC 容器初始化分为定位 → 加载 → 注册三阶段,注册完 BeanDefinition 后容器有了"说明书"但还没创建对象
- BeanFactory 是最底层容器,懒加载、轻量,仅提供基础 IoC 能力
- ApplicationContext 继承 BeanFactory,增加 AOP、事件、国际化、资源加载能力,且默认预加载所有单例
refresh()的 13 步是核心,其中finishBeanFactoryInitialization()是启动最慢的阶段- 面试加分:把 IoC 容器初始化与 Agent 框架的 Tool 注册流程关联起来——Spring AI 的 ToolCallback 注册在
SmartInitializingSingleton阶段,比@PostConstruct晚 - 踩坑汇总:BeanFactoryPostProcessor 里别用 @Value/@Autowired;@Lazy 可以解决跨机房连接的启动慢问题;注册顺序决定初始化顺序,不要依赖隐式顺序;Agent 冷启动时 LLM 客户端连接要池化或懒加载
- 启动慢用 Actuator 的 startup 端点分析,别靠猜
- 面试顺便把 BeanFactory vs FactoryBean 分清,这是加分项
参考:Spring Framework 源码 —
org.springframework.context.support.AbstractApplicationContext.refresh()·org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons()· 《Spring 揭秘》· Spring Boot Actuator 官方文档 · Spring AI ToolCallback 源码