Skip to content

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 的"说明书",但还没真正创建对象。

java
// 手动模拟 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 容器,但层次和定位完全不同。下面这个表一目了然:

维度BeanFactoryApplicationContext
接口层级最底层容器接口继承 BeanFactory,扩展企业级能力
实例化策略懒加载:getBean() 时才创建预加载:refresh() 时创建所有非懒加载单例
启动速度快,适合资源受限场景(Android/嵌入式)慢,但启动即用,运行时无延迟
AOP 支持无自动代理,需手动配置 BeanPostProcessor自动注册 BeanPostProcessor,支持 @Aspect
事件机制ApplicationEventPublisher,发布-监听
国际化MessageSource 接口
环境抽象EnvironmentCapable(Profile + Property)
资源加载ResourceLoader 统一资源访问
典型实现DefaultListableBeanFactoryAnnotationConfigApplicationContext / ClassPathXmlApplicationContext / XmlWebApplicationContext
典型场景单元测试、轻量集成生产级微服务、Web 应用、Spring Boot
java
// 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()

java
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);
    }
}

对于 AnnotationConfigWebApplicationContextloadBeanDefinitions() 会扫描 @ComponentScan 指定的包路径,解析 @Bean 方法。这里的 DefaultListableBeanFactory 是 BeanFactory 的最终实现,它的 beanDefinitionMapConcurrentHashMap,但是 beanDefinitionNamesArrayList——这意味着注册顺序决定了 bean 的初始化顺序依赖。

三种容器的 loadBeanDefinitions 加载方式对比

容器实现loadBeanDefinitions 加载方式配置源适用场景
AnnotationConfigApplicationContextAnnotatedBeanDefinitionReader 解析 @Configuration 类,ClassPathBeanDefinitionScanner 扫描 @ComponentScan 包路径注解类 + 扫描路径Spring Boot 项目、新项目
ClassPathXmlApplicationContextXmlBeanDefinitionReader 解析 XML 中的 <bean> 标签,支持 <import> 引入其他配置文件XML 文件遗留系统、Spring 3.x 以前项目
XmlWebApplicationContext同上 XmlBeanDefinitionReader,但额外加载 WEB-INF/applicationContext.xmlXML + 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 的执行顺序由 PriorityOrderedOrdered → 无序 三级,且必须在 Bean 实例化之前执行完。如果在 BeanFactoryPostProcessor 里误用了 @Autowired,那个 bean 已经被提前实例化了,PostProcessor 再改 BeanDefinition 就没用了。

java
// 自定义 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 注解方法里不能用构造函数注入配置值,只能通过 @PostConstructApplicationContextAware 延迟获取。

第 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。

java
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() 的源码:

java
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();
        }
    }
}

关键细节

  • beanDefinitionNamesArrayList,按注册顺序遍历——注册顺序决定初始化顺序
  • 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 相关方法——要用 SmartInitializingSingletonApplicationListener<ContextRefreshedEvent> 来延迟初始化。

预加载耗时分析

生产环境启动慢,90% 的瓶颈在 finishBeanFactoryInitialization()。可以用 spring-boot-starter-actuatorstartup 端点做精确分析:

yaml
# application.yml
management:
  endpoints:
    web:
      exposure:
        include: startup
  endpoint:
    startup:
      enabled: true
bash
# 获取启动耗时分析
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,怎么保证执行顺序?

通过 PriorityOrderedOrdered 接口排序。PriorityOrdered 先执行,按 getOrder() 升序;然后是 Ordered,按 getOrder() 升序;最后是无序的。ConfigurationClassPostProcessor 实现了 PriorityOrderedgetOrder() 返回 Integer.MIN_VALUE(最高优先级),确保 @Configuration 注解先被解析。

Q:BeanFactory 和 FactoryBean 的区别?

  • BeanFactory:IoC 容器,管理 bean 的完整生命周期
  • FactoryBean:工厂 bean,当 bean 的创建逻辑比较复杂(比如需要从 JNDI 查找、需要动态代理)时,实现 FactoryBean<T> 接口,getObject() 返回实际的 bean 实例。Spring 容器里通过 &beanName 获取 FactoryBean 本身,beanName 获取 getObject() 的产物。MyBatis-SpringSqlSessionFactoryBean 就是典型实现。

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

面试官会追问的点:BeanFactoryPostProcessorBeanPostProcessor 的区别、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 源码

手撕 → 框架 → 生产化,一步步把 AI Agent 工程化搞透。