.NET CORE 依賴注入 實踐總結
阿新 • • 發佈:2020-05-27
知識點回顧
- 依賴包。 Microsoft.Extensions.DependencyInjection.Abstractions
- 核心物件和方法。
- IServiceCollection。注入物件的容器。注意它只儲存物件的元資料,並不儲存例項物件。
- IServiceProvider。注入物件的提供者。它負責提供具體的物件例項。在架構中,IServiceProvider有2種身份,一種是Root ServiceProvider,由service.BuildServiceProvider()建立,生命週期貫穿整個應用程式,AddSingleton物件儲存在這裡。另外一種則是普通IServiceProvider,由IServiceScope建立,生命週期即為AddScoped的生命週期。AddScope 的物件儲存在這裡。普通ServiceProvider由Root ServiceProvider建立的IServiceScope建立。
- IServiceScope。表某一個生命週期範圍。由ServiceProvider.CreateScope()建立。
- 注入方式,知識點一。
- 注入功能預設在Startup類中的ConfigureServices方法中。
- 注入方式,知識點二。支援以下三種方式
- AddScoped。生命週期為Scoped型別。例如在web框架中,即表示一次Request請求範圍內。
- AddSingleton。單例,應用程式全域性使用同一個例項。
- AddTransient。即時的,即物件每次使用都會重新例項化。
- 注入方式,知識點三。提供多種注入技巧,以Transient為例
- 例項注入。AddTransient<TService>(this IServiceCollection services)。
- 泛型注入。AddTransient<TService, TImplementation>(this IServiceCollection services)。
- 工廠注入。AddTransient<TService>(this IServiceCollection services, Func<IServiceProvider, TService> implementationFactory)。
- TryAddXXX。僅當XXX尚未註冊實現時,註冊該服務。此方法用來避免在容器中註冊一個例項的兩個副本。
- 獲取例項的方法 GetService和GetRequiredService的區別,前置如果service不存在會返回NULL,後者會直接丟擲異常。根據需要選擇GetRequiredService,可能會讓你的程式碼變得簡潔一點。
WHY 依賴注入
這裡只談益處。
- 使用介面或基類抽象化依賴關係實現,明確各個類之間的依賴關係。
- 生命週期的統一管理,尤其對於某些類被多處依賴,關係會變得分散難以管理,依賴注入容器可以解決這點。
- 非常利於單元測試。
最佳實踐
部分來自官方文件的一些建議
- 對於需要注入為單例的例項,不要依賴Scoped例項。會觸發 .NET CORE作用域驗證失敗。
- 不要從Root IServiceProvider解析有作用域的例項,這樣會導致該作用域的例項變成單一例項。同樣會觸發作用域驗證失敗。
- 對於Asp.Net Core,儘量通過建構函式而不是HttpContext.RequestServices獲取例項,這樣更易於單元測試。
- 需要對某個元件服務或是一些服務集合(包括其依賴注入時),使用約定的 Add{SERVICE_NAME} 擴充套件方法來註冊該服務所需的所有服務。
- 若必須要從IServiceProvider中解析例項(如在單元測試中),請通過using (var scope = ServiceProvider.CreateScope()){ }方式建立作用域來獲取例項。
- 程式碼中避免設計有狀態的、靜態類和成員。可以考慮設計注入為單一例項。
- 程式碼中避免在服務中直接例項化以來類。儘量採用依賴注入的方式
- 注意以下兩種方式注入的區別,後者的例項化不是服務容器建立的,所有容器不會處理例項的Dispose !!
public class Service1 : IDisposable {} public class Service2 : IDisposable {} //方式一 public void ConfigureServices(IServiceCollection services) { services.AddSingleton<Service1>(); services.AddSingleton<IService2>(sp => new Service2()); } //方式二 public void ConfigureServices(IServiceCollection services) { services.AddSingleton<Service1>(new Service1()); services.AddSingleton(new Service2()); }
- 延伸上一點,對於複雜物件的建立,儘量採用提供的工廠注入方式。注意工廠注入的引數是IServiceProvider,可以通過它獲取你需要的例項物件。
- 繼續延伸上一點,不要在ConfigureServices方法中 通過collection.BuildServiceProvider()來獲取IServiceProvider。這個建立的是一個Root IServiceProvider。單例會例項化一次,然後ConfigureServices方法結束後框架會再次呼叫collection.BuildServiceProvider(),單例又會重新例項化一次。
- 不支援基於async/await和Task的服務解析。C# 不支援非同步建構函式;因此建議模式是在同步解析服務後使用非同步方法
- 避免在容器中直接儲存資料和配置。配置應使用NET CORE的選項模型。
- 避免使用服務定位器模式。例如直接注入IServiceProvider來獲取多個需要的服務。PS,如果你的服務依賴項過多,應該考慮分割成幾個小功能服務了。
引入第三方IOC框架
.NET CORE 3.x版本後,引入第三方IOC框架的方式變更了,這裡不再貼出2.x的方式。以Autofac框架為例。
Program.cs
public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .UseServiceProviderFactory(new AutofacServiceProviderFactory()) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>() .ConfigureLogging((hostingContext, logging) => { logging.ClearProviders(); logging.AddConsole(); logging.AddNLog(); }); });
Startup.cs
//原來的 ConfigureServices保留,也可以使用原來的框架繼續注入
public void ConfigureServices(IServiceCollection services) { services.AddControllers(); services.AddMemoryCache(); services.Configure<List<string>>(Configuration.GetSection("BlackPhoneList")); services.Configure<Dictionary<string, string>>(Configuration.GetSection("BusinessMessages")); } //增加ConfigureContainer(ContainerBuilder builder) 方式,使用Autofac框架注入 public void ConfigureContainer(ContainerBuilder builder) { builder.RegisterType<PhoneBlackListValidator>().Named<IPhoneValidator>("PHONE_BLACKLIST").SingleInstance(); builder.RegisterType<PhonePerDayCountValidator>().Named<IPhoneValidator>("PHONE_PERDAYCOUNT").SingleInstance(); builder.RegisterType<UniqueIdPerDayCountValidator>().Named<IUniqueIdValidator>("UNIQUEID_PERDAYCOUNT").SingleInstance(); //可遍歷型別注入,注意 只支援IEnumerable\IList\ICollection 型別 builder.RegisterType<MessageSendValidator>().As<IMessageSendValidator>().SingleInstance(); }
3.x 主要是在IServiceCollection和IServiceProvider之間增加了一個 ContainerBuilder 容器適配,使得第三方IOC框架引入更加合理了。具體實現原理可以網上原始碼查詢。
特別關注-執行緒安全
- 建立執行緒安全的單一例項服務。 如果單例服務依賴於一個Transient服務,那麼Transient服務可能也需要執行緒安全,具體取決於單例使用它的方式。
- 工廠注入方式的Func<IServiceProvider,TService>不需要是執行緒安全的,框架保證它由單個執行緒呼叫一次。