手写一个Spring容器
手写一个Spring容器
面试官您好,手写 Spring 容器我会从它的核心本质入手,Spring 的灵魂就是IoC(控制反转)和 DI(依赖注入),所以我会先实现这两个最核心的特性,再逐步扩展生命周期和扩展点。整体思路清晰,代码量不大但能覆盖所有核心考点 ✨
核心架构设计(💡 先画架构,再写代码)
Spring 容器本质上就是一个Bean 的工厂,负责管理 Bean 的全生命周期。我设计的极简容器架构如下:
核心组件说明:
- 📦
BeanDefinition:存储 Bean 的元数据(类名、作用域、依赖关系、初始化方法等) - 📝
BeanDefinitionRegistry:Bean 定义的注册表,负责注册和查询 BeanDefinition - 🏭
BeanFactory:容器的核心接口,定义了getBean()方法 - 🚀
DefaultListableBeanFactory:默认容器实现,整合了所有功能
分步实现(🎯 核心代码,言简意赅)
1. 定义 Bean 元数据:BeanDefinition
public class BeanDefinition {
// Bean的类名
private String beanClassName;
// 是否单例(默认单例)
private boolean singleton = true;
// 依赖的Bean名称
private List<String> dependsOn = new ArrayList<>();
// 初始化方法名
private String initMethodName;
// getter/setter省略
}2. 定义容器核心接口:BeanFactory
public interface BeanFactory {
// 根据Bean名称获取Bean实例
Object getBean(String beanName) throws Exception;
}3. 实现核心容器:DefaultListableBeanFactory
这是整个容器的心脏,我会分三步实现核心逻辑:
public class DefaultListableBeanFactory implements BeanFactory, BeanDefinitionRegistry {
// 1. Bean定义注册表:存储所有Bean的元数据
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();
// 2. 单例池:存储所有单例Bean的实例
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>();
// 实现BeanDefinitionRegistry接口:注册Bean定义
@Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) {
beanDefinitionMap.put(beanName, beanDefinition);
}
// 实现BeanFactory接口:获取Bean(核心方法)
@Override
public Object getBean(String beanName) throws Exception {
// 第一步:先从单例池获取,有则直接返回
Object singletonBean = singletonObjects.get(beanName);
if (singletonBean != null) {
return singletonBean;
}
// 第二步:获取Bean定义,反射创建实例
BeanDefinition bd = beanDefinitionMap.get(beanName);
if (bd == null) {
throw new RuntimeException("Bean not found: " + beanName);
}
Object bean = Class.forName(bd.getBeanClassName()).newInstance();
// 第三步:依赖注入(自动装配)
populateBean(bean, bd);
// 第四步:初始化回调
initializeBean(bean, bd);
// 第五步:如果是单例,放入单例池
if (bd.isSingleton()) {
singletonObjects.put(beanName, bean);
}
return bean;
}4. 实现依赖注入:populateBean 方法
private void populateBean(Object bean, BeanDefinition bd) throws Exception {
// 遍历所有依赖的Bean,递归获取并注入
for (String dependBeanName : bd.getDependsOn()) {
Object dependBean = getBean(dependBeanName);
// 简单实现:按属性名注入(真实Spring支持按类型、构造器注入)
Field field = bean.getClass().getDeclaredField(dependBeanName);
field.setAccessible(true);
field.set(bean, dependBean);
}
}5. 实现初始化回调:initializeBean 方法
private void initializeBean(Object bean, BeanDefinition bd) throws Exception {
// 1. 执行InitializingBean接口的afterPropertiesSet方法
if (bean instanceof InitializingBean) {
((InitializingBean) bean).afterPropertiesSet();
}
// 2. 执行自定义init-method方法
if (bd.getInitMethodName() != null && !bd.getInitMethodName().isEmpty()) {
Method initMethod = bean.getClass().getMethod(bd.getInitMethodName());
initMethod.invoke(bean);
}
}
}使用演示(🚀 跑起来验证)
public class Main {
public static void main(String[] args) throws Exception {
// 1. 创建容器
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
// 2. 注册UserService的Bean定义
BeanDefinition userServiceBd = new BeanDefinition();
userServiceBd.setBeanClassName("com.example.UserService");
userServiceBd.getDependsOn().add("userDao");
factory.registerBeanDefinition("userService", userServiceBd);
// 3. 注册UserDao的Bean定义
BeanDefinition userDaoBd = new BeanDefinition();
userDaoBd.setBeanClassName("com.example.UserDao");
factory.registerBeanDefinition("userDao", userDaoBd);
// 4. 获取Bean,自动完成依赖注入
UserService userService = (UserService) factory.getBean("userService");
userService.getUser(); // 正常调用,UserDao已自动注入
}
}进阶优化点(💪 体现技术深度)
如果时间允许,我还会扩展以下功能,这些也是面试官常追问的点:
- 循环依赖处理:通过 "三级缓存" 提前暴露未初始化的 Bean 实例
- 作用域扩展:实现 prototype、request、session 等作用域
- BeanPostProcessor 扩展点:允许在 Bean 初始化前后进行自定义处理
- 注解支持:实现 @Component、@Autowired 注解的自动扫描和注入
- 销毁回调:实现 DisposableBean 接口和 destroy-method 方法
面试官高频追问(📚 提前准备,稳拿 offer)
| 追问问题 | 满分回答要点 |
|---|---|
| 手写的容器和真实 Spring 有什么差距? | 真实 Spring 还包含 AOP、事务、事件、国际化、资源加载等模块,且在性能、并发安全、异常处理上做了大量优化 |
| 为什么 Spring 要使用三级缓存解决循环依赖? | 一级缓存存成品 Bean,二级缓存存半成品 Bean,三级缓存存 Bean 工厂。只有当需要 AOP 代理时,三级缓存才会生成代理对象,避免提前创建代理 |
| Bean 的完整生命周期有哪些阶段? | 实例化 → 依赖注入 → 初始化前(BeanPostProcessor) → 初始化 → 初始化后 → 使用 → 销毁 |
核心代码完整版 & 技术亮点解析
1 基础接口定义(先定义契约,再写实现)
// Bean定义注册表接口
public interface BeanDefinitionRegistry {
void registerBeanDefinition(String beanName, BeanDefinition beanDefinition);
BeanDefinition getBeanDefinition(String beanName);
}
// 初始化回调接口
public interface InitializingBean {
void afterPropertiesSet() throws Exception;
}
// Bean后置处理器(Spring扩展点灵魂)
public interface BeanPostProcessor {
default Object postProcessBeforeInitialization(Object bean, String beanName) throws Exception {
return bean;
}
default Object postProcessAfterInitialization(Object bean, String beanName) throws Exception {
return bean;
}
}2 核心容器完整实现(✅ 可直接运行)
public class DefaultListableBeanFactory implements BeanFactory, BeanDefinitionRegistry {
// ==================== 核心缓存 ====================
// 1. Bean定义注册表:存储所有Bean的元数据
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);
// 2. 单例池:存储完全初始化完成的成品Bean(一级缓存)
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
// 3. 后置处理器注册表:存储所有BeanPostProcessor实例
private final List<BeanPostProcessor> beanPostProcessors = new CopyOnWriteArrayList<>();
// ==================== 技术亮点1:优雅的扩展点设计 ====================
public void addBeanPostProcessor(BeanPostProcessor postProcessor) {
beanPostProcessors.add(postProcessor);
}
// ==================== 技术亮点2:懒加载+线程安全的getBean实现 ====================
@Override
public Object getBean(String beanName) throws Exception {
// 快速检查:单例池已有,直接返回
Object singleton = singletonObjects.get(beanName);
if (singleton != null) {
return singleton;
}
// 双重检查锁:防止并发重复创建Bean
synchronized (singletonObjects) {
singleton = singletonObjects.get(beanName);
if (singleton != null) {
return singleton;
}
return createBean(beanName, beanDefinitionMap.get(beanName));
}
}
// ==================== 技术亮点3:Bean生命周期拆解清晰 ====================
private Object createBean(String beanName, BeanDefinition bd) throws Exception {
// 阶段1:实例化Bean(分配内存)
Object bean = instantiateBean(bd);
// 阶段2:依赖注入(填充属性)
populateBean(bean, bd);
// 阶段3:初始化(执行回调和后置处理器)
bean = initializeBean(bean, beanName, bd);
// 阶段4:放入单例池
if (bd.isSingleton()) {
singletonObjects.put(beanName, bean);
}
return bean;
}
private Object instantiateBean(BeanDefinition bd) throws Exception {
// 简单实现:无参构造器实例化
return Class.forName(bd.getBeanClassName()).getDeclaredConstructor().newInstance();
}
private void populateBean(Object bean, BeanDefinition bd) throws Exception {
// 按属性名自动注入依赖
for (String dependBeanName : bd.getDependsOn()) {
Object dependBean = getBean(dependBeanName);
Field field = bean.getClass().getDeclaredField(dependBeanName);
field.setAccessible(true);
field.set(bean, dependBean);
}
}
// ==================== 技术亮点4:标准初始化流程 ====================
private Object initializeBean(Object bean, String beanName, BeanDefinition bd) throws Exception {
// 1. 执行初始化前后置处理器
Object wrappedBean = bean;
for (BeanPostProcessor processor : beanPostProcessors) {
wrappedBean = processor.postProcessBeforeInitialization(wrappedBean, beanName);
}
// 2. 执行InitializingBean接口回调
if (wrappedBean instanceof InitializingBean) {
((InitializingBean) wrappedBean).afterPropertiesSet();
}
// 3. 执行自定义init-method
if (bd.getInitMethodName() != null && !bd.getInitMethodName().isEmpty()) {
Method method = wrappedBean.getClass().getMethod(bd.getInitMethodName());
method.invoke(wrappedBean);
}
// 4. 执行初始化后后置处理器(AOP代理在这里生成!)
for (BeanPostProcessor processor : beanPostProcessors) {
wrappedBean = processor.postProcessAfterInitialization(wrappedBean, beanName);
}
return wrappedBean;
}
// BeanDefinitionRegistry接口实现
@Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) {
beanDefinitionMap.put(beanName, beanDefinition);
}
@Override
public BeanDefinition getBeanDefinition(String beanName) {
return beanDefinitionMap.get(beanName);
}
}3 关键技术亮点总结 💡
| 技术亮点 | 说明 | 对应真实 Spring 设计 |
|---|---|---|
| 接口与实现分离 | 先定义 BeanFactory、BeanDefinitionRegistry 等契约,再写默认实现 | Spring 的面向接口编程思想,方便替换不同容器实现 |
| 双重检查锁 + ConcurrentHashMap | 保证多线程环境下 Bean 创建的线程安全,同时兼顾性能 | Spring 单例 Bean 创建的线程安全机制 |
| 生命周期阶段拆解 | 将 Bean 创建拆分为实例化→依赖注入→初始化三个独立阶段 | Spring Bean 生命周期的标准划分,为扩展点提供基础 |
| BeanPostProcessor 扩展点 | 允许在 Bean 初始化前后插入自定义逻辑,不侵入容器核心代码 | Spring 的核心扩展机制,AOP、事务、注解处理都基于此 |
| 懒加载机制 | Bean 只有在第一次调用 getBean () 时才会创建,节省启动资源 | Spring 默认的懒加载策略(可通过 @Lazy 配置) |
核心技术难点与解决方案 🚩
手写 Spring 容器最容易踩坑、也是面试官最常深挖的 6 个技术难点,我整理如下:
1 难点全景图
高频率
↑
| * 并发安全 (0.4,0.6)
| * 依赖注入扩展 (0.3,0.4)
| * 作用域 (0.5,0.5)
| * AOP整合 (0.8,0.7)
| * 循环依赖 (0.9,0.9) ← 绝对热点
| * 初始化/销毁 (0.2,0.3)
|
└──────────────────────────────────────────→ 高难度
低难度📍 各难点坐标位置
| 难点 | 难度 (X) | 考察频率 (Y) | 所在象限 | 面试热度 |
|---|---|---|---|---|
| 循环依赖问题 | 0.9 | 0.9 | 右上(高难高频) | 🔥🔥🔥🔥🔥 |
| AOP与IoC整合 | 0.8 | 0.7 | 右上(高难高频) | 🔥🔥🔥🔥 |
| 并发安全问题 | 0.4 | 0.6 | 左上半区 | 🔥🔥🔥 |
| 作用域实现 | 0.5 | 0.5 | 正中央 | 🔥🔥 |
| 依赖注入方式扩展 | 0.3 | 0.4 | 左下(低难低频) | 🔥 |
| 初始化/销毁顺序 | 0.2 | 0.3 | 左下(低难低频) | 🔥 |
2 详细难点与解决方案
| 技术难点 | 问题本质 | 解决方案 | 核心代码片段 |
|---|---|---|---|
| ⚠️ 循环依赖问题 (最高频考点) | A 依赖 B,B 依赖 A,递归创建时会出现栈溢出 | 采用三级缓存机制,提前暴露未初始化的 Bean 实例,将实例化和初始化分离 | 见下方 3 节完整实现 |
| ⚠️ 并发安全问题 | 多线程同时调用 getBean () 时,可能创建多个相同的单例 Bean | 1. 单例池使用 ConcurrentHashMap2. 单例 Bean 创建时加双重检查锁 | 见 2 节 getBean () 方法 |
| ⚠️ Bean 作用域实现 | prototype 作用域每次 getBean () 都要创建新实例,且不能放入单例池 | 1. 在 BeanDefinition 中增加 scope 字段2. getBean () 时根据 scope 决定是否缓存 | if (bd.isPrototype()) {return createBean(beanName, bd);} |
| ⚠️ 依赖注入方式扩展 | 初始实现只支持按属性名注入,真实 Spring 支持构造器、按类型注入 | 1. 扩展 BeanDefinition 存储构造器参数2. 增加 AutowiredAnnotationBeanPostProcessor 处理 @Autowired 注解 | 解析 @Autowired 注解,按类型匹配 Bean |
| ⚠️ 初始化 / 销毁顺序 | 依赖的 Bean 必须先初始化,销毁时必须先销毁被依赖的 Bean | 1. 依赖注入时递归创建依赖 Bean 2. 维护一个销毁顺序链表,销毁时逆序执行 | 记录 Bean 的依赖关系图,拓扑排序 |
| ⚠️ AOP 与 IoC 整合 | AOP 代理对象必须代替原始 Bean 放入容器 | 在 BeanPostProcessor 的 postProcessAfterInitialization 方法中生成代理对象 | public Object postProcessAfterInitialization(Object bean, String beanName) { return createProxy(bean);} |
3 最高难度:循环依赖三级缓存完整实现 ✅
public class DefaultListableBeanFactory implements BeanFactory, BeanDefinitionRegistry {
// 一级缓存:成品Bean(完全初始化完成)
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
// 二级缓存:半成品Bean(已实例化,未初始化)
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
// 三级缓存:Bean工厂(用于生成AOP代理对象)
private final Map<String, ObjectFactory<?>> singletonFactories = new ConcurrentHashMap<>(16);
@Override
public Object getBean(String beanName) throws Exception {
// 先从三级缓存依次查找
Object singleton = getSingleton(beanName);
if (singleton != null) {
return singleton;
}
synchronized (singletonObjects) {
singleton = getSingleton(beanName);
if (singleton != null) {
return singleton;
}
return createBean(beanName, beanDefinitionMap.get(beanName));
}
}
private Object getSingleton(String beanName) {
// 1. 先查一级缓存(成品)
Object singleton = singletonObjects.get(beanName);
if (singleton != null) {
return singleton;
}
// 2. 再查二级缓存(半成品)
singleton = earlySingletonObjects.get(beanName);
if (singleton != null) {
return singleton;
}
// 3. 最后查三级缓存(Bean工厂)
ObjectFactory<?> factory = singletonFactories.get(beanName);
if (factory != null) {
singleton = factory.getObject();
// 从三级缓存移到二级缓存
singletonFactories.remove(beanName);
earlySingletonObjects.put(beanName, singleton);
return singleton;
}
return null;
}
private Object createBean(String beanName, BeanDefinition bd) throws Exception {
// 1. 实例化Bean
Object bean = instantiateBean(bd);
// 2. 提前暴露到三级缓存(解决循环依赖的关键!)
if (bd.isSingleton()) {
singletonFactories.put(beanName, () -> getEarlyBeanReference(bean, beanName));
}
// 3. 依赖注入(此时如果出现循环依赖,会从三级缓存获取提前暴露的Bean)
populateBean(bean, bd);
// 4. 初始化
bean = initializeBean(bean, beanName, bd);
// 5. 从二级/三级缓存移到一级缓存
if (bd.isSingleton()) {
singletonObjects.put(beanName, bean);
earlySingletonObjects.remove(beanName);
singletonFactories.remove(beanName);
}
return bean;
}
// 生成提前暴露的Bean引用(如果需要AOP代理,在这里生成)
protected Object getEarlyBeanReference(Object bean, String beanName) {
Object exposedObject = bean;
for (BeanPostProcessor processor : beanPostProcessors) {
if (processor instanceof SmartInstantiationAwareBeanPostProcessor) {
exposedObject = ((SmartInstantiationAwareBeanPostProcessor) processor)
.getEarlyBeanReference(exposedObject, beanName);
}
}
return exposedObject;
}
}
// 支持提前暴露代理的后置处理器接口
public interface SmartInstantiationAwareBeanPostProcessor extends BeanPostProcessor {
Object getEarlyBeanReference(Object bean, String beanName) throws Exception;
}三级缓存核心原理:
- 只有单例 Bean支持循环依赖(prototype 作用域不支持)
- 实例化完成后立即将 Bean 工厂放入三级缓存,提前暴露引用
- 当出现循环依赖时,从三级缓存获取 Bean(如果需要 AOP 代理,会在这里生成)
- 初始化完成后,将 Bean 从二级 / 三级缓存移到一级缓存,保证最终返回的是同一个对象
以上就是手写 Spring 容器的核心代码和所有关键技术难点的解决方案。我认为手写 Spring 容器最重要的不是写出完整的框架,而是理解它的核心设计思想:通过工厂模式解耦对象的创建和使用,通过扩展点机制保证框架的灵活性。
真实面试模拟
真实面试模拟
面试官(推了推眼镜,笑着问)
“来,我们放松点。假设让你从零手写一个 Spring 容器,你会怎么设计?能把核心的 IoC、DI 甚至循环依赖都体现出来吗?”
我(搓搓手,略带兴奋)
“好的,那我就直接在白板上画个简图,边画边说吧。
其实 Spring 容器本质上就是一个 大型的 Map + 反射工厂,最核心的流程就四步:
包扫描 → 封装定义 → 实例化 → 注入依赖 → 放进单例池。”
(我一边说,一边在白板上画出简化的结构)
面试官(点点头)
“嗯,图很清晰。那第一步,你打算怎么把这些类信息存起来?”
我
“我会先抽象一个 BeanDefinition,它不存对象,只存 元数据。
里面至少要有三样东西:beanName、beanClass、scope。
scope 很关键,它决定这个 Bean 是单例还是多例,直接影响后续是否放进缓存。
这就是 Spring 的 ‘配置与实例分离’ 思想。📋”
public class BeanDefinition {
private String beanName;
private Class<?> beanClass;
private String scope; // singleton / prototype
}面试官(身体前倾)
“好,那第二步,怎么把 @Component 这种注解的类扫进你这个容器?”
我
“我会模拟 @ComponentScan,用 ClassLoader 加载资源 + 递归文件路径 的方式。
比如 com.example 这个包,我先通过 Thread.currentThread().getContextClassLoader().getResources() 拿到路径,然后递归找出所有 .class 文件,再反射加载成 Class,检查是否有 @Component 注解。
有的话,就生成 BeanDefinition,并放进一个 beanDefinitionMap 里。
注意,这时候我还没创建任何对象,只是一个‘定义仓库’。🔍”
面试官(追问)
“创建对象的时候呢?依赖注入你打算怎么处理?比如 @Autowired。”
我
“调用 getBean() 的时候触发真正的创建。流程我拆成两大步:
- 反射实例化:
clazz.getDeclaredConstructor().newInstance() - 属性注入:遍历所有
Field,如果发现@Autowired,就递归调用getBean(field.getName())获取依赖,然后反射塞进去。
当然这只是一个简化版,真实 Spring 还会按类型、@Qualifier 等复杂推导。💉”
面试官(笑了下)
“Spring 的 AOP 核心是通过 BeanPostProcessor 实现的,你这个容器能留出类似的扩展口吗?”
我
“必须有。
我会定义一个接口 BeanPostProcessor,里面有两个默认方法:
postProcessBeforeInitialization 和 postProcessAfterInitialization。
在创建 Bean 的流程里,我会在 实例化之后、注入之后 分别调用它们。
这样如果有人想加 AOP 逻辑,只要在 postProcessAfterInitialization 里返回一个代理对象,就能把原始 Bean 替换掉。
这就是 AOP 的基石 —— 给容器开了一个‘拦截窗’。🧩”
面试官(抛出经典难题)
“厉害。那循环依赖呢?比如 A 依赖 B,B 又依赖 A,你这个容器会不会死循环?”
我
“这正是三级缓存上场的时候。
我会设计三层 Map:
singletonObjects:一级,完全创建好的成品earlySingletonObjects:二级,提前暴露的半成品(已实例化但未注入属性)singletonFactories:三级,生成早期引用的工厂
流程是这样:
- 创建 A,先实例化,在注入属性前把 A 的
ObjectFactory放进三级缓存。 - 给 A 注入时发现需要 B,触发
getBean(B)。 - 创建 B,注入时发现需要 A,这时从三级缓存拿到工厂生成 A 的早期引用,放入二级缓存,B 拿到这个引用先完成创建。
- B 创建完毕,A 拿到完整的 B,继续完成自己的注入,最后进一级缓存。
⚠️ 注意,这种方式只能解决 单例 setter 注入 的循环依赖,构造器注入无解,Spring 也是一样。🌪️”
(我顺手在白板上画出三级缓存的转移过程)
面试官(满意地放下笔)
“最后,把这个容器的主干结构写出来吧。”
我
“好。容器就叫 MiniApplicationContext,核心字段包括:
beanDefinitionMap:定义仓库singletonObjects / earlySingletonObjects / singletonFactories:三级缓存beanPostProcessors:后置处理器列表
对外只暴露 scan() 和 getBean() 两个方法,其余细节全部隐藏,完全体现 IoC 思想。📦”
public class MiniApplicationContext {
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(); // 一级
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(); // 二级
private final Map<String, ObjectFactory<?>> singletonFactories = new ConcurrentHashMap<>(); // 三级
private final List<BeanPostProcessor> beanPostProcessors = new ArrayList<>();
public void scan(String basePackage) { ... }
public Object getBean(String name) { ... }
private Object createBean(String name, BeanDefinition bd) { ... }
private void populateBean(String name, Object instance) { ... }
}面试官(站起身,伸出手)
“非常棒,从 IoC 的思想到循环依赖的细节,该有的亮点都有了,而且设计完全接地气。期待后面的面试顺利!”
我(握握手)
“谢谢面试官,我也从梳理中学到很多!” 🚀
✨ 面试核心收获总结
- ✔️ IoC 容器:大型 Map + 反射工厂
- ✔️ BeanDefinition:将配置与实例分离
- ✔️ 依赖注入:递归 getBean + 反射赋值
- ✔️ AOP 扩展:BeanPostProcessor 拦截窗口
- ✔️ 循环依赖:三级缓存 + 提前暴露半成品引用
面试官(双手交叉放在桌上,眼神期待)
“你的设计思路我理解了,但我还想看看核心代码长什么样,尤其是技术亮点的部分。另外,你在设计这个容器时,遇到的关键难点是什么?怎么解决的?”
我(点点头,打开笔记本电脑把关键代码片段展示出来)
“好的,我先贴出五个技术亮点代码,然后直接对着代码讲难点。”
🔥 技术亮点代码
1. 注解版扫描 + 递归查找 .class
public void scan(String basePackage) {
String path = basePackage.replace('.', '/');
Enumeration<URL> resources = Thread.currentThread()
.getContextClassLoader().getResources(path);
while (resources.hasMoreElements()) {
File dir = new File(resources.nextElement().getFile());
for (File file : dir.listFiles()) {
if (file.getName().endsWith(".class")) {
String className = basePackage + "."
+ file.getName().replace(".class", "");
Class<?> clazz = Class.forName(className);
if (clazz.isAnnotationPresent(Component.class)) {
Component comp = clazz.getAnnotation(Component.class);
String beanName = comp.value().isEmpty() ?
toLowerCaseFirst(clazz.getSimpleName()) : comp.value();
beanDefinitionMap.put(beanName,
new BeanDefinition(beanName, clazz, resolveScope(clazz)));
}
}
}
}
}✨ 亮点:绕过框架直接用 ClassLoader 物理扫描,和 Spring 1.0 的思路一致。
2. 三级缓存解决循环依赖
public Object getBean(String beanName) {
// 1. 查一级缓存(成品)
Object singleton = singletonObjects.get(beanName);
if (singleton != null) return singleton;
// 2. 查二级缓存(早期半成品,解决循环依赖)
singleton = earlySingletonObjects.get(beanName);
if (singleton != null) return singleton;
// 3. 查三级缓存(工厂) → 生成早期对象,放入二级缓存
ObjectFactory<?> factory = singletonFactories.get(beanName);
if (factory != null) {
singleton = factory.getObject();
earlySingletonObjects.put(beanName, singleton);
singletonFactories.remove(beanName);
return singleton;
}
// 4. 都没有,真正创建
BeanDefinition bd = beanDefinitionMap.get(beanName);
return createBean(beanName, bd);
}3. 创建Bean流程,嵌入BeanPostProcessor
private Object createBean(String beanName, BeanDefinition bd) {
// 实例化
Object instance = bd.getBeanClass().getDeclaredConstructor().newInstance();
// 前置处理(AOP入口一)
for (BeanPostProcessor bpp : beanPostProcessors) {
instance = bpp.postProcessBeforeInitialization(instance, beanName);
}
// 如果是单例,先暴露工厂到三级缓存(解决循环依赖)
if (bd.isSingleton()) {
singletonFactories.put(beanName, () -> instance);
}
// 属性注入
populateBean(beanName, instance);
// 后置处理(AOP入口二,可返回代理)
for (BeanPostProcessor bpp : beanPostProcessors) {
instance = bpp.postProcessAfterInitialization(instance, beanName);
}
if (bd.isSingleton()) {
singletonObjects.put(beanName, instance);
earlySingletonObjects.remove(beanName);
singletonFactories.remove(beanName);
}
return instance;
}4. 属性注入(核心DI)
private void populateBean(String beanName, Object instance) {
for (Field field : instance.getClass().getDeclaredFields()) {
if (field.isAnnotationPresent(Autowired.class)) {
String dependName = field.getName(); // 简化:用字段名当beanName
Object dependBean = getBean(dependName); // 递归解决依赖
field.setAccessible(true);
field.set(instance, dependBean);
}
}
}5. BeanPostProcessor接口(AOP扩展口)
public interface BeanPostProcessor {
default Object postProcessBeforeInitialization(Object bean, String beanName) {
return bean;
}
default Object postProcessAfterInitialization(Object bean, String beanName) {
return bean;
}
}⚠️ 技术难点与解决方案
我边说边在白板上画了一个表:
| 难点 | 问题描述 | 解决方案 |
|---|---|---|
| 1. 包扫描的通用性 | 如何跨平台正确扫描类文件?直接 File 操作在 JAR 包中会失效。 | 先判断 URL 协议:file 协议用文件递归,jar 协议用 JarFile 解析,或用 ClassPathScanningCandidateComponentProvider 的简化版(我这里先忽略 jar 以精简代码)。 |
| 2. 循环依赖致死循环 | A→B,B→A 时互相等待,产生 StackOverflow。 | 三级缓存 + 提前暴露:实例化后立刻把工厂放进三级缓存,属性注入前就能拿到早期引用。构造器注入无法解决,抛 BeanCurrentlyInCreationException。 |
| 3. 依赖注入的类型匹配 | 如果多个同类型 Bean,只用字段名匹配会出错。 | 先用 @Autowired + @Qualifier 注解指定名称,再回退到类型推断,最后可引入 List<BeanDefinition> 按类型筛选。这里是简化版,展示原理。 |
| 4. AOP 代理如何无缝植入 | 动态代理替换原始 Bean,却要保证容器对外透明。 | 通过 BeanPostProcessor 的 postProcessAfterInitialization 返回代理对象,存入一级缓存的是代理,容器无感知。 |
| 5. 线程安全与性能 | 多线程同时 getBean 可能创建多个单例。 | 用 ConcurrentHashMap 存放缓存,创建单例的代码块加 synchronized 或双重检查锁,确保单例只创建一次。 |
面试官(频频点头,竖起大拇指)
“代码很清晰,尤其是三级缓存的几个Map直接放在 getBean 里用,逻辑一目了然。技术难点的表也总结得干净。我这边没问题了,你还有什么想问我的吗?”
我(微笑)
“谢谢您的肯定!想请教一下,在实际业务中,用这种迷你容器做项目脚手架,还需要注意哪些生产环境坑点?”
面试官
“好问题。主要是类加载器隔离、优雅关闭回调(@PreDestroy)、还有单测集成时注意上下文污染。你有空可以研究下 SpringBoot 的 SpringApplication 关闭钩子,思路和我们今天聊的很像。”
💪 最终总结
- 核心代码聚焦 扫描 → 三级缓存 → DI → BPP,每一段都能单独拆开讲。
- 难点覆盖了 循环依赖、代理植入、类型注入、并发安全,面试答出来绝对是高分表现。
- 表达时多用流程图、表格,让面试官一眼看到你的结构化思维。
