Spring 框架的核心是 IOC (Inversion of Control,控制反轉) 和 DI (Dependency Injection,依賴注入)。許多初學者對這兩個概念感到模糊:它們是技術還是思想?有什麼區別?為什齉稱之為「反轉」?本文綜合多位技術專家(如 Iteye 開濤、Bromon 等)的經典講解,結合實際開發經驗,從設計哲學、核心原理、實現機制三個維度,徹底講透 Spring IOC/DI。
IOC——Inversion of Control,即「控制反轉」,不是什麼技術,而是一種設計思想。在 Java 開發中,IOC 意味著將你設計好的對象交給容器控制,而不是傳統的在對象內部直接控制。
| 維度 | 傳統模式 (正轉) | IOC 模式 (反轉) |
|---|---|---|
| 誰控制誰 | 對象主動 new 依賴對象 | IOC 容器控制對象創建 |
| 控制什麼 | 對象創建、依賴查找、生命周期 | 外部資源獲取 (對象、文件、配置等) |
| 依賴獲取方式 | 主動查找 (主動權在調用者) | 被動接受注入 (主動權在容器) |
| 耦合度 | 高耦合,難測試、難復用 | 鬆耦合,易測試、易擴展 |
傳統應用程序結構:
[Client] ──new──► [ServiceA] ──new──► [DaoA] │ │ │ └──new──► [ServiceB] ──new──► [DaoB]
所有對象創建鏈路硬編碼在調用者中,修改實現需改動代碼重新編譯。
有 IOC 容器後的結構:
[IOC Container] │ ├─創建、管理──► [ServiceA] ──注入──► [DaoA] │ ├─創建、管理──► [ServiceB] ──注入──► [DaoB] │ └─創建、管理──► [Client] ───注入──► [ServiceA/ServiceB]
客戶端不再主動創建依賴,而是聲明需求,由容器在運行期注入。
IOC 完美體現了面向對象設計原則中的好萊塢法則:組件只需聲明依賴接口,具體實現由容器在適當時機注入。應用程序從「主動尋找資源」變為「被動等待資源」,實現了主從換位。
DI——Dependency Injection,即「依賴注入」:組件之間依賴關系由容器在運行期決定,形象地說,即由容器動態地將某個依賴關系注入到組件之中。
Martin Fowler 2004 年提出「依賴注入」一詞,旨在比「控制反轉」更具體地描述:「被注入對象依賴 IOC 容器配置依賴對象」。兩者是同一概念的不同角度描述:IOC 強調控制權轉移,DI 強調依賴關系的動態構建。
| 注入方式 | 特點 | 適用場景 |
|---|---|---|
| 構造器注入 | 強制依賴、不可變性、天然線程安全 | 必要依賴、不可變對象 |
| Setter 注入 | 可選依賴、可重新配置、符合 JavaBean 規範 | 可選依賴、運行時重配置 |
| 接口注入 | 實現注入接口、強契約 | 框架級開發、插件架構 |
現代 Spring 推薦構造器注入作為首選,配合 @Required 或驗證框架確保依賴完整性。
Iteye 開濤從「是什麼、能做什麼、IOC與DI關系」三個維度系統闡述:
用「找女朋友」vs「婚介所」的生動比喻:
傳統模式:自己找、自己聯系、自己約會、自己分手——全程主動控制,復雜深奧。
IOC 模式:註冊婚介所(容器)、提交要求(配置)、等待匹配(注入)、直接見面(使用)——控制權交給第三方。
Spring 容器就是這個「婚介所」:管理所有對象生命周期,根據配置主動注入依賴。
並指出反射技術是 DI 實現的基石:運行期動態生成對象、執行方法、修改屬性。
BeanFactory (頂層接口)
└─ ApplicationContext (擴展接口:事件、國際化、資源加載)
├─ ClassPathXmlApplicationContext
├─ FileSystemXmlApplicationContext
└─ AnnotationConfigApplicationContext
核心輔助組件:
BeanDefinitionReader → 解析配置
BeanFactoryPostProcessor → BeanDefinition 後處理 (如 PropertyPlaceholderConfigurer)
BeanPostProcessor → Bean 實例後處理 (如 AutowiredAnnotationProcessor, AOP 代理創建)
InstantiationStrategy → 實例化策略 (CGLIB/字節碼增強)
Spring 通過三級緩存解決單例模式下的循環依賴:
流程:A 實例化 → 放入 singletonFactories → 屬性注入 B → B 實例化 → 注入 A (從 singletonFactories 獲取早期引用) → B 初始化完成 → A 繼續初始化完成。
// 1. XML 時代 (Spring 1.x/2.x)
<bean id="userService" class="com.example.UserServiceImpl">
<property name="userDao" ref="userDao"/>
</bean>
// 2. 註解時代 (Spring 2.5+)
@Service
public class UserServiceImpl implements UserService {
@Autowired private UserDao userDao;
}
// 3. Java Config 時代 (Spring 3.0+)
@Configuration
@ComponentScan("com.example")
public class AppConfig {
@Bean public UserService userService() {
return new UserServiceImpl(userDao());
}
@Bean public UserDao userDao() {
return new UserDaoImpl();
}
}
// 4. Spring Boot 時代 (自動配置 + 條件裝配)
@SpringBootApplication
public class Application { public static void main(String[] args) {...} }
| 誤區 | 後果 | 正確做法 |
|---|---|---|
| 濫用 @Autowired 欄位注入 | 難測試、隱藏依賴、不可變性破壞 | 優先構造器注入,配合 final 修飾 |
| 將所有類納入組件掃描 | 啟動慢、Bean 衝突、內存浪費 | 精確配置 base-package、使用 @ComponentScan 過濾器 |
| 忽略 Bean 作用域 | 線程安全問題、狀態洩露 | 明確聲明 @Scope("prototype/request/session") |
| 循環依賴作為常態 | 設計缺陷、啟動失敗風險 | 重構設計:提取公共接口、引入中介者、事件驅動 |
Spring IOC 的核心價值不在於「依賴注入」這項技術本身,而在於它帶來的架構哲學變革:
理解 IOC 的關鍵在於從代碼層面上升到設計思想層面。當你開始思考「這個類真正需要什麼?由誰提供?何時提供?如何變更?」時,你就真正掌握了 IOC 的精髓。這正是 Spring 能夠統治 Java 企業級開發十多年的根本原因——它不只是一個框架,更是一種優雅的軟件架構哲學。