Spring 框架核心是 IoC 容器與依賴注入 (DI)。本文深度剖析三種注入模式 (Type 1/2/3) 的實現機制、優缺點對比、適用場景與現代最佳實踐,助力靈活選型。
// 組件實現注入介面
public class ServiceA implements InjectorAware {
private DependencyB depB;
public void inject(DependencyB depB) { // 介面方法
this.depB = depB;
}
}
// 容器實現 (偽代碼)
public class Container {
public void deploy(Component comp) {
if (comp instanceof InjectorAware) {
((InjectorAware) comp).inject(lookupDependency());
}
}
}
缺點:侵入性強 (需實現特定介面)、耦合容器 API、現代 Spring 不推薦
@Component
public class OrderService {
private final PaymentService paymentService;
private final InventoryService inventoryService;
private final String currencyCode;
// 單構造器 @Autowired 可省略 (Spring 4.3+)
public OrderService(PaymentService paymentService,
InventoryService inventoryService,
@Value("${app.currency:USD}") String currencyCode) {
this.paymentService = paymentService;
this.inventoryService = inventoryService;
this.currencyCode = currencyCode;
}
}
@Component
public class NotificationService {
private EmailSender emailSender;
private SmsSender smsSender;
private PushSender pushSender;
private boolean enableEmail = true;
@Autowired(required = false) // 可選依賴
public void setEmailSender(EmailSender emailSender) {
this.emailSender = emailSender;
}
@Autowired
public void setSmsSender(SmsSender smsSender) {
this.smsSender = smsSender;
}
@Value("${notification.push.enabled:false}")
public void setPushSender(PushSender pushSender) {
this.pushSender = pushSender;
}
}
| 維度 | 構造器注入 | Setter 注入 | 介面注入 |
|---|---|---|---|
| 不可變性 | 強:final 欄位、構造後狀態固定 | 弱:可隨時修改、可為 null | 弱 |
| 依賴可見性 | 構造器簽名集中聲明 | 分散在多個 Setter | 介面方法隱含 |
| 循環依賴處理 | 無法直接解決 (需 `@Lazy`/重構) | 原生支援:早期暴露引用 | 視容器實現 |
| 可選依賴 | 需多構造器或 `@Nullable` | 天然支援:`required=false` | 視介面設計 |
| 測試友好度 | 極高:純構造器實例化、無容器依賴 | 需反射調用 Setter 或容器 | 需容器/介面實現 |
| 繼承場景 | 子類需調用 super() 傳遞依賴 | 子類可選擇覆寫/調用 super | 介面繼承 |
| 第三方類庫 | 無法修改構造器 → 不適用 | 適用:只要有 Setter | 需實現介面 → 不適用 |
| 注入順序控制 | 精確控制:參數順序即注入順序 | 無序 (依賴字母序/聲明序) | 介面方法順序 |
| IDE 支援 | 構造器參數導航、重構安全 | Setter 導航、重構風險 | 一般 |
強制依賴用構造器注入,可選依賴用 Setter 注入
@Service
public class UserService {
// === 強制依賴:構造器注入 ===
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
private final ApplicationProperties props; // 配置屬性對象
public UserService(UserRepository userRepository,
PasswordEncoder passwordEncoder,
ApplicationProperties props) {
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
this.props = props;
}
// === 可選依賴:Setter 注入 ===
private AuditLogger auditLogger; // 非核心、可缺失
private CacheManager cacheManager; // 性能優化、非必須
@Autowired(required = false)
public void setAuditLogger(AuditLogger auditLogger) {
this.auditLogger = auditLogger;
}
@Autowired(required = false)
public void setCacheManager(CacheManager cacheManager) {
this.cacheManager = cacheManager;
}
// === 業務方法 ===
public User register(RegisterRequest req) {
// 使用 final 強制依賴
// 可選依賴需空檢查
if (auditLogger != null) auditLogger.log("register", req.getUsername());
// ...
}
}
| 場景 | 推薦模式 | 理由 |
|---|---|---|
| 核心業務 Service、Repository | 構造器 | 不可變性、測試友好、依賴明確 |
| 配置屬性綁定 | 構造器 (`@ConfigurationProperties`) | 啟動期驗證、不可變配置 |
| 第三方庫 Bean (無法修改源碼) | Setter / `@Bean` 方法 | 無構造器控制權 |
| 可選功能模組 (插件化) | Setter (`required=false`) | 優雅降級、不啟動失敗 |
| 循環依賴 (應盡量重構避免) | Setter + `@Lazy` | 容器早期暴露代理對象 |
| 測試代碼 | 構造器 (手動實例化) | 零容器依賴、極速單測 |
| 遺留代碼遷移 | Setter (漸進式) | 最小改動、風險可控 |
依賴注入模式無絕對優劣,關鍵在於語義表達:構造器表達「此組件不可缺少此依賴即無法工作」,Setter 表達「此依賴增強功能但非核心」。現代 Spring 應用默認構造器注入,輔以 Setter 注入處理可選依賴,拒絕字段注入,通過架構分層消除循環依賴,實現高內聚、低耦合、可測試、可演進的代碼庫。