阅读本文你的收获:
- 了解面试中常被问到的IOC和DI的理论知识
- 了解ASP.NET Core中内置的依赖注入机制
- 学会使用第三方Autofac进行批量依赖注入
一、控制反转(IOC)与依赖注入(DI)
控制反转(Inversion of Control,缩写为IoC),IOC不是一种技术,而是一种设计思想。它是一个重要的面向对象编程的法则,它能指导我们如何设计出松耦合、更优良的程序。
为什么要控制反转?(IOC)
传统应用程序都是由我们在类内部主动创建依赖对象,从而导致类与类之间高耦合,难于测试;
有了IoC容器后,把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是松散耦合,这样也方便测试,利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活。
用图例对比一下:
没有使用IOC思想的时候,都是主动去创建相关的对象,然后再组合起来,如下图所示:
当有了IoC/DI的容器后,在客户端类中不再主动去创建这些对象了,如下图:
如何实现控制反转(IOC)
实现IOC的最常见的方式叫做依赖注入(Dependency Injection,简称DI),还有一种方式叫“依赖查找”(Dependency Lookup)。
DI—Dependency Injection,即“依赖注入”:组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。
理解DI的关键是:“谁依赖谁,为什么需要依赖,谁注入谁,注入了什么”,那我们来深入分析一下:
●谁依赖于谁:当然是应用程序依赖于IoC容器;
●为什么需要依赖:应用程序需要IoC容器来提供对象需要的外部资源;
●谁注入谁:很明显是IoC容器注入应用程序某个对象,应用程序依赖的对象;
●注入了什么:就是注入某个对象所需要的外部资源(包括对象、资源、常量数据)。
二、ASP.NET Core中内置的依赖注入机制
ASP.NET Core 支持依赖关系注入 (DI) 软件设计模式,这是一种在类及其依赖关系之间实现控制反转IOC的技术。
什么是依赖?
当一个类需要另一个类协作来完成工作的时候就产生了依赖,这里有一个设计原则:依赖于抽象,而不是具体的实现。
如上图:AccountController就有一个ILoginService的依赖
什么是注入?
把依赖的创建丢给其它人,自己只负责使用,其它人丢给你依赖的这个过程理解为注入。
//如果没有 使用依赖注入,则需要自己new实例化所依赖的对象
private ILoginService<ApplicationUser> _loginService;
public AccountController()
{
_loginService = new EFLoginService();
}
//如果使用依赖注入,通过构造函数让外界把这两个依赖传给你
private ILoginService<ApplicationUser> _loginService;
public AccountController(ILoginService<ApplicationUser> loginService)
{
_loginService = loginService;
}
.NET Core中的DI有三个核心方法
在.NET Core中DI的核心分为两个组件:IServiceCollection和 IServiceProvider。
-
IServiceCollection 负责注册
-
IServiceProvider 负责提供实例
IServiceCollection 接口有三个扩展方法(在Microsoft.Extensions.DependencyInjection命名空间下),分别用于三种生命周期的实例注册:
-
AddSingleton<, >(), 单例模式,整个应用程序之内只有一个实例(单例)
-
AddTransient<, >(), 瞬态模式,一次性的,需要了实例化,用完了丢弃;
-
AddScoped<, >(), 域模式,在用户请求的范围之内,只产生一个实例。不用的请求又不同的实例。
.NET Core中DI注册的服务有三种生命周期
-
Transient: 瞬时模式、每一次GetService都会创建一个新的实例
-
Scoped: 域模式、在同一个请求域内只初始化一个实例 ,可以理解为( 每一个request级别只创建一个实例,同一个http request会在一个 scope内)
-
Singleton :单例模式、整个应用程序生命周期以内只创建一个实例
高频问题:这三种服务周期的区别
权重:AddSingleton→AddTransient→AddScoped
AddSingleton的生命周期:项目启动-项目关闭 相当于静态类 只会有一个
AddScoped的生命周期:请求开始-请求结束 在这次请求中获取的对象都是同一个
AddTransient的生命周期:请求获取-(GC回收-主动释放) 每一次获取的对象都不是同一个
如何完成依赖注入
1. 首先编写相关的接口和实现类,并在Startup.cs的ConfigureService方法中注册服务
//在Startup.cs中,完成服务的注册
services.AddTransient<IOrderService, OrderService>(); //注入订单Service
services.AddTransient<IGoodsService, GoodsService>(); //注入商品Service
services.AddScoped<IOrderRepository, OrderRepository>(); //注入订单的仓储服务
2. 实现依赖注入的方式有三种
- 接口 + 构造函数 (强制依赖注入)
public class GoodsService : IGoodsService
{
//代码略
}
//控制器中
[Route("api/[controller]/[action]")]
[ApiController]
public class GoodsController : ControllerBase
{
//定义一个接口字段
private readonly IGoodsService _goodsService;
//构造函数注入该接口对象
public GoodsController(IGoodsService goodsService)
{
this._goodsService = goodsService;
}
}
-
属性 + 构造函数注入(可选依赖注入)
//Startup.cs
public class Startup
//构造函数注入该属性
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
//定义一个属性
public IConfiguration Configuration { get; }
}
- 方法注入
首先,Startup类的Configure方法采用了方法注入的方式,Configure方法的参数可以是任何已经注册的服务类型。
public void Configure(IApplicationBuilder app,
IWebHostEnvironment env /*其他已注册类型的接口*/)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json",
"XfTech.ApiServer v1"));
}
//静态文件中间件
app.UseStaticFiles();
...
}
在方法中,用[FromServices] 获取对应的服务,如:
//商品信息
[HttpGet]
public IActionResult Get(int id, [FromServices] IGoodsService _goodsService)
{
var result = _goodsService.GetById(id);
return Ok(result);
}
三、ASP.NET Core中使用第三方Autofac依赖注入框架
.NET Core 自带的DI容器已经很强大了,适合中小型项目,大项目如果不嫌一个一个注册服务麻烦的话,也可以用。如果想要批量注入依赖或一些特殊的情况下,我们需要用到第三方的IOC容器,其中Autofac是最常用的。
Autofac 是一个轻量级的依赖注入(DI)框架,它可以帮助 .NET 开发人员更好地管理对象的生命周期和依赖关系。Autofac 可以大大简化应用程序中对象之间的耦合,使代码更加可维护和可测试。
以下是Autofac的简单示例:
在.NET5中使用Autofac
//下载并引用nuget包
using Autofac.Extensions.DependencyInjection;
//在Program.cs中,增加 UseServiceProviderFactory(new AutofacServiceProviderFactory())
public static IHostBuilder CreateHostBuilder(string[] args)
{
return Host.CreateDefaultBuilder(args)
.UseServiceProviderFactory(new AutofacServiceProviderFactory()) //使用Autofac第三方依赖注入容器
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
})
.UseNLog(); //使用Nlog日志服务
}
//-------------------------------
//在StartUp.cs中增加如下方法
//配置IOC容器
public void ConfigureContainer(ContainerBuilder builder)
{
#region //1简单的使用--- 与默认DI容器相同的功能
//生命周期:默认,也就是瞬时模式==AddTransient
// services.AddScoped<IStudent, Student>(); --这个是微软自带的写法
//builder.RegisterType<OrderService>().As<IOrderService>();
生命周期:默认,也就是瞬时模式==AddTransient
//builder.RegisterType<Student>().As<IStudent>().InstancePerDependency();
生命周期:Scope模式==AddScoped
//builder.RegisterType<Student>().As<IStudent>().InstancePerLifetimeScope();
生命周期:单例,AddSingleton
//builder.RegisterType<Student>().As<IStudent>().SingleInstance();
//AutoFac批量注册OrderService所在程序集中所有Service类
builder.RegisterAssemblyTypes(typeof(OrderService).Assembly)
.Where(t => t.Name.EndsWith("Service"))
.AsImplementedInterfaces()
.InstancePerDependency(); //瞬态模式
//AutoFac批量注册XfTech.IOT9Test.EFCore"中的所有Repository仓储类
builder.RegisterAssemblyTypes(Assembly.Load("XfTech.EFCore"))
.Where(t => t.Name.EndsWith("Repository"))
.AsImplementedInterfaces()
.InstancePerLifetimeScope(); //域模式
//AutoFac批量注册泛型仓储
builder.RegisterGeneric(typeof(EfBaseRepository<>))
.AsImplementedInterfaces()
.InstancePerLifetimeScope();
#endregion
}
在.NET6中使用Autofac
//下载并引用nuget包
using Autofac.Extensions.DependencyInjection;
//在Program.cs中,追加以下代码
builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory()); //注册Autofac容器,不注册就用不了
//配置容器
builder.Host.ConfigureContainer<ContainerBuilder>((context, builder) =>
{
//注册 你所指定的程序集中的所有(非泛型的)类型
builder.RegisterAssemblyTypes(Assembly.Load("XXX.Application"))
.Where(a => a.Name.EndsWith("Service")) //过滤条件,只注册Service结尾的类型
.AsImplementedInterfaces() //接口注入
.InstancePerLifetimeScope(); //三种生命周期 之 域模式Scope
//注入所有的仓储
builder.RegisterAssemblyTypes(Assembly.Load("XXX.EFCore"))
.Where(a => a.Name.EndsWith("Repository"))
.AsImplementedInterfaces()
.InstancePerLifetimeScope();
//注册所有的泛型类(GenericTypes)
builder.RegisterAssemblyOpenGenericTypes(Assembly.Load("XXX.EFCore"))
.AsImplementedInterfaces()
.InstancePerLifetimeScope();
});
Autofac 的架构主要分为两个部分:注册器(ContainerBuilder)和容器(IContainer)。注册器用于注册应用程序中所有需要注入的服务和组件,而容器则用于创建和管理这些组件实例
Autofac 的主要特点
轻量级:Autofac 是一个非常轻量级的框架,其核心库只有几个 DLL 文件。这意味着它可以很容易地与其他框架集成,并且对应用程序的性能没有任何影响。
灵活性:Autofac 提供了多种不同的注册方式,如 XML 配置文件、代码配置和属性注解等。开发人员可以根据自己的需求选择最适合的注册方式。
高性能:由于 Autofac 是一个轻量级框架,它的性能非常高。在实例化对象时,Autofac 可以比其他 DI 框架更快地找到并创建所需的依赖项。
生命周期管理:Autofac 提供了多种不同的生命周期管理选项,如瞬态(Transient)、作用域(Scoped)和单例(Singleton)等。这使得开发人员可以更好地控制对象的生命周期,有效地降低内存使用和提高性能。
AOP 支持:Autofac 可以轻松地与 AOP 框架集成,如 Castle DynamicProxy 等。这使得开发人员可以很容易地实现诸如事务管理、缓存和验证等方面的横切关注点。
关于Autofac与AOP框架的集成,将在另一篇博文中进行示例讲解。
参考连接: