討論控制反轉以前,先看看軟件系統提出控制反轉
的前世此生。
一個完整精密的軟件系統,組件之間就像齒輪,協同工做,相互耦合。web
軟件專家爲此提出IOC理論,用來實現對象之間的解耦。
再來看看,控制反轉(IOC)到底爲何要起這麼個名字?咱們來對比一下:面試
經過先後對比,咱們不難看出:
對象A得到依賴對象B的過程,由主動變爲了被動行爲,控制權顛倒過來,這就是「控制反轉」的由來。sql
有些人會把控制反轉和依賴注入等同,實際上有本質區別:
控制反轉是一種思想;依賴注入是一種設計模式。
依賴注入是實現控制反轉的一種方式,可是控制反轉還有其餘實現方式,例如說ServiceLocator
(服務定位器、依賴查找),因此不能將控制反轉和依賴注入等同。設計模式
依賴注入:容器全權負責組件的裝配,它會把符合依賴關係的對象經過屬性或者構造函數傳遞給須要的對象。架構
符合依賴倒置原則,高層模塊不該該依賴低層模塊,二者都應該依賴其抽象app
使用方式大致相似:框架
IServiceProvider
1 // 編寫組件和服務 2 public interface IMyDependency 3 { 4 string WriteMessage(string message); 5 } 6 --- 7 public class MyDependency : IMyDependency 8 { 9 public string WriteMessage(string message) 10 { 11 return $"MyDependency.WriteMessage Message: {message}"; 12 } 13 } 14 // 註冊組件和依賴,下面註冊的`IMyDependency`在一個web請求中有效 15 public void ConfigureServices(IServiceCollection services) 16 { 17 services.AddScoped<IMyDependency, MyDependency>(); 18 services.AddRazorPages(); 19 } 20 --- 21 // 在構造函數注入組件 22 public class HomeController: AbpController 23 { 24 private readonly IMyDependency _dep; 25 public HomeController(IMyDependency dep) 26 { 27 _dep = dep; 28 } 29 30 public IActionResult Index() 31 { 32 var content = _dep.WriteMessage($"The Reflection instance is {_dep.GetType().FullName} "); 33 return Content(content); 34 } 35 }
在請求某個服務時,框架會完整解析出這個對象的依賴樹和做用範圍。webapp
上面的示例代碼造成 req--->HomeController--->IMyDependency依賴樹。編輯器
IMyDependency在每一個web請求範圍內使用同一服務實例。ide
輸出:MyDependency.WriteMessage Message: The Reflection instance is TestDI.MyDependency
根據現實須要,前人從使用場景中總結出三種服務生命週期。
ASP.NET Core提供了一個枚舉ServiceLifetime
:
-- | --- | --- | --- |
---|---|---|---|
Singleton | 單例 | 服務容器首次請求會建立,後續都使用同一實例 | AddSingleton |
Scoped | 特定範圍 | 在一個請求(鏈接)週期內使用一個示例 | AddScoped |
Transient | 瞬時 | 服務容器每次請求,都會建立一個實例 | AddTransient |
對於Scoped Service
的理解:
在webapp:scoped service 會在請求結束時被銷燬;
在EFCore:使用AddDbContext默認註冊的是特定範圍的DbContext,這意味在咱們能夠在一次sql鏈接內,使用同一個DbContext實例進行屢次DB操做。
結合理論、使用方式 猜想依賴注入的原理:
實現DI,核心在於依賴注入容器IContainer
,該容器具備如下功能
①.(容器)保存可用服務的集合
// 要用的特定對象、特定類、接口服務
②.(註冊)提供一種方式將各類部件與他們依賴的服務綁定到一塊兒;
// Add...函數或containerBuilder.Register函數
③.(解析點)爲應用程序提供一種方式來請求已配置的對象:構造函數注入、屬性注入.
運行時,框架會一層層經過反射構造實例,最終獲得完整對象。
利用反射
產生對象是依賴注入的核心過程,這也是面試造航母時常常問到的。
.NETSystem.Reflection
、System.Type
命名空間中的類能夠獲取可裝配組件、類、接口的信息,並提供了在運行時建立實例,調用動態實例方法、獲取動態實例的能力。
實際上,咱們能夠在依賴樹的尾部對象的構造函數手動拋出異常,異常的調用棧就是一個自然的源碼導航。
因而我在上面示例代碼的request----> HomeController--->MyDependency MyDependency構造函數中添加異常代碼:
1 public MyDependency() 2 { 3 throw new Exception("exception content!"); 4 }
結果以下圖:
從Github Dependency Injection 庫進入System.Reflection的調用分界線代碼:
1 protected override object VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context) 2 { 3 object[] parameterValues; 4 if (constructorCallSite.ParameterCallSites.Length == 0) 5 { 6 parameterValues = Array.Empty<object>(); 7 } 8 else 9 { 10 parameterValues = new object[constructorCallSite.ParameterCallSites.Length]; 11 for (var index = 0; index < parameterValues.Length; index++) 12 { 13 parameterValues[index] = VisitCallSite(constructorCallSite.ParameterCallSites[index], context); 14 } 15 } 16 17 try 18 { 19 return constructorCallSite.ConstructorInfo.Invoke(parameterValues); 20 } 21 catch (Exception ex) when (ex.InnerException != null) 22 { 23 ExceptionDispatchInfo.Capture(ex.InnerException).Throw(); 24 // The above line will always throw, but the compiler requires we throw explicitly. 25 throw; 26 } 27 }
黃色背景行就是.NET反射特性的體現:
對類型信息(構造函數、參數)使用Invoke
方法產生對象。
ServiceLocator
,因此不能將控制反轉和依賴注入等同。