IoC (Inversion of Control) 和 DI (Dependency Injection) 是現代企業級框架的基石,Spring 框架更是將其發揮到極致。本文從概念本質、實現機制、三種注入方式、容器架構、最佳實踐五個維度,徹底講透這對「雙子星」概念。
定義:將對象的創建權、依賴管理權、生命週期控制權從應用代碼「反轉」給容器框架。
// 傳統編程:主動控制
public class UserService {
private UserDao userDao = new UserDaoImpl(); // 硬編碼依賴
private EmailService emailService = new SmtpEmailService(); // 直接實例化
}
// IoC 容器管理:反轉控制權
public class UserService {
private UserDao userDao; // 只聲明依賴,不創建
private EmailService emailService;
// 容器負責注入具體實現
public void setUserDao(UserDao userDao) { this.userDao = userDao; }
public void setEmailService(EmailService emailService) { this.emailService = emailService; }
}
| 傳統模式痛點 | IoC 解決方案 |
|---|---|
| 高耦合:業務代碼依賴具體實現類 | 面向接口編程,依賴抽象而非實現 |
| 難測試:無法輕易替換為 Mock 對象 | 測試時注入 Mock/Stub 實現 |
| 難擴展:修改實現需改動業務代碼 | 配置文件/註解切換實現,符合開閉原則 |
| 生命週期分散:對象創建/銷毀邏輯散落 | 統一由容器管理 (單例/原型/請求/會話) |
| 配置硬編碼:數據源、線程池參數寫死代碼 | 外部化配置,環境隔離 (dev/test/prod) |
定義:容器在運行時,根據配置元數據,將依賴對象「注入」到被依賴對象中。DI 是實現 IoC 的具體技術手段。
// Martin Fowler 經典定義:
// "Dependency Injection is a specific form of Inversion of Control where the
// concern being inverted is the process of obtaining dependencies."
// 關係:IoC 是「何」與「為何」,DI 是「如何」
// IoC 容器 = DI 容器 (業界通用叫法)
@Component
public class OrderService {
private final PaymentService paymentService; // final 確保不可變
private final InventoryService inventoryService;
private final String orderPrefix; // 可注入基本類型/配置
// 單構造器 @Autowired 可省略 (Spring 4.3+)
public OrderService(PaymentService paymentService,
InventoryService inventoryService,
@Value("${order.prefix:ORD}") String orderPrefix) {
this.paymentService = paymentService;
this.inventoryService = inventoryService;
this.orderPrefix = orderPrefix;
}
}
@Component
public class NotificationService {
private EmailService emailService;
private SmsService smsService;
private PushService pushService;
private int retryCount = 3; // 可選依賴有默認值
@Autowired(required = false) // 可選依賴
public void setEmailService(EmailService emailService) {
this.emailService = emailService;
}
@Autowired
public void setSmsService(SmsService smsService) {
this.smsService = smsService;
}
// 無 @Autowired 也可由 XML 配置注入
public void setPushService(PushService pushService) {
this.pushService = pushService;
}
public void setRetryCount(int retryCount) {
this.retryCount = retryCount;
}
}
@Component
public class ReportService {
@Autowired
private DataSource dataSource; // 反射強制注入,無視 private
@Autowired
@Qualifier("redisTemplate") // 多實現指定
private RedisTemplate redisTemplate;
@Value("${report.cache.ttl:3600}")
private int cacheTtl;
}
| 維度 | 構造器注入 | Setter 注入 | 字段注入 |
|---|---|---|---|
| 不可變性 | ✅ 支持 final | ❌ 可變 | ❌ 可變 |
| 非空保證 | ✅ 編譯期 | ❌ 運行期 | ❌ 運行期 |
| 循環依賴 | ❌ 不支持 | ✅ 支持 | ⚠️ 風險高 |
| 可選依賴 | ❌ 需重載構造器 | ✅ required=false | ✅ required=false |
| 測試友好度 | ✅ 純 new 即可 | ✅ 需調用 setter | ❌ 需反射/Spring 上下文 |
| DDD 風格 | ✅ 推薦 | ⚠️ 部分場景 | ❌ 反模式 |
| 官方推薦度 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐ (不推薦) |
// BeanDefinition 屬性:autowireMode
public interface BeanDefinition {
int AUTOWIRE_NO = 0; // 手動注入 (XML 顯式配置)
int AUTOWIRE_BY_NAME = 1; // 按名稱匹配 setter
int AUTOWIRE_BY_TYPE = 2; // 按類型匹配 (單一候選)
int AUTOWIRE_CONSTRUCTOR = 3; // 按類型匹配構造器參數
int AUTOWIRE_AUTODETECT = 4; // 嘗試構造器,失敗回退 byType
}
// 現代 Spring 主要使用:AUTOWIRE_BY_TYPE (配合 @Autowired/@Qualifier)
// @Autowired 默認 byType,多候選時需 @Qualifier 指定名稱
// @Resource (JSR-250) 默認 byName,找不到回退 byType
// AutowiredAnnotationBeanPostProcessor.postProcessProperties()
@Override
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
try {
metadata.inject(bean, beanName, pvs); // 核心注入邏輯
} catch (Throwable ex) {
throw new BeanCreationException(beanName, "Injection failed", ex);
}
return pvs;
}
// InjectionMetadata.inject() 遍歷所有注入點
// 1. 構造器注入點:ConstructorInjectionPoint
// 2. 字段注入點:FieldInjectionPoint
// 3. 方法注入點:MethodInjectionPoint (setter)
// 每個注入點執行:
// 1. 解析依賴描述符 DependencyDescriptor (類型、註解、required)
// 2. beanFactory.resolveDependency(descriptor, beanName, autowiredBeanNames, typeConverter)
// 3. 反射設置值 (Field.set / Method.invoke / Constructor.newInstance)
// DefaultListableBeanFactory.resolveDependency()
public Object resolveDependency(DependencyDescriptor descriptor,
String requestingBeanName,
Set autowiredBeanNames,
TypeConverter typeConverter) {
// 1. 先查找 @Value / SpEL 表達式
if (descriptor.getField() != null || descriptor.getMethodParameter() != null) {
Object value = resolveValue(descriptor, requestingBeanName);
if (value != null) return value;
}
// 2. 類型匹配查找候選 Bean
Class> type = descriptor.getDependencyType();
String[] candidateNames = beanFactory.getBeanNamesForType(type, true, false);
// 3. 過濾:@Primary、@Qualifier、autowireCandidate
String selectedName = determineAutowireCandidate(candidateNames, descriptor);
// 4. 獲取實例 (觸發創建/從緩存獲取)
return getBean(selectedName);
}
// 多候選解決優先級:
// 1. @Qualifier("exactName") 精確匹配
// 2. @Primary 標註的 Bean
// 3. Bean 名稱與注入點字段/參數名匹配
// 4. 拋出 NoUniqueBeanDefinitionException
// 定義注入契約
public interface EmailServiceAware {
void setEmailService(EmailService emailService);
}
// 實現類自動實現接口
@Component
public class UserService implements EmailServiceAware {
private EmailService emailService;
@Override
public void setEmailService(EmailService emailService) {
this.emailService = emailService;
}
}
// 容器識別接口並回調 (需實現 Aware 接口或自定義 BPP)
// Spring 內建 Aware 接口:BeanFactoryAware, ApplicationContextAware, EnvironmentAware...
// 原型 Bean 每次獲取新實例
@Component
public class CommandManager {
// 容器自動實現此抽象方法,每次調用返回新的 Prototype Bean
@Lookup("command") // 或 XML
protected abstract Command createCommand();
public void process() {
Command cmd = createCommand(); // 每次獲得新實例
cmd.execute();
}
}
@Component
@Scope("prototype")
public class Command { ... }
// 解決循環依賴、啟動性能優化
@Component
public class HeavyService {
@Autowired
@Lazy // 注入代理,首次調用時才真實創建
private HeavyDependency heavyDependency;
}
// 或在依賴端標註
@Component
@Lazy // 整個 Bean 延遲初始化
public class HeavyDependency { ... }
// 數據源場景:主庫 + 從庫
@Configuration
public class DataSourceConfig {
@Bean
@Primary // 默認注入主庫
@ConfigurationProperties("spring.datasource.master")
public DataSource masterDataSource() { return DataSourceBuilder.create().build(); }
@Bean
@ConfigurationProperties("spring.datasource.slave")
public DataSource slaveDataSource() { return DataSourceBuilder.create().build(); }
}
// 業務代碼
@Service
public class OrderService {
@Autowired
private DataSource dataSource; // 自動注入 master (有 @Primary)
@Autowired
@Qualifier("slaveDataSource") // 顯式指定從庫
private DataSource slaveDataSource;
}
@Component
public class SearchService {
private final ElasticsearchClient esClient; // 必須依賴
private final RedisClient redisClient; // 可選依賴
private final MetricsCollector metricsCollector; // 可選依賴
public SearchService(ElasticsearchClient esClient,
@Autowired(required = false) RedisClient redisClient,
@Autowired(required = false) MetricsCollector metricsCollector) {
this.esClient = esClient;
this.redisClient = redisClient;
this.metricsCollector = metricsCollector;
}
public void search() {
// 可選依賴需非空判斷
if (redisClient != null) { ... }
}
}
// 高層模塊不依賴低層模塊,雙方依賴抽象
// 抽象不依賴細節,細節依賴抽象
// 領域層 (核心業務,無框架依賴)
public interface OrderRepository {
Order findById(String id);
void save(Order order);
}
// 基礎設施層 (實現細節)
@Repository
public class JpaOrderRepository implements OrderRepository { ... }
@Repository
public class MongoOrderRepository implements OrderRepository { ... }
// 應用層 (編排領域對象)
@Service
public class OrderService {
private final OrderRepository orderRepository; // 依賴抽象
public OrderService(OrderRepository orderRepository) {
this.orderRepository = orderRepository;
}
}
// 支付策略接口
public interface PaymentStrategy {
PayResult pay(PayRequest request);
}
// 多種實現
@Component("alipay")
public class AlipayStrategy implements PaymentStrategy { ... }
@Component("wechatpay")
public class WechatPayStrategy implements PaymentStrategy { ... }
@Component("unionpay")
public class UnionPayStrategy implements PaymentStrategy { ... }
// 策略工廠 (DI 注入 Map)
@Component
public class PaymentStrategyFactory {
private final Map strategies;
@Autowired
public PaymentStrategyFactory(Map strategies) {
this.strategies = strategies; // Spring 自動收集所有實現
}
public PaymentStrategy getStrategy(String payType) {
return strategies.get(payType);
}
}
// 使用
@Service
public class OrderPaymentService {
private final PaymentStrategyFactory factory;
public PayResult pay(Order order) {
PaymentStrategy strategy = factory.getStrategy(order.getPayType());
return strategy.pay(new PayRequest(order));
}
}
@ConfigurationProperties(prefix = "app.payment")
@Validated
@Data
public class PaymentProperties {
@NotNull
private String merchantId;
@Min(1) @Max(10000)
private int timeoutMs = 5000;
private Map channelMapping = new HashMap<>();
private List supportedCurrencies = List.of("CNY", "USD");
}
// 啟用綁定
@Configuration
@EnableConfigurationProperties(PaymentProperties.class)
public class PaymentConfig {
@Bean
public PaymentClient paymentClient(PaymentProperties props) {
return new PaymentClient(props.getMerchantId(), props.getTimeoutMs());
}
}
| 反模式 | 問題 | 修正方案 |
|---|---|---|
| ServiceLocator 模式 | context.getBean() 到處調用,隱藏依賴 | 構造器注入聲明依賴,容器自動裝配 |
| 字段注入泛濫 | 測試難、封裝破壞、循環依賴 | 遷移至構造器注入 (IDE 可自動重構) |
| 循環依賴不重構 | @Lazy 掩蓋設計缺陷 | 拆分公共依賴、引入事件總線、調整職責邊界 |
| God Object 注入過多依賴 | 單一職責違反、構造器參數>5 | 拆分服務、引入 Facade、聚合根設計 |
| 配置硬編碼 | 環境遷移困難、敏感信息洩露 | @Value/${} / @ConfigurationProperties 外部化 |
| 濫用 @Autowired(required=false) | 隱性可選依賴,業務邏輯充斥非空判斷 | 引入 Null Object 模式、Option 類型、或重構為必須依賴 |
@ExtendWith(MockitoExtension.class)
class OrderServiceTest {
@Mock private PaymentService paymentService;
@Mock private InventoryService inventoryService;
private OrderService orderService;
@BeforeEach
void setUp() {
// 直接構造器注入 Mock,無需 Spring 上下文
orderService = new OrderService(paymentService, inventoryService, "TEST");
}
@Test
void shouldCreateOrder() {
// 純單元測試,毫秒級執行
}
}
@SpringBootTest
@AutoConfigureMockMvc
class OrderControllerIntegrationTest {
@Autowired private MockMvc mockMvc;
@MockBean // 替換容器中真實 Bean
private PaymentService paymentService;
@Test
void shouldReturn200() throws Exception {
when(paymentService.pay(any())).thenReturn(PayResult.success());
mockMvc.perform(post("/orders").contentType(JSON))
.andExpect(status().isOk());
}
}
@SpringBootTest
@Import(TestConfig.class) // 僅測試用配置
class ServiceIntegrationTest {
@TestConfiguration
static class TestConfig {
@Bean
@Primary // 覆蓋生產 Bean
public ExternalApiClient externalApiClient() {
return new MockExternalApiClient(); // 測試雙
}
}
}
IoC 是架構哲學,DI 是落地技術。 Spring 通過 BeanFactory/ApplicationContext 實現了功能完備的 DI 容器,支援構造器/Setter/字段三種注入方式,提供 @Autowired/@Qualifier/@Primary/@Lazy/@Value/@ConfigurationProperties 等豐富注解。
現代最佳實踐原則:
深度理解 DI 原理,不僅能寫出更優雅的 Spring 代碼,更能在架構設計、測試策略、故障排查中遊刃有餘。