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(环境变量)
}
两者的直接实现类对比:
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 默认不用懒加载,因为:
- 启动慢一点,但运行时稳定
- 提前暴露 Bean 创建问题,比运行时崩溃好
- 配合 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?
这个问题能筛选出有生产经验的候选人:
- 嵌入式应用:Spring Boot 微服务之外的场景,如定时任务进程、CLI 工具
- 内存敏感环境:IoT 设备、Serverless 函数(Lambda),Bean 按需加载节省内存
- 超大规模 Bean 工厂:Bean 数量达到数千上万,预加载耗时太长
- 测试场景:单元测试中用 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 用的是 ApplicationContext(AnnotationConfigApplicationContext),它的底层 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"));
原因:
XmlBeanFactory 功能太少,无法满足现代 Spring 需求
DefaultListableBeanFactory 更通用,可以灵活组合
- 解耦了 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 的类型选择
// 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() 创建所有单例
优化手段:
- 用
@SpringBootApplication(exclude = {xxx.class}) 排除不需要的自动配置
- 用
spring.autoconfigure.exclude 配置排除
- 用
@ConditionalOnProperty 控制自动配置生效条件
四、工程选型
4.1 选型决策树
需要启动吗?
├─ 否(嵌入式、测试)→ BeanFactory 或 DefaultListableBeanFactory
│
└─ 是
├─ 需要国际化、事件、资源加载吗?
│ ├─ 是 → ApplicationContext
│ └─ 否 → 两者皆可,但 ApplicationContext 仍是首选
│
└─ Bean 数量多少?
├─ 几百个以下 → ApplicationContext 预加载没问题
└─ 几千个以上 → 考虑懒加载 + 分批加载
4.2 各大框架的默认选择
五、面试总结
BeanFactory vs ApplicationContext 这道题,考察的不是"谁更厉害",而是"为什么会有两个接口,各自的权衡是什么"。
P5 候选人能说出"BeanFactory 是基础接口,ApplicationContext 是增强版"。
P6 候选人能说出懒加载 vs 预加载的权衡、ApplicationContext 的四大扩展功能。
P7 候选人能说出 Spring Boot 底层用的是 DefaultListableBeanFactory + ApplicationContext 包装、知道什么场景下应该选哪个。
能答到第三层的,基本都看过 Spring 源码。