依賴注入 (Dependency Injection, DI) 是 Spring IOC 容器的核心功能,本質是「由容器負責建立對象依賴關係」。本文系統講解構造器注入、Setter 注入、集合類型注入、注入方式選擇策略,並以文檔管理系統、MVC 分層架構為實戰案例展示 IOC/DI 的工程價值。
適用場景:強依賴、不可變對象、循環依賴檢測
<bean id="person" class="com.example.Person">
<!-- 方式 A:按參數索引 -->
<constructor-arg index="0" value="張三" />
<constructor-arg index="1" ref="student" />
<!-- 方式 B:按參數類型 (推薦,順序不敏感) -->
<constructor-arg type="java.lang.String" value="李四" />
<constructor-arg type="com.example.Student" ref="student" />
</bean>
要求:類必須提供對應參數的構造器,無參構造器可選。
適用場景:可選依賴、可變對象、屬性眾多、與框架整合 (如 JPA Entity)
<bean id="person" class="com.example.Person">
<!-- 簡單類型 -->
<property name="name" value="王五" />
<property name="age" value="25" />
<!-- 引用類型 -->
<property name="student" ref="student" />
<!-- 集合類型 -->
<property name="lists">
<list>
<value>item1</value>
<ref bean="student" />
</list>
</property>
<property name="sets">
<set><value>unique1</value></set>
</property>
<property name="maps">
<map>
<entry key="k1" value="v1" />
<entry key="k2" ref="student" />
</map>
</property>
<property name="props">
<props>
<prop key="p1">v1</prop>
</props>
</property>
</bean>
要求:類需提供無參構造器 + 對應 setter 方法。
| 維度 | 構造器注入 | Setter 注入 |
|---|---|---|
| 依賴強度 | 強依賴 (必須) | 弱依賴 (可選) |
| 不可變性 | 支援 final 字段 | 字段不可為 final |
| 循環依賴 | 容器啟動時檢測報錯 | 可解決循環依賴 |
| 可讀性 | 參數多時構造器膨脹 | 屬性名語意清晰 |
| 框架相容 | 部分舊框架不支援 | 通用標準 |
現代最佳實踐:構造器注入為主,Setter 注入為輔 (Spring 4+ 官方推薦)。
// 介面
public interface Document { void read(); void write(); }
// 實現
@Component("wordDocument") public class WordDocument implements Document { ... }
@Component("pdfDocument") public class PDFDocument implements Document { ... }
// 管理類 - 依賴介面而非實現
@Component
public class DocumentManager {
@Resource(name="pdfDocument") // 運行時靈活切換實現
private Document document;
public void read() { document.read(); }
}
核心價值:代碼中零硬編碼實現類,切換實現僅修改 XML/註解配置,實現真正的面向介面編程。
// DAO 層
@Repository public class PersonDaoImpl implements PersonDao { ... }
// Service 層 - 依賴 DAO 介面
@Service
public class PersonServiceImpl implements PersonService {
@Resource private PersonDao personDao; // 注入介面
public void savePerson() { personDao.savePerson(); }
}
// Action/Controller 層 - 依賴 Service 介面
@Controller
public class PersonAction {
@Resource private PersonService personService;
public void savePerson() { personService.savePerson(); }
}
各層僅依賴上層介面,實現類由容器注入,單元測試時可輕鬆 Mock 介面。
<!-- 父 Bean 定義公共屬性 -->
<bean id="baseConfig" abstract="true">
<property name="timeout" value="30000" />
<property name="encoding" value="UTF-8" />
</bean>
<!-- 子 Bean 繼承並覆蓋 -->
<bean id="serviceA" class="com.example.ServiceA" parent="baseConfig">
<property name="timeout" value="5000" /> <!-- 覆蓋 -->
</bean>
<bean id="serviceB" class="com.example.ServiceB" parent="baseConfig" /> <!-- 直接繼承 -->
InitializingBean.afterPropertiesSet()、@PostConstruct、init-method)DisposableBean.destroy()、@PreDestroy、destroy-method)lazy-init="true 可延遲到首次獲取時初始化;depends-on 強制初始化順序。
DI 不僅是「屬性賦值」,更是解耦合、可測試、可擴展的架構基石。掌握注入方式選擇、集合注入細節、IOC 容器生命周期,能讓您設計出更優雅、可維護的 Spring 應用架構。