.NET 中依賴注入組件 Autofac 的性能漫聊

Autofac 是一款超讚的 .NET IoC 容器 ,在衆多性能測評中,它也是表現最優秀的一個。 它管理類之間的依賴關係, 從而使 應用在規模及複雜性增加的狀況下依然能夠輕易地修改。它的實現方式是將常規的.net類當作 組件 處理。git

簡單的性能測試

在 LINQPad 中,咱們能夠很容易的構建出一個測試環境(須要引入 Microsoft.Extensions.DependencyInjection 和 Autofac.Extensions.DependencyInjection 組件):github

void Main() { var services = new ServiceCollection(); services.AddSingleton<ClassSingleton>(); services.AddTransient<ClassTransient>(); services.AddScoped<ClassScoped>(); var builder = new Autofac.ContainerBuilder(); builder.Populate(services); var provider = new AutofacServiceProvider(builder.Build()); var singleton = provider.GetService<ClassSingleton>(); singleton.Dump(); } // You can define other methods, fields, classes and namespaces here class ClassSingleton { public string Name =>this.GetType().Name; } class ClassTransient { public string Name => this.GetType().Name; } class ClassScoped { public string Name => this.GetType().Name; }

寫一些簡單的性能進行測試代碼:web

private static void TestSingleton(IServiceProvider provider, int times) { for (int i = 0; i < times; i++) { var _ = provider.GetRequiredService<ClassSingleton>(); } } private static void TestTransient(IServiceProvider provider, int times) { for (int i = 0; i < times; i++) { var _ = provider.GetRequiredService<ClassTransient>(); } } private static void TestScoped(IServiceProvider provider, int times) { using (var scope = provider.CreateScope()) { for (int i = 0; i < times; i++) { var _ = scope.ServiceProvider.GetRequiredService<ClassScoped>(); } } }

在 LINQPad 中對上述代碼進行一萬次、十萬次、百萬次三個量級的測試,得出如下報表(縱軸單位爲「毫秒」):ide

從統計圖中能夠看到,即使是最耗時的 Transient 對象,百萬級別建立的時間消耗也不到 400 毫秒。這說明,大多數狀況下 Autofac 之類的 IoC 容器不會成爲應用的性能瓶頸。函數

構造函數爆炸

當一個系統不斷完善,業務在底層會被不斷拆分爲小的 Service ,而後在頂層(應用層或表現層)組裝以完成功能。這表示在 Controller 中咱們須要注入大量的 Service 才能保證功能完備。若是咱們須要的對象經過構造函數注入,那麼就會形成該構造函數的參數多到爆炸。性能

nopCommerce 是一個 ASP.NET 開發的電子商城系統,具有商城該有的各類功能和特性。在 ShoppingCartController 中你能夠看到如下代碼:單元測試

public ShoppingCartController(CaptchaSettings captchaSettings, CustomerSettings customerSettings, ICheckoutAttributeParser checkoutAttributeParser, ICheckoutAttributeService checkoutAttributeService, ICurrencyService currencyService, ICustomerActivityService customerActivityService, ICustomerService customerService, IDiscountService discountService, IDownloadService downloadService, IGenericAttributeService genericAttributeService, IGiftCardService giftCardService, ILocalizationService localizationService, INopFileProvider fileProvider, INotificationService notificationService, IPermissionService permissionService, IPictureService pictureService, IPriceFormatter priceFormatter, IProductAttributeParser productAttributeParser, IProductAttributeService productAttributeService, IProductService productService, IShippingService shippingService, IShoppingCartModelFactory shoppingCartModelFactory, IShoppingCartService shoppingCartService, IStaticCacheManager staticCacheManager, IStoreContext storeContext, ITaxService taxService, IUrlRecordService urlRecordService, IWebHelper webHelper, IWorkContext workContext, IWorkflowMessageService workflowMessageService, MediaSettings mediaSettings, OrderSettings orderSettings, ShoppingCartSettings shoppingCartSettings) { ... }

構造函數爆炸的性能問題

即使參數再多,在感官上也只是一個強迫症的問題。但構造函數爆炸所形成的影響不單單只是看上去沒那麼舒服而已。當咱們注入一個對象時,IoC 容器會保證該對象以及其依賴的對象已經被正確初始化。因此咱們不能簡單的根據注入對象的數量來判斷性能消耗,由於頗有可能某個接口的實現依賴了數個其餘對象。學習

當咱們訪問一個網頁時,只會用到 Controller 中的某一個方法,一般,該方法不會對全部注入的對象都產生依賴。這也意味着咱們建立了大量非必要的對象,爲內存和 GC 形成了壓力。測試

在 ASP.NET Core 中解決構造函數爆炸問題

ASP.NET Core 提供了一個名爲 FromServicesAttribute 的屬性來幫助解決必須在構造函數中注入依賴的問題。咱們能夠爲 Action 的參數增長此屬性,將所需的依賴注入進來:ui

public class ShoppingCartController : BasePublicController { public IActionResult Notify([FromServices] INotificationService notificationService) { notificationService.Notify("..."); } }

這固然解決了構造函數爆炸的問題,很好。但同時,該方案也讓方法的參數變得複雜也爲單元測試留下了障礙,依舊不夠完美。

使用 IServiceProvider 解決構造函數爆炸問題

在依賴注入容器中包含的全部對象均可以經過 IServiceProvider 獲取到。基於該特性能夠實現依賴對象的按需加載功能:

void Main() { var services = new ServiceCollection(); services.AddTransient<MyController>(); services.AddTransient<MyService>(); var builder = new Autofac.ContainerBuilder(); builder.Populate(services); var provider = new AutofacServiceProvider(builder.Build()); var controller = provider.GetRequiredService<MyController>(); Console.WriteLine("NoCallMethod"); controller.NoCallMethod(); Console.WriteLine("CallMethod"); controller.CallMethod(); } // You can define other methods, fields, classes and namespaces here class MyService { public MyService() { Console.WriteLine("MyService 被建立"); } public void SayHello() { Console.WriteLine("Hello"); } } class MyController { private IServiceProvider _serviceProvider; public MyController(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; } protected T LazyGetRequiredService<T>(ref T value) { if (value != null) { return value; } return value = _serviceProvider.GetRequiredService<T>(); } private MyService _myService; public MyService MyService => LazyGetRequiredService(ref _myService); public void CallMethod() { MyService.SayHello(); } public void NoCallMethod() { } }

以上代碼在 MyService 的構造函數中輸出了建立日誌。MyController 類型中經過 LazyGetRequiredService 方法實現了 MyService 的按需加載,構造函數也只剩下一個 IServiceProvider 對象。以上代碼會產生下面的輸出:

NoCallMethod CallMethod MyService 被建立 Hello End

能夠看到,在調用不依賴 MyService 的方法 NoCallMethod 時,MyService 並無被建立。直到 CallMethod 被調用後用到了 MyService 時,它才被建立。

向 Volo.Abp 學習

Volo.Abp 在 4.2.0 版本中加入了一個新的接口: IAbpLazyServiceProvider 。

using System; namespace Volo.Abp.DependencyInjection { public interface IAbpLazyServiceProvider { T LazyGetRequiredService<T>(); object LazyGetRequiredService(Type serviceType); T LazyGetService<T>(); object LazyGetService(Type serviceType); T LazyGetService<T>(T defaultValue); object LazyGetService(Type serviceType, object defaultValue); object LazyGetService(Type serviceType, Func<IServiceProvider, object> factory); T LazyGetService<T>(Func<IServiceProvider, object> factory); } }

其實現一樣採用 IServiceProvider 建立對象,同時使用了字典來保存對實例的引用。若是你和我同樣使用 Abp 開發代碼,那麼 LazyServiceProvider 值得嘗試。

相關文章
相關標籤/搜索