手写一个Spring容器

手写一个Spring容器

核心本质 ,Spring 的灵魂就是IoC(控制反转)和 DI(依赖注入),实现这两个最核心的特性,再逐步扩展生命周期和扩展点。整体思路清晰,代码量不大但能覆盖所有核心考点 ✨

核心架构设计(💡 先画架构,再写代码)

Spring 容器本质上就是一个Bean 的工厂,负责管理 Bean 的全生命周期。我设计的极简容器架构如下:

核心组件说明:

  • 📦 BeanDefinition:存储 Bean 的元数据(类名、作用域、依赖关系、初始化方法等)
  • 📝 BeanDefinitionRegistry:Bean 定义的注册表,负责注册和查询 BeanDefinition
  • 🏭 BeanFactory:容器的核心接口,定义了getBean()方法
  • 🚀 DefaultListableBeanFactory:默认容器实现,整合了所有功能

分步实现(🎯 核心代码,言简意赅)

1. 定义 Bean 元数据:BeanDefinition

java 复制代码
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

java 复制代码
public interface BeanFactory {
    // 根据Bean名称获取Bean实例
    Object getBean(String beanName) throws Exception;
}

3. 实现核心容器:DefaultListableBeanFactory

这是整个容器的心脏,我会分三步实现核心逻辑:

java 复制代码
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 方法

java 复制代码
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 方法

java 复制代码
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);
        }
    }
}

使用演示(🚀 跑起来验证)

java 复制代码
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已自动注入
    }
}

进阶优化点(💪 体现技术深度)

如果时间允许,我还会扩展以下功能,这些也是面试官常追问的点:

  1. 循环依赖处理:通过 "三级缓存" 提前暴露未初始化的 Bean 实例
  2. 作用域扩展:实现 prototype、request、session 等作用域
  3. BeanPostProcessor 扩展点:允许在 Bean 初始化前后进行自定义处理
  4. 注解支持:实现 @Component、@Autowired 注解的自动扫描和注入
  5. 销毁回调:实现 DisposableBean 接口和 destroy-method 方法

面试官高频追问(📚 提前准备,稳拿 offer)

追问问题 满分回答要点
手写的容器和真实 Spring 有什么差距? 真实 Spring 还包含 AOP、事务、事件、国际化、资源加载等模块,且在性能、并发安全、异常处理上做了大量优化
为什么 Spring 要使用三级缓存解决循环依赖? 一级缓存存成品 Bean,二级缓存存半成品 Bean,三级缓存存 Bean 工厂。只有当需要 AOP 代理时,三级缓存才会生成代理对象,避免提前创建代理
Bean 的完整生命周期有哪些阶段? 实例化 → 依赖注入 → 初始化前(BeanPostProcessor) → 初始化 → 初始化后 → 使用 → 销毁

关于这个问题的底层原理和更多实战细节,我整理了一份《大厂面试手册》,包含大厂高频面试题、源码解析和性能调优案例。

关注公众号【Rain的Java大神之路】,回复"Java"即可免费领取,持续更新中。


核心代码完整版 & 技术亮点解析

1 基础接口定义(先定义契约,再写实现)

java 复制代码
// 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 核心容器完整实现(✅ 可直接运行)

java 复制代码
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 难点全景图

text 复制代码
高频率
↑
|          * 并发安全 (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. 依赖注入时递归创建依赖 Bean2. 维护一个销毁顺序链表,销毁时逆序执行 记录 Bean 的依赖关系图,拓扑排序
⚠️ AOP 与 IoC 整合 AOP 代理对象必须代替原始 Bean 放入容器 在 BeanPostProcessor 的 postProcessAfterInitialization 方法中生成代理对象 public Object postProcessAfterInitialization(Object bean, String beanName) {`` return createProxy(bean);``}

3 最高难度:循环依赖三级缓存完整实现 ✅

java 复制代码
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 的 '配置与实例分离' 思想。📋"

java 复制代码
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() 的时候触发真正的创建。流程我拆成两大步:

  1. 反射实例化 :clazz.getDeclaredConstructor().newInstance()
  2. 属性注入 :遍历所有 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:三级,生成早期引用的工厂

流程是这样:

  1. 创建 A,先实例化,在注入属性前把 A 的 ObjectFactory 放进三级缓存。
  2. 给 A 注入时发现需要 B,触发 getBean(B)。
  3. 创建 B,注入时发现需要 A,这时从三级缓存拿到工厂生成 A 的早期引用,放入二级缓存,B 拿到这个引用先完成创建。
  4. B 创建完毕,A 拿到完整的 B,继续完成自己的注入,最后进一级缓存。

⚠️ 注意,这种方式只能解决 单例 setter 注入 的循环依赖,构造器注入无解,Spring 也是一样。🌪️"

(我顺手在白板上画出三级缓存的转移过程)

面试官(满意地放下笔)

"最后,把这个容器的主干结构写出来吧。"

我

"好。容器就叫 MiniApplicationContext,核心字段包括:

  • beanDefinitionMap:定义仓库
  • singletonObjects / earlySingletonObjects / singletonFactories:三级缓存
  • beanPostProcessors:后置处理器列表

对外只暴露 scan() 和 getBean() 两个方法,其余细节全部隐藏,完全体现 IoC 思想。📦"

java 复制代码
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

java 复制代码
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. 三级缓存解决循环依赖

java 复制代码
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

java 复制代码
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)

java 复制代码
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扩展口)

java 复制代码
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 按类型筛选。这里是简化版,展示原理。
4. AOP 代理如何无缝植入 动态代理替换原始 Bean,却要保证容器对外透明。 通过 BeanPostProcessor 的 postProcessAfterInitialization 返回代理对象,存入一级缓存的是代理,容器无感知。
5. 线程安全与性能 多线程同时 getBean 可能创建多个单例。 用 ConcurrentHashMap 存放缓存,创建单例的代码块加 synchronized 或双重检查锁,确保单例只创建一次。

面试官(频频点头,竖起大拇指)

"代码很清晰,尤其是三级缓存的几个Map直接放在 getBean 里用,逻辑一目了然。技术难点的表也总结得干净。我这边没问题了,你还有什么想问我的吗?"

我(微笑)

"谢谢您的肯定!想请教一下,在实际业务中,用这种迷你容器做项目脚手架,还需要注意哪些生产环境坑点?"

面试官

"好问题。主要是类加载器隔离、优雅关闭回调(@PreDestroy)、还有单测集成时注意上下文污染。你有空可以研究下 SpringBoot 的 SpringApplication 关闭钩子,思路和我们今天聊的很像。"

💪 最终总结

  • 核心代码聚焦 扫描 → 三级缓存 → DI → BPP,每一段都能单独拆开讲。
  • 难点覆盖了 循环依赖、代理植入、类型注入、并发安全,面试答出来绝对是高分表现。
  • 表达时多用流程图、表格,让面试官一眼看到你的结构化思维。

如果本文对你有帮助,欢迎关注我的公众号【Rain的Java大神之路】。

专注 Java 面试、源码、高并发实战,回复"Java"领取《大厂面试手册》,持续更新。

相关推荐
Rain的Java大神实战圈10 小时前
100亿订单号如何去重
场景设计题
Rain的Java大神实战圈1 天前
Git 冲突全攻略:从原理到实战,一文打通所有场景
场景设计题
Rain的Java大神实战圈1 天前
保证线程安全的方法有哪些
场景设计题
Rain的Java大神实战圈7 天前
DTO、VO、PO 到底要不要拆?一篇讲透 Java 实体分层的底层逻辑
场景设计题
Rain的Java大神实战圈8 天前
百万数据Excel如何快速导入导出
场景设计题
Rain的Java大神实战圈9 天前
如何快速上传10G文件
场景设计题
Rain的Java大神实战圈12 天前
数据脱敏是怎么做的
场景设计题
Rain的Java大神实战圈13 天前
对加密的手机号如何进行模糊查询
场景设计题
Rain的Java大神实战圈14 天前
如何自定义一个MyBatis插件
场景设计题
Rain的Java大神实战圈15 天前
高并发下的抽奖系统:不只是扣库存,还要防作弊与风控
场景设计题