Autofac 是一款超贊的 .NET IoC 容器 ,在眾多性能測評中,它也是表現最優秀的一個。 它管理類之間的依賴關系, 從而使 應用在規模及復雜性增長的情況下依然可以輕易地修改。它的實現方式是將常規的.net類當做 組件 處理。
簡單的性能測試
在 LINQPad 中,我們可以很容易的構建出一個測試環境(需要引入 Microsoft.Extensions.DependencyInjection
和 Autofac.Extensions.DependencyInjection
組件):
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; }
寫一些簡單的性能進行測試代碼:
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 中對上述代碼進行一萬次、十萬次、百萬次三個量級的測試,得出以下報表(縱軸單位為“毫秒”):
從統計圖中可以看到,即便是最耗時的 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 的參數增加此屬性,將所需的依賴注入進來:
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()