.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 值得尝试。

相关文章
相关标签/搜索