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

Spring 依賴注入三大模式深度對比:接口注入、構造器注入、Setter 注入

     編輯:Java 相關知識

核心概念:控制反轉 (IoC) 與依賴注入 (DI)

IoC 容器負責創建對象、管理依賴關係、注入依賴。DI 實現方式決定了依賴何時、如何、以何種形式進入組件。

Type 1:接口注入

機制

組件實現特定接口,容器通過接口方法注入依賴。

// 容器定義的接口
public interface ServiceLocator {
    void injectService(Service service);
}

// 組件實現
public class Client implements ServiceLocator {
    private Service service;
    public void injectService(Service service) { this.service = service; }
}

評價

  • ❌ 侵入性強:組件必須實現容器專屬接口,污染業務代碼。
  • ❌ 靈活性差:每種依賴需一個注入接口,接口爆炸。
  • ✅ 早期容器 (Avalon、早期 J2EE Context.lookup()) 採用。
  • 現代 Spring 不推薦、不直接支持。

Type 2:構造器注入

機制

通過構造函數參數注入依賴,構造函數即依賴聲明。

public class OrderService {
    private final PaymentGateway gateway;
    private final InventoryService inventory;
    private final String currency;

    public OrderService(PaymentGateway gateway, InventoryService inventory, 
                        @Value("${app.currency}") String currency) {
        this.gateway = gateway;
        this.inventory = inventory;
        this.currency = currency;
    }
}

優勢

優勢說明
不可變性final 字段,構造後狀態固化,線程安全
強依賴契約缺少依賴啟動失敗,快速失敗,避免運行時 NPE
依賴可見構造函數簽名即文檔,IDE 可直接導航
注入順序可控參數順序決定初始化順序,解決循環依賴、初始化順序敏感場景
黑盒封裝調用者無需關心內部依賴,符合迪米特法則
無 Setter 汙染防止運行時被惡意/誤修改,單例組件尤為關鍵

劣勢

  • 構造函數參數過多(>5-7)時可讀性下降 → 考慮拆分組件或用 Builder。
  • 第三方庫強制無參構造函數 (Struts 1 Action、JPA Entity、部分序列化框架) 無法使用。
  • 循環依賴無法直接解決(需 @Lazy 或重構)。

Type 3:Setter 注入

機制

通過 setter 方法注入依賴,符合 JavaBean 規範。

public class NotificationService {
    private EmailSender emailSender;
    private SmsSender smsSender;
    private PushSender pushSender;
    private String templatePath = "default.vm"; // 可選依賴有默認值

    public void setEmailSender(EmailSender emailSender) { this.emailSender = emailSender; }
    public void setSmsSender(SmsSender smsSender) { this.smsSender = smsSender; }
    public void setPushSender(PushSender pushSender) { this.pushSender = pushSender; }
    public void setTemplatePath(String templatePath) { this.templatePath = templatePath; }
}

優勢

  • 直觀自然,符合 JavaBean 慣例,工具鏈支持最好。
  • 可選依賴友好:提供默認值,不注入也能工作。
  • 解決循環依賴:A 注入 B,B 注入 A,先構造再 setter 可打破循環。
  • 第三方庫兼容:無參構造 + setter 滿足絕大多數框架要求。

劣勢

  • 可變狀態:運行時可被修改,單例組件存在線程安全風險。
  • 隱性依賴:類定義看不出必需依賴,啟動時才發現缺失。
  • Setter 爆炸:依賴多時代碼冗長。
  • 部分注入風險:調用者可能在對象未完全初始化時使用。

Spring 官方建議與最佳實踐

「構造器注入用於強制依賴,Setter 注入用於可選依賴。」 — Spring Reference Documentation

混合模式範例

@Component
public class ReportGenerator {
    // 強制依賴:構造器注入
    private final DataSource dataSource;
    private final ReportTemplateEngine engine;

    // 可選依賴:Setter 注入 + 默認值
    private OutputFormat format = OutputFormat.PDF;
    private String footerText = "Confidential";

    public ReportGenerator(DataSource dataSource, ReportTemplateEngine engine) {
        this.dataSource = dataSource;
        this.engine = engine;
    }

    public void setFormat(OutputFormat format) { this.format = format; }
    public void setFooterText(String footerText) { this.footerText = footerText; }
}

進階:Lombok 簡化構造器注入

@Component
@RequiredArgsConstructor // 生成包含 final/@NonNull 字段的構造函數
@Setter(onMethod_ = @Autowired) // 為非 final 字段生成 setter 並標記 @Autowired
public class OrderService {
    private final PaymentGateway gateway;        // 構造器注入
    private final InventoryService inventory;    // 構造器注入
    
    @Autowired(required = false)                 // 可選 Setter 注入
    private DiscountService discountService;
    
    private String currency = "USD";             // 默認值,可選 Setter
}

循環依賴解決方案對比

方案適用注入模式原理缺點
@Lazy 代理構造器注入代理,首次調用時實例化掩蓋設計問題、代理開銷
Setter 注入Setter先構造再注入,打破循環引入可變狀態
@PostConstruct 初始化構造器 + Setter構造時注入 Provider/Factory,初始化時獲取實例代碼複雜化
重構拆分任意提取公共接口、引入中介者、事件總線需修改架構

總結決策矩陣

場景推薦模式理由
核心業務服務、Repository、強依賴組件構造器注入不可變、快速失敗、線程安全
可選功能、配置屬性、第三方庫兼容Setter 注入默認值、無參構造、循環依賴緩解
遺留代碼、Struts/JSF/JPA 實體Setter 注入框架強制要求
新專案綠地開發構造器為主 + Setter 輔最佳實踐平衡點

結語

理解三種注入模式的本質差異、適用邊界、組合策略,是掌握 Spring IoC 容器、編寫高質量可維護代碼的關鍵。現代 Spring 生態默認構造器注入,配合 Lombok @RequiredArgsConstructor 實現「零樣板、強契約、高可讀」。

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