- IOC: Inversion Of Control 控制反轉
- DI: Dependency Injection 依賴注入
1.控制反轉 Inversion Of Control 的前世今生
1.1 IOC理論產生的背景
討論控制反轉之前,先看看軟件系統提出控制反轉
的前世今生。
一個完整精密的軟件系統,組件之間就像齒輪,協同工作,相互耦合。
- 一個零件不正常,整個系統就崩潰了。
- 系統對象之間耦合關系無法避免,在項目規模和復雜度變大的情況下,管理類之間的依賴關系將會很復雜。
- 對象之間耦合度很高的系統,架構師和開發人員對於系統的修改,必然會出現牽一發而動全身的情形。
- 對象之間耦合性依賴,單元測試很復雜。
1.2 IOC理論
軟件專家為此提出IOC理論,用來實現對象之間的解耦。
再來看看,控制反轉(IOC)到底為什么要起這么個名字?我們來對比一下:
- 軟件系統在沒有引入IOC容器之前,對象A依賴於對象B,那么對象A在初始化或者運行到某一點的時候,自己必須主動去創建對象B或者使用已經創建的對象B。無論是創建還是使用對象B,控制權都在自己手上。
- 軟件系統在引入IOC容器之后,這種情形就完全改變了,由於IOC容器的加入,對象A與對象B之間失去了直接聯系,所以,當對象A運行到需要對象B的時候,IOC容器會主動創建一個對象B注入到對象A需要的地方。
通過前后對比,我們不難看出:
對象A獲得依賴對象B的過程,由主動變為了被動行為,控制權顛倒過來,這就是[控制反轉]的由來。
1.3 控制反轉 和 依賴注入
有些人會把控制反轉和依賴注入等同,實際上有本質區別:
控制反轉是 一種思想;
依賴注入是一種設計模式。
依賴注入是實現控制反轉的一種方式,但是控制反轉還有其他實現方式,例如說ServiceLocator
(服務定位器、依賴查找),所以不能將控制反轉和依賴注入等同。
2 依賴注入 Dependency Injection
依賴注入:容器全權負責組件的裝配,它會把符合依賴關系的對象通過屬性或者構造函數傳遞給需要的對象。
符合依賴倒置原:高層模塊不應該依賴低層模塊,兩者都應該依賴抽象;抽象不應該依賴於細節,細節應該依賴於抽象。
2.1 ASP.NET Core依賴注入
使用方式大體類似:
①. 定義依賴實現的接口或者抽象類
②. 在服務容器中注冊組件依賴 :IServiceProvider
③. 在構造函數中注入服務, 框架會負責創建和銷毀實例
// 編寫組件和服務
public interface IMyDependency
{
string WriteMessage(string message);
}
---
public class MyDependency : IMyDependency
{
public string WriteMessage(string message)
{
return $"MyDependency.WriteMessage Message: {message}";
}
}
// 注冊組件和依賴,下面注冊的`IMyDependency`在一個web請求中有效
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IMyDependency, MyDependency>();
services.AddRazorPages();
}
---
// 在構造函數注入組件
public class HomeController: AbpController
{
private readonly IMyDependency _dep;
public HomeController(IMyDependency dep)
{
_dep = dep;
}
public IActionResult Index()
{
var content = _dep.WriteMessage($"The Reflection instance is {_dep.GetType().FullName} ");
return Content(content);
}
}
在請求某個服務時,框架會完整解析出這個對象的依賴樹和作用范圍。
上面的示例代碼形成
req--->HomeController--->IMyDependency 依賴樹
IMyDependency在每個web請求范圍內使用同一服務實例。
輸出:MyDependency.WriteMessage Message: The Reflection instance is TestDI.MyDependency
2.2 對象生命周期
根據現實需要,前人從使用場景中總結出三種服務生命周期。
ASP.NET Core提供了一個枚舉ServiceLifetime
:
-- | --- | --- | --- |
---|---|---|---|
Singleton | 單例 | 服務容器首次請求會創建,后續都使用同一實例 | AddSingleton |
Scoped | 特定范圍 | 在一個請求(連接)周期內使用一個示例 | AddScoped |
Transient | 瞬時 | 服務容器每次請求,都會創建一個實例 | AddTransient |
對於Scoped Service
的理解:
在webapp:scoped service 會在請求結束時被銷毀;
在EFCore: 使用AddDbContext默認也會注冊特定范圍的DbContext,這意味在我們可以在一次sql連接內,使用同一個DbContext實例進行多次DB操作。
2.3 依賴注入實現原理
結合理論、使用方式 猜測依賴注入的原理:
實現DI,核心在於依賴注入容器IContainer
,該容器具有以下功能
①.(容器)保存可用服務的集合
// 要用的特定對象、特定類、接口服務
②.(注冊)提供一種方式將各種部件與他們依賴的服務綁定到一起;
// Add...函數或containerBuilder.Register函數,
③.(解析點)為應用程序提供一種方式來請求已配置的對象: 構造函數注入、屬性注入.
運行時,框架會一層層通過反射構造實例,最終得到完整對象。
3.源碼導航
利用反射
產生對象是依賴注入的核心過程,這也是面試造航母時經常問到的。
.NET
System.Reflection
、System.Type
命名空間中的類可以獲取可裝配組件、類、接口的信息,並提供了在運行時創建實例,調用動態實例方法、獲取動態實例的能力。
當我嘗試從github源碼中探究[依賴注入產生對象]的偽代碼時,文件/代碼眾多,迷路了!
實際上,我們可以在依賴樹的尾部對象的構造函數手動拋出異常,異常的調用棧就是一個天然的源碼導航。
於是我在上面示例代碼的request----> HomeController--->MyDependency
MyDependency構造函數中添加異常代碼:
public MyDependency()
{
throw new Exception("exception content!");
}
結果如下圖:
從Github Dependency Injection 庫進入System.Reflection的調用分界線代碼:
protected override object VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
{
object[] parameterValues;
if (constructorCallSite.ParameterCallSites.Length == 0)
{
parameterValues = Array.Empty<object>();
}
else
{
parameterValues = new object[constructorCallSite.ParameterCallSites.Length];
for (var index = 0; index < parameterValues.Length; index++)
{
parameterValues[index] = VisitCallSite(constructorCallSite.ParameterCallSites[index], context);
}
}
try
{
return constructorCallSite.ConstructorInfo.Invoke(parameterValues);
}
catch (Exception ex) when (ex.InnerException != null)
{
ExceptionDispatchInfo.Capture(ex.InnerException).Throw();
// The above line will always throw, but the compiler requires we throw explicitly.
throw;
}
}
第19行就是.NET反射特性的體現:
對類型信息(構造函數、參數)使用Invoke
方法產生對象。
干貨旁白
- 控制反轉是一種在軟件工程中解耦合的思想,調用方依賴接口或抽象類,減少了耦合,控制權交給了服務容器,由容器維護注冊項,並將具體的實現動態注入到調用方。
- 有些人會把控制反轉和依賴注入等同,實際上有本質區別:
控制反轉是 一種思想;
依賴注入是一種設計模式。
依賴注入是實現控制反轉的一種方式,但是控制反轉還有其他實現方式,例如說ServiceLocator
,所以不能將控制反轉和依賴注入等同。 - 在運行時,框架會解析依賴樹、依賴圖,通過反射在運行期生成對象。