javax.inject包
java提出的依賴注入標准,有別於以下傳統的對象獲取方式
- 構造方法
- 工廠模式
- 服務器定位模式(e.g. JNDI)
開發過程中是會有很多層層依賴的對象的,例如,Stopwatch依賴於TimeSource,為當前對象尋找一個所依賴對象的實例稱做解決依賴,若沒有實例被找到,則應用執行失敗,我們稱依賴不滿足
當沒有依賴注入時,也有很多解決依賴的方法,例如直接調用構造器
class Stopwatch {
final TimeSource timeSource;
Stopwatch() {
timeSource = new AtomicClock(...);
}
void start() {...}
void stop() {...}
}
如果需要更多的靈活性,可以使用工廠方法
class Stopwatch {
final TimeSource timeSource;
Stopwatch() {
timeSource = DefaultTimeSource.getInstance();
}
void start() {...}
void stop() {...}
}
我們必須權衡這兩種方式:
- 構造器很簡潔,但不靈活
- 工廠方法雖然從一定程度上解耦了調用方和具體實現,但是需要很多模版代碼
- Service locators方式雖然實現了更好的耦合,但缺少了編譯時的類型檢查
而且,這幾種方法都限制了單元測試,例如,如果我們使用工廠方法,所有依賴於依賴於工廠類的測試代碼都需要模擬出factory,還要記得在用完之后清理掉它
void testStopwatch() {
// 先獲取原始的實例
TimeSource original = DefaultTimeSource.getInstance();
// 用mock數據替換原始實例
DefaultTimeSource.setInstance(new MockTimeSource());
try {
Stopwatch sw = new StopWatch();
...
} finally {
// 將原始實例放回去以避免一些風險
DefaultTimeSource.setInstance(original);
}
}
實踐經驗告訴我們,模擬factor會導致大量的模式化代碼,大量的模擬和清理將會很快失控。
依賴注入解決了所有的這些問題
class Stopwatch {
final TimeSource timeSource;
@Inject
Stopwatch(TimeSource timeSource) {
this.timeSource = timeSource;
}
void start() {...};
void stop() {...};
}
構造器進一步的將依賴層層傳遞,直到滿足全部依賴。例如,我們需要構造一個StopwatchWidget實例:
class StopwatchWidget {
@Inject
StopwatchWidget(Stopwatch sw) {...}
}
構造器做了什么
- 找到一個TimeSource
- 利用TimeSource構造Stopwatch
- 利用Stopwatch構造StopwatchWidget
這使我們的代碼看起來更加簡潔和靈活,並從一定程度上弱化了依賴關系
在測試用例中,我們也可以直接通過像構造器傳遞模擬數據進行單元測試,再也不需要設置/清理factories了
void testStopwatch() {
Stopwatch sw = new Stopwatch(new MockTimeSource());
}