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 提供了一个名为 FromServicesAttribute
的属性来帮助解决必须在构造函数中注入依赖的问题。咱们能够为 Action 的参数增长此属性,将所需的依赖注入进来:ui
public class ShoppingCartController : BasePublicController { public IActionResult Notify([FromServices] INotificationService notificationService) { notificationService.Notify("..."); } }
这固然解决了构造函数爆炸的问题,很好。但同时,该方案也让方法的参数变得复杂也为单元测试留下了障碍,依旧不够完美。
在依赖注入容器中包含的全部对象均可以经过 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() {