許多開發者在使用 Spring 多年後,依然對 IOC 容器的核心運作機制感到好奇。本文將帶你從零開始,模仿 Spring Boot 的啟動模式與註解設計,一步步實現一個精簡卻功能完備的 IOC 容器。無論是為了深入理解依賴注入原理,還是想在個人小專案中擺脫龐大 Spring 框架的束縛,這都是一份極佳的實戰指南。
作者在專案中長期使用 Spring 3.x,直到發現 Spring 5 即將釋出,並接觸到 Spring Boot 這個「解脫 XML 配置地獄」的利器。研讀 Spring Boot 1.5.2 官方文檔後,他決定仿照其專案結構與核心註解,親手寫一個簡易 IOC 容器。目標有二:一是加深對 IOC 原理的理解,二是為未來個人專案提供一個開箱即用、輕量級的自動裝配工具,無需引入整個 Spring 生態。
在動工寫容器代碼前,先定義好「使用者視角」的測試場景。測試專案結構如下:
sample
├── SampleApplication.java // 啟動入口
├── runner
│ └── SampleApplicationRunner.java // 容器啟動後的回調任務
└── model
├── BeanA.java
├── BeanB.java
└── BeanC.java
完全照搬 Spring Boot 風格,將啟動任務委託給 SimpleIocApplication:
@SimpleIocBootApplication
public class SampleApplication {
public static void main(String[] args) {
SimpleIocApplication.run(SampleApplication.class, args);
}
}
@SimpleIocBootApplication 可指定掃描基礎包路徑,未配置時預設掃描啟動類所在包及其子包。
實現 SimpleIocApplicationRunner 介面,容器裝配完成後自動調用 run() 方法:
@SimpleComponent
public class SampleApplicationRunner implements SimpleIocApplicationRunner {
@SimpleAutowired private BeanA beanA;
@SimpleAutowired private BeanB beanB;
@SimpleAutowired("beanC1") private BeanC beanC1;
@SimpleAutowired("beanC2") private BeanC beanC2;
@Override
public void run(String[] args) throws Exception {
beanA.print();
beanB.print();
System.out.println(beanC1);
System.out.println(beanC2);
}
@SimpleBean("beanC1")
private BeanC createBeanC1() { return new BeanC("I am bean c1!"); }
@SimpleBean("beanC2")
private BeanC createBeanC2() { return new BeanC("I am bean c2!"); }
}
關鍵註解說明:
@Component,標記為自動掃描目標。@Autowired 與 @Qualifier 功能,支援 required 與 value(Bean 名稱)屬性。@Bean,根據方法回傳值生成 Bean。@SimpleComponent
public class BeanA {
@SimpleAutowired private BeanB beanB;
public void print() { System.out.printf("[BeanA].beanB = %s\n", beanB); }
}
@SimpleComponent
public class BeanB {
@SimpleAutowired private BeanA beanA;
public void print() { System.out.printf("[BeanB].beanA = %s\n", beanA); }
}
兩者互相引用,構成典型的循環依賴場景,容器必須能正確解決。
BeanC 未加 @SimpleComponent,僅作為 @SimpleBean 方法的回傳類型測試:
public class BeanC {
private String content;
public BeanC(String content) { this.content = content; }
@Override public String toString() { return "BeanC.content = " + content; }
}
測試代碼就緒,下一篇將深入剖析容器核心實現:掃描機制、Bean 定義註冊、依賴注入與循環依賴解決方案。完整代碼已開源:simple-ioc。