BeanFactory vs ApplicationContext

候选人小李在面试美团 P6 时,面试官看了他的简历问道:

"你在项目里用的是什么容器来管理 Bean?"

小李说:"用的是 ApplicationContext。"

面试官点点头:"那 BeanFactory 和 ApplicationContext 有什么区别?"

小李说:"ApplicationContext 是 BeanFactory 的子接口,功能更强大..."

面试官追问:"那为什么还要保留 BeanFactory?直接用 ApplicationContext 不就完了?"

小张卡住了。

【面试官心理】 这道题我用来筛选候选人是否真的理解 Spring 容器的分层设计。BeanFactory 和 ApplicationContext 的区别不是简单背一句"子接口"就能回答的——我真正想知道的是:他有没有理解懒加载 vs 预加载的权衡、有没有用过 BeanFactory 的场景、能不能说出各自适用的生产环境。


一、核心问题 🔴

1.1 问题拆解

第一层:基本区别

  • "BeanFactory 和 ApplicationContext 有什么区别?"
  • "ApplicationContext 是不是就是 BeanFactory 的增强版?"

第二层:加载时机

  • "BeanFactory 是懒加载还是预加载?ApplicationContext 呢?"
  • "懒加载和预加载各有什么优缺点?"

第三层:扩展能力

  • "ApplicationContext 比 BeanFactory 多了哪些功能?"
  • "BeanFactory 的扩展点在哪里?ApplicationContext 呢?"

第四层:生产选型

  • "什么场景下应该用 BeanFactory 而不是 ApplicationContext?"
  • "Spring Boot 用的是哪个?是怎么选择的?"

1.2 ❌ 错误示范

候选人原话 A:"ApplicationContext 是 BeanFactory 的子类,功能更强大。"

问题诊断

  • "子类" 说法错误,ApplicationContext 是接口(org.springframework.context.ApplicationContext),BeanFactory 也是接口(org.springframework.beans.factory.BeanFactory),两者是同层接口而非继承关系
  • 把 Spring 知识学成了"名词对名词"的死记硬背
  • 根本不知道两者的继承体系

候选人原话 B:"BeanFactory 是懒加载,ApplicationContext 是预加载,就这么回事。"

问题诊断

  • 知道了一个知识点,但完全不理解为什么这么设计
  • 不知道懒加载在什么场景下有用
  • 不知道预加载会带来什么代价

候选人原话 C:"用 BeanFactory 会省内存,ApplicationContext 启动时占用内存多。"

问题诊断

  • 这个说法本身没错,但太片面
  • 没有考虑运行时性能、启动时间、可预测性之间的权衡
  • 不知道 Spring Boot 默认用 ApplicationContext 的原因

1.3 标准回答

P5 回答:基本区别

BeanFactory 是 Spring IoC 容器的最底层接口,定义了容器的基本契约:getBean() 方法负责创建和获取 Bean。ApplicationContext 是 BeanFactory 的子接口,在其基础上增加了很多企业级功能。

两者的继承关系如下:

// BeanFactory 顶层接口
public interface BeanFactory {
    Object getBean(String name) throws BeansException;
    Object getBean(String name, Class<?> requiredType) throws BeansException;
    boolean containsBean(String name);
    boolean isSingleton(String name);
    // ... 其他基础方法
}

// ApplicationContext 扩展了 BeanFactory
public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory, MessageSource, ApplicationEventPublisher, ResourcePatternResolver {
    // 在 BeanFactory 基础上新增了:
    // - MessageSource(国际化)
    // - ApplicationEventPublisher(事件发布)
    // - ResourcePatternResolver(资源加载)
    // - EnvironmentCapable(环境变量)
}

两者的直接实现类对比:

特性BeanFactoryApplicationContext
实现类XmlBeanFactoryClassPathXmlApplicationContextFileSystemXmlApplicationContextAnnotationConfigApplicationContextWebApplicationContext
懒加载默认懒加载默认预加载(非严格,但实际行为如此)
扩展点极少丰富(BeanFactoryPostProcessor、BeanPostProcessor、ApplicationListener 等)

1.4 追问升级

追问 1:懒加载 vs 预加载的性能权衡

BeanFactory 的懒加载(Lazy Initialization)和 ApplicationContext 的预加载(Eager Initialization)是一个经典的设计权衡:

// XmlBeanFactory - 懒加载代表
// Spring 3.0 已废弃,这里仅作原理说明
BeanFactory factory = new XmlBeanFactory(new ClassPathResource("beans.xml"));
// 此时所有 Bean 都没有被实例化
// 只有当你调用 getBean() 时,才会真正创建 Bean
OrderService orderService = factory.getBean(OrderService.class); // 此时才创建

// ClassPathXmlApplicationContext - 预加载代表
ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
// 容器启动时就会创建所有单例 Bean
// 调用 getBean() 直接获取已有的 Bean
OrderService orderService = ctx.getBean(OrderService.class); // 直接使用

预加载的代价:

  • 启动时间长:所有 Bean 都要在启动时创建,如果有 1000 个 Bean,启动可能需要 10 秒
  • 内存占用高:所有 Bean 实例同时存在于内存
  • 问题暴露早:循环依赖、Bean 创建错误在启动时就暴露

预加载的好处:

  • 运行时零开销:getBean() 只是从 Map 中取对象,没有创建成本
  • 提前发现问题:循环依赖、依赖缺失在启动时就暴露,而不是运行时
  • 线程安全简单:单例在启动时就固定了

懒加载的代价:

  • 运行时开销:第一次 getBean() 时需要创建,可能导致接口响应时间不稳定
  • 问题暴露晚:Bean 创建错误可能在运行时才暴露
  • 可能导致并发问题:多线程同时触发 Bean 创建,需要同步

懒加载的好处:

  • 启动快:只创建立即需要的 Bean
  • 内存可控:按需创建
💡

Spring Boot 2.x 开始支持懒加载 Bean,通过 spring.main.lazy-initialization=true 可以让整个容器的 Bean 都懒加载。但 Spring Boot 默认不用懒加载,因为:

  1. 启动慢一点,但运行时稳定
  2. 提前暴露 Bean 创建问题,比运行时崩溃好
  3. 配合 Actuator 可以监控启动过程 :::

追问 2:ApplicationContext 到底比 BeanFactory 多了什么?

这是面试中必须答出来的核心区别:

// 1. 国际化支持(MessageSource)
ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
String message = ctx.getMessage("greeting", null, Locale.CHINA);

// 2. 事件发布(ApplicationEventPublisher)
ctx.publishEvent(new MyCustomEvent(this)); // 发布事件
// 任何实现了 ApplicationListener 的 Bean 都会收到通知

// 3. 资源加载(ResourcePatternResolver)
Resource[] resources = ctx.getResources("classpath*:spring/*.xml");

// 4. 环境变量(EnvironmentCapable)
ConfigurableEnvironment env = ctx.getEnvironment();
String property = env.getProperty("server.port");

// 5. BeanFactoryPostProcessor 支持
// BeanFactory 也有,但 ApplicationContext 会自动检测并执行所有 BeanFactoryPostProcessor
// BeanFactory 需要手动注册

// 6. BeanPostProcessor 自动注册
// ApplicationContext 会自动注册所有 BeanPostProcessor
// XmlBeanFactory 需要手动 addBeanPostProcessor()

核心源码在 AbstractApplicationContext#refresh() 中:

// AbstractApplicationContext.java
public void refresh() throws BeansException {
    // ...prepareBeanFactory...

    // 【关键】调用所有 BeanFactoryPostProcessor
    // ApplicationContext 自动发现并调用
    // BeanFactory 需要手动调用
    invokeBeanFactoryPostProcessors(beanFactory);

    // 【关键】注册 BeanPostProcessor
    // ApplicationContext 自动注册所有 BeanPostProcessor
    registerBeanPostProcessors(beanFactory);

    // 【关键】初始化消息源
    initMessageSource();

    // 【关键】初始化事件广播器
    initApplicationEventMulticaster();

    // ...finishBeanFactoryInitialization...
}

【面试官心理】 我问他 ApplicationContext 多了什么,其实是在试探他有没有在生产环境用过这些高级功能。知道"有国际化、有事件发布"只是第一步,能说出"事件发布机制在微服务间解耦通信很有用"才是真正用过的人。

追问 3:什么场景下应该用 BeanFactory?

这个问题能筛选出有生产经验的候选人:

  1. 嵌入式应用:Spring Boot 微服务之外的场景,如定时任务进程、CLI 工具
  2. 内存敏感环境:IoT 设备、Serverless 函数(Lambda),Bean 按需加载节省内存
  3. 超大规模 Bean 工厂:Bean 数量达到数千上万,预加载耗时太长
  4. 测试场景:单元测试中用 BeanFactory 更轻量,可以精确控制 Bean 创建时机
// Spring Boot 内核也用到了 BeanFactory 的思想
// 但它的实现是 DefaultListableBeanFactory
// 通过 AnnotationConfigApplicationContext 包装后使用
@Configuration
public class AppConfig {
    public static void main(String[] args) {
        // 底层其实是 new DefaultListableBeanFactory()
        // 然后往里面注册各种 BeanDefinition
        // 最后包装成 ApplicationContext
        AnnotationConfigApplicationContext ctx =
            new AnnotationConfigApplicationContext(AppConfig.class);
    }
}

追问 4:Spring Boot 用的是哪个?

这是 P7 拉开差距的追问:

Spring Boot 底层用的其实是 DefaultListableBeanFactory(实现了 BeanFactory 接口),但通过 AnnotationConfigApplicationContext 包装后暴露出来的是 ApplicationContext 接口。

// Spring Boot 启动流程简化
public class SpringApplication {
    public ConfigurableApplicationContext run(String... args) {
        // 1. 创建 DefaultListableBeanFactory
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();

        // 2. 包装成 ApplicationContext
        AnnotationConfigApplicationContext context =
            new AnnotationConfigApplicationContext(beanFactory);

        // 3. 注册配置类、启动自动配置
        context.register((Class<?>[]) sources.toArray());
        context.refresh(); // 这里调用的是 ApplicationContext.refresh()

        return context;
    }
}

所以严格来说:Spring Boot 用的是 ApplicationContextAnnotationConfigApplicationContext),它的底层 Bean 管理是由 DefaultListableBeanFactory 完成的。

:::warning ⚠️ 这里有个面试陷阱:有些候选人会在"BeanFactory vs ApplicationContext"问题上绝对化——要么说"ApplicationContext 更好",要么说"BeanFactory 更轻量所以更好"。但正确的理解是:两者是互补的,不是替代关系。BeanFactory 是底层接口,ApplicationContext 是在其上的增强。Spring 源码里大量用的是 DefaultListableBeanFactory。

二、延伸问题 🟡

2.1 XmlBeanFactory 为什么被废弃了?

Spring 3.0 开始 XmlBeanFactory 被标记为 @Deprecated,官方推荐使用 DefaultListableBeanFactory + XmlBeanDefinitionReader 代替。

// ❌ 废弃写法(Spring 3.0 之前)
BeanFactory factory = new XmlBeanFactory(new ClassPathResource("beans.xml"));

// ✅ 推荐写法
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(new ClassPathResource("beans.xml"));

原因:

  1. XmlBeanFactory 功能太少,无法满足现代 Spring 需求
  2. DefaultListableBeanFactory 更通用,可以灵活组合
  3. 解耦了 Bean 容器和 Bean 定义加载逻辑

2.2 DefaultListableBeanFactory 的核心地位

Spring 源码中 DefaultListableBeanFactory 是绝对的核心,几乎所有容器实现都继承或依赖它:

DefaultListableBeanFactory
├── AbstractAutowireCapableBeanFactory(提供自动装配能力)
│   └── AbstractApplicationContext.refresh() 内部创建的就是它
├── XmlBeanFactory(已废弃)
├── StaticListableBeanFactory(持有静态 Bean 引用的简单实现)

它实现了 BeanDefinitionRegistry 接口,可以注册、移除、查询 BeanDefinition:

public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory
        implements ConfigurableListableBeanFactory, BeanDefinitionRegistry {

    // BeanDefinition 存储 Map
    private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);

    @Override
    public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
            throws BeanDefinitionStoreException {
        // 注册 BeanDefinition
        this.beanDefinitionMap.put(beanName, beanDefinition);
    }

    @Override
    public BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException {
        // 获取 BeanDefinition
        BeanDefinition bd = this.beanDefinitionMap.get(beanName);
        if (bd == null) {
            if (containsBeanDefinition(beanName)) {
                // 可能在父工厂中
            }
            throw new NoSuchBeanDefinitionException(beanName);
        }
        return bd;
    }
}

2.3 ApplicationContext 的类型选择

类型用途加载方式
ClassPathXmlApplicationContext从 classpath 加载 XML 配置XML
FileSystemXmlApplicationContext从文件系统加载 XML 配置XML
AnnotationConfigApplicationContext从 Java 配置类加载注解 + JavaConfig
GenericApplicationContext通用容器,支持混合配置XML + 注解 + JavaConfig
WebApplicationContextWeb 应用容器注解,绑定到 ServletContext
// XML 时代
ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");

// 注解时代
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);

// Spring Boot 时代
SpringApplication.run(AppConfig.class, args); // 内部创建 AnnotationConfigApplicationContext

// 混合配置
GenericApplicationContext ctx = new GenericApplicationContext();
new XmlBeanDefinitionReader(ctx).loadBeanDefinitions("beans.xml");
ctx.refresh();

三、生产避坑

3.1 预加载导致的启动超时

生产环境中最常见的问题是:Bean 太多导致 ApplicationContext 启动太慢。

场景:有一个老项目有 500+ 个 Bean(包含大量的第三方 SDK 初始化),在测试环境没问题,但生产环境部署时 Pod 启动超时。

排查方法:

# 开启懒加载
spring.main.lazy-initialization=true

# 或者部分懒加载
spring.main.lazy-initialization=false
@Lazy // 在需要的 Bean 上单独标注

但更正确的做法是:审查 Bean 清单,去掉不必要的自动配置

3.2 懒加载导致的首请求慢

如果用了全局懒加载,第一个请求会触发大量 Bean 创建,导致响应时间飙高。

// Spring Boot 2.x 支持部分懒加载
@Component
@Lazy(false) // 这个 Bean 不懒加载
public class CriticalBean { }

// 而其他 Bean 可以懒加载
@Component
@Lazy(true) // 按需加载
public class NonCriticalBean { }

3.3 Spring Boot 自动配置导致的预加载过大

Spring Boot 的 @SpringBootApplication 默认会触发大量自动配置(AutoConfiguration),每个自动配置类都会往容器里注册 Bean。

启动流程:
@SpringBootApplication
  └─ @EnableAutoConfiguration
      └─ 扫描 spring.factories / META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
      └─ 注册 100+ 个自动配置 Bean
      └─ ApplicationContext.refresh() 创建所有单例

优化手段:

  1. @SpringBootApplication(exclude = {xxx.class}) 排除不需要的自动配置
  2. spring.autoconfigure.exclude 配置排除
  3. @ConditionalOnProperty 控制自动配置生效条件

四、工程选型

4.1 选型决策树

需要启动吗?
├─ 否(嵌入式、测试)→ BeanFactory 或 DefaultListableBeanFactory

└─ 是
    ├─ 需要国际化、事件、资源加载吗?
    │   ├─ 是 → ApplicationContext
    │   └─ 否 → 两者皆可,但 ApplicationContext 仍是首选

    └─ Bean 数量多少?
        ├─ 几百个以下 → ApplicationContext 预加载没问题
        └─ 几千个以上 → 考虑懒加载 + 分批加载

4.2 各大框架的默认选择

框架默认容器选择原因
Spring MVCXmlWebApplicationContext历史兼容性
Spring Boot 1.xAnnotationConfigApplicationContext注解优先
Spring Boot 2.xAnnotationConfigApplicationContext同上
Spring CloudAnnotationConfigApplicationContext配合 Spring Boot
ShardingSphereDefaultListableBeanFactory需要细粒度控制

五、面试总结

BeanFactory vs ApplicationContext 这道题,考察的不是"谁更厉害",而是"为什么会有两个接口,各自的权衡是什么"。

P5 候选人能说出"BeanFactory 是基础接口,ApplicationContext 是增强版"。 P6 候选人能说出懒加载 vs 预加载的权衡、ApplicationContext 的四大扩展功能。 P7 候选人能说出 Spring Boot 底层用的是 DefaultListableBeanFactory + ApplicationContext 包装、知道什么场景下应该选哪个。

能答到第三层的,基本都看过 Spring 源码。