首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

手把手教你打造輕量級 Spring Boot 風格 IOC 容器(下):核心源碼深度解析

     編輯:Java 相關知識

承接上篇測試代碼,本文將深入剖析簡易 IOC 容器的核心實現:從自定義註解定義、掃描機制、Bean 生命周期管理,到循環依賴的三級緩存解決方案。雖然代碼量不大,但麻雀雖小、五臟俱全,是理解 Spring IOC 核心原理的最佳切入點。

專案結構一覽

專案結構圖

核心模組集中在 context、boot、annotation 三個包,完整代碼見 GitHub。

四大核心註解定義

@SimpleAutowired — 欄位級自動裝配

@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @Documented
public @interface SimpleAutowired {
    boolean required() default true;
    String value() default ""; // Bean 名稱,對應 @Qualifier
}

僅作用於欄位,融合 @Autowired + @Qualifier,簡化實現複雜度。

@SimpleBean — 方法級 Bean 定義

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented
public @interface SimpleBean { String value() default ""; }

對應 Spring @Bean,方法回傳值即為 Bean 實例,value 指定 Bean 名稱。

@SimpleComponent — 類級組件掃描

@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented
public @interface SimpleComponent { String value() default ""; }

標記類為組件掃描目標,**要求類必須有無參構造函數**,否則無法實例化。

@SimpleIocBootApplication — 啟動入口標識

@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented
public @interface SimpleIocBootApplication { String[] basePackages() default {}; }

指定掃描基礎包,未配置時預設掃描啟動類所在包及子包。

啟動流程總攬:SimpleIocApplication

public class SimpleIocApplication {
    public static void run(Class entryClass, String[] args) {
        new SimpleIocApplication(entryClass).run(args);
    }
    private void run(String[] args) {
        // 1. 建立 ApplicationContext
        SimpleApplicationContext ctx = new SimpleApplicationContext();
        // 2. 解析基礎包,建立初始化器
        ApplicationContextInitializer initializer = createInitializer(entryClass);
        // 3. 初始化上下文(掃描、實例化、裝配)
        initializer.initialize(ctx);
        // 4. 處理特殊 Bean(如 ApplicationRunner)
        processSpecialBeans(ctx, args);
    }
    private void processSpecialBeans(SimpleApplicationContext ctx, String[] args) {
        Map runners = ctx.getBeansOfType(SimpleIocApplicationRunner.class);
        runners.values().forEach(r -> { try { r.run(args); } catch (Exception e) { throw new RuntimeException(e); } });
    }
    // ... 省略輔助方法
}

掃描與初始化核心:SimpleApplicationContext

這是容器的心臟,維護四大集合:

  • registeredBeans:已完成裝配的 Bean
  • earlyBeans:已實例化但未完成裝配的 Bean(用於解決循環依賴)
  • scannedPackages:已掃描包集合,防重複
  • totalBeanCount:Bean 計數器,生成唯一名稱用

掃描入口:scan()

public void scan(Set basePackages, boolean recursively) {
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    Set> classes = new LinkedHashSet<>();
    for (String pkg : basePackages) {
        if (scannedPackages.add(pkg)) {
            classes.addAll(ClassUtil.getClassesByPackageName(cl, pkg, recursively));
        }
    }
    // 1. 處理每個類,嘗試創建 Bean
    classes.forEach(this::processSingleClass);
    // 2. 統一處理早期 Bean,完成循環依賴裝配
    processEarlyBeans();
}

單類處理:processSingleClass()

private void processSingleClass(Class clazz) {
    if (!clazz.isAnnotationPresent(SimpleComponent.class)) return;
    Object instance = clazz.newInstance(); // 需無參構造
    SimpleComponent ann = clazz.getAnnotation(SimpleComponent.class);
    String beanName = ann.value().isEmpty() ? generateUniqueName(clazz) : ann.value();

    // 嘗試裝配欄位
    boolean wired = autowireFields(instance, clazz, false);
    if (wired) registeredBeans.put(beanName, new Bean(instance, clazz));
    else earlyBeans.put(beanName, new Bean(instance, clazz));

    // 處理 @Bean 方法
    createBeansByMethodsOfClass(instance, clazz);
}

欄位裝配邏輯:autowireFields()

private boolean autowireFields(Object instance, Class clazz, boolean lastChance) throws IllegalAccessException {
    for (Field field : clazz.getDeclaredFields()) {
        SimpleAutowired ann = field.getAnnotation(SimpleAutowired.class);
        if (ann == null) continue;
        String name = ann.value();
        Bean target = getBeanByNameOrType(name, field.getType(), true); // 允許從 earlyBeans 獲取
        if (target == null) {
            if (lastChance) {
                if (!ann.required()) continue;
                throw new RuntimeException("Autowire failed: " + clazz.getName() + "." + field.getName());
            }
            return false; // 第一次裝配失敗,放入 earlyBeans 等待第二輪
        }
        field.setAccessible(true);
        field.set(instance, target.getObject());
    }
    return true;
}

循環依賴解決:processEarlyBeans()

private synchronized void processEarlyBeans() {
    for (Map.Entry entry : earlyBeans.entrySet()) {
        Bean bean = entry.getValue();
        if (autowireFields(bean.getObject(), bean.getClazz(), true)) { // lastChance=true
            registeredBeans.put(entry.getKey(), bean);
            earlyBeans.remove(entry.getKey());
        }
    }
}

這裡實現了類似 Spring 的「三級緩存」思想:實例化 → 放入 earlyBeans → 暴露早期引用 → 完成裝配 → 遷移至 registeredBeans,優雅解決 BeanA ↔ BeanB 循環依賴。

@Bean 方法處理

private void createBeansByMethodsOfClass(Object instance, Class clazz) {
    for (Method m : getMethodsWithAnnotation(clazz, SimpleBean.class)) {
        m.setAccessible(true);
        Object beanObj = m.invoke(instance);
        SimpleBean ann = m.getAnnotation(SimpleBean.class);
        String name = ann.value().isEmpty() ? generateUniqueName(beanObj.getClass()) : ann.value();
        registeredBeans.put(name, new Bean(beanObj, beanObj.getClass()));
    }
}

優缺點總結

優點缺點
代碼精簡(<500 行),核心邏輯清晰易懂功能極簡,無 AOP、事務、生命周期回調等企業級特性
支援 @Component/@Autowired/@Bean 核心註解無參構造限制、無條件裝配、無作用域控制
含循環依賴解決方案,適合教學與微型專案非生產級,生產環境請直接用 Spring Boot

適用場景

  • 深入理解 IOC/DI 底層原理
  • 個人小工具、測試專案,不想引入完整 Spring
  • 面試手寫 IOC 容器的參考實現

完整代碼:simple-ioc。若需更強大功能,請轉向 Spring Boot 官方生態。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved