编程博客
002、修改Nuget包位置
001、C#常用的单词
003、收藏的书签
回家准备
004、ASP.NET Core 3.0 gRPC
001、ASP.NET Core 3.0 使用gRPC
002、ASP.NET Core 3.0 gRPC 双向流
003、ASP.NET Core 3.0 gRPC 身份认证和授权
005、飞牛NAS
001、 FnOS飞牛系统实用配置1-应用远程访问(迅雷)
006、DeepSeek
001、DeepSeek 使用指南
007、并发编程
01、并发编程 - 死锁的产生、排查与解决方案
02、并发编程 - 初识线程
03、并发编程 - 线程浅试
04、并发编程 - 线程同步(一)
05、并发编程 - 线程同步(二)
06、并发编程 - 线程同步(三)之原子操作 Interlocked 简介
07、并发编程 - 线程同步(四)之原子操作 Interlocked 详解一
08、并发编程 - 线程同步(五)之原子操作 Interlocked 详解二
09、并发编程 - 线程同步(六)之锁 lock
008、启动项
03、应用--Program 中的 WebApplication
02、主机--Host
01、Program 文件的作用
04、控制反转 IOC 与依赖注入 DI
05、中间件
06、Logger 原理及配置 Log4Net
07、ElasticSearch
本文档使用 MrDoc 发布
-
+
首页
04、控制反转 IOC 与依赖注入 DI
# 控制反转 IOC 与依赖注入 DI 需要掌握 * [ ] DI 和 IOC 的含义是什么? * [ ] 掌握 NetCore 自带的依赖注入原理以及如何实现的流程以及熟悉对应的接口作用。 * [ ] 学会在你的项目中集成 AutoFac ## IOC 控制反转--思想[#](https://www.cnblogs.com/boise/p/18034650#ioc%E6%8E%A7%E5%88%B6%E5%8F%8D%E8%BD%AC--%E6%80%9D%E6%83%B3) 1. **什么是 IOC?** IOC 即控制反转,记住他是一种思想,目的是用来管理项目中对象的生命周期和依赖关系。 2. **为什么需要 IOC?** 没有使用 IOC 之前,我们一般是通过 new 来实例化一个对象。但是我们使用 IOC 之后,我们无需手动在代码块中 new 一个实例来调用方法,创建这个对象的控制权将由内部转换到外部,即 IOC 容器,由 IOC 容器来统一创建和销毁。 举一个例子:在我们的代码中,A 类需要 B 类的一个方法,那么我们可以理解为 A 类依赖于 B 类,我们一般就会直接 new 一个 B 类出来然后调用对应的方法,而采用 IOC 则代表,你不需要手动的 new 出来这个 B 类,这个 B 类的创造与销毁由容器自己决定,由它填充到 A 类中,A 类只要被动的等待 IOC 容器来注入 B 类的资源。传统的 new 对象不易于修改,耦合太紧密,而采用 IOC 来设计的话,类与类的依赖关系清晰可见,且易于修改。 ## DI 依赖注入--实现技术[#](https://www.cnblogs.com/boise/p/18034650#di%E4%BE%9D%E8%B5%96%E6%B3%A8%E5%85%A5--%E5%AE%9E%E7%8E%B0%E6%8A%80%E6%9C%AF) DI 是一种技术,它是用来实现 IOC 的,他的全称叫 Dependency Injection,即依赖注入。 依赖:依赖于 IOC 容器提供的实例而非 New 的资源、依赖于抽象而非具体的实现。 注入:由 IOC 容器来查找 A 类所需要的资源并注入到 A 类中。A 类所需要的资源不再由 A 类自己创建,而是由容器统一给予。 ## NetCore 原生依赖注入[#](https://www.cnblogs.com/boise/p/18034650#netcore%E5%8E%9F%E7%94%9F%E4%BE%9D%E8%B5%96%E6%B3%A8%E5%85%A5) 想要知道我们在 `Program.cs文件` 中使用各种系统服务,或者注入自己写的服务是如何实现的,首先就需要了解以下几个接口和类。 ### IServiceCollection 接口(WebApplicationBuilder):[#](https://www.cnblogs.com/boise/p/18034650#iservicecollection%E6%8E%A5%E5%8F%A3webapplicationbuilder) 之前我们在应用章节已经了解到 `WebApplicationBuilder类` 有一个类型为 `IServiceCollection` 的 `services属性`,用于提供注册服务接口。 `IServiceCollection接口` 包含一个私有列表字段 `_descriptors` 用于保存服务描述,继承于四个列表集合接口,用于实现对于服务描述列表的 CURD 服务,所以 `IServiceCollection接口` 本质上就是服务的集合,里面存储的是我们配置和注入的服务。 *IServiceCollection 源码* ```C# // 指定服务描述符集合的协定。 public interface IServiceCollection : ICollection<ServiceDescriptor>, IEnumerable<ServiceDescriptor>, IEnumerable, IList<ServiceDescriptor> { // 用于保存服务描述 private readonly List<ServiceDescriptor> _descriptors = new List<ServiceDescriptor>(); } ``` #### 3 个拓展方法:用来添加服务[#](https://www.cnblogs.com/boise/p/18034650#3%E4%B8%AA%E6%8B%93%E5%B1%95%E6%96%B9%E6%B3%95%E7%94%A8%E6%9D%A5%E6%B7%BB%E5%8A%A0%E6%9C%8D%E5%8A%A1) 这 3 个拓展方法的原理其实很简单,无论是单例或者瞬时,都是调用的 `IServiceCollection接口` 父类的 `Add()` 私有方法,通过传入 `ServiceLifetime枚举` 来控制服务的声明周期,简单来说就是往服务列表插入一条数据。 * `AddTransient()`:将服务注册为瞬时,在一次请求内,每使用一次服务都会创建一个新的实例; * `AddScoped()`:将服务注册为域,在一次请求内,创建一次实例,整个请求过程中共享该实例; * `AddSingleton()`:将服务注册为单例,在整个程序的生命周期内,每次请求都会使用同一个实例; *demo* ```C# // 常用的重载 // 把指定类型的单一实例服务注册到IServiceCollection builder.Services.AddSingleton(new UserServiceImpl()); // 把指定类型的单一实例服务和对应的接口类型的实现注册到IServiceCollection builder.Services.AddSingleton<IUserService, UserServiceImpl>(); ``` `Add()` 方法源码的本质是首先把创建一个新的 `ServiceDescriptor对象`,然后添加到集合中。 *IServiceCollection 接口的 Add()方法源码* ```c# private static IServiceCollection Add( IServiceCollection collection, Type serviceType, Type implementationType, ServiceLifetime lifetime) { // 创造一个新的ServiceDescriptor对象 var descriptor = new ServiceDescriptor(serviceType, implementationType, lifetime); // 调用的是collection的泛型父类的add方法 collection.Add(descriptor); return collection; } ``` #### ServiceDescriptor 类:[#](https://www.cnblogs.com/boise/p/18034650#servicedescriptor%E7%B1%BB) **作用:**用于描述注册的服务,描述服务类型与服务实例或者接口与生命周期的关系。 ##### 三个属性 *ServiceDescriptor 类源码:* ```C# public class ServiceDescriptor { // 服务的对应的接口类型 [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] public Type? ImplementationType { get; } // 服务的实例 public object? ImplementationInstance { get; } // 泛型工厂 public Func<IServiceProvider, object>? ImplementationFactory { get; } // 生命周期 public ServiceLifetime Lifetime { get; } // 服务的类型 public Type ServiceType { get; } } ``` ##### 三个构造函数 用于初始化属性 *ServiceDescriptor 构造函数源码* ```c# public class ServiceDescriptor { // 使用指定的 instance 作为 Singleton 来初始化 ServiceDescriptor 的新实例。 // 服务类型和实例 typeof(UserService),new UserService(),注入的是单例模式 // 所以我们看到的很多系统服务默认通过此构造函数来依赖注入 public ServiceDescriptor(Type serviceType, object instance); // 用指定的 ServiceDescriptor 初始化 factory 的新实例。 // 对象类型、服务工厂、生命周期 public ServiceDescriptor(Type serviceType, Func<IServiceProvider, object> factory, ServiceLifetime lifetime); // 用指定的 ServiceDescriptor 初始化 implementationType 的新实例。 // 服务类型、接口类型、生命周期 public ServiceDescriptor(Type serviceType, Type implementationType, ServiceLifetime lifetime); } ``` ##### Describe 方法 通过这个方法来生成一个 `ServiceDescripts对象`,该方法有 3 个重载 *ServiceDescripts 类的 Describe 方法源码* ```c# public static ServiceDescriptor Describe( Type serviceType, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type implementationType, ServiceLifetime lifetime ) { return new ServiceDescriptor(serviceType, implementationType, lifetime); } ``` ### IServiceProvider 接口(WebApplication):[#](https://www.cnblogs.com/boise/p/18034650#iserviceprovider%E6%8E%A5%E5%8F%A3webapplication) 接口的作用是**在程序运行的时候提供服务的实例**。 **来源:** 在 `WebApplication类` 上有一个 `IServiceProvider类型` 的的一个属性,他是由调用 `WebApplicationBuilder.build()` 方法的时候,会调用 `BuildServiceProvider()` 拓展方法来把 `WebApplicationBuilder对象` 中的 `services属性` 生成服务描述的实体赋值给它。 它内部有一个方法: `GetService()` 来获取对象实例,查看源码发现,它调用的其实是 `IServiceProviderEngine接口` 里面的方法 *IServiceProvider 接口源码* ```C# public interface IServiceProvider { private readonly IServiceProviderEngine _engine; // 摘要: 构造函数实例化ServiceProvider对象 internal ServiceProvider(IEnumerable<ServiceDescriptor> serviceDescriptors, ServiceProviderOptions options) { IServiceProviderEngineCallback callback = null; if (options.ValidateScopes) { callback = this; _callSiteValidator = new CallSiteValidator(); } // 根据ServiceProviderMode来实例化不同的对象 switch (options.Mode) { // 默认 case ServiceProviderMode.Default: if (RuntimeFeature.IsSupported("IsDynamicCodeCompiled")) { _engine = new DynamicServiceProviderEngine(serviceDescriptors, callback); } else { _engine = new RuntimeServiceProviderEngine(serviceDescriptors, callback); } break; // 动态 case ServiceProviderMode.Dynamic: _engine = new DynamicServiceProviderEngine(serviceDescriptors, callback); break; // 运行时 case ServiceProviderMode.Runtime: _engine = new RuntimeServiceProviderEngine(serviceDescriptors, callback); break; // IL case ServiceProviderMode.ILEmit: _engine = new ILEmitServiceProviderEngine(serviceDescriptors, callback); break; // 表达式 case ServiceProviderMode.Expressions: _engine = new ExpressionsServiceProviderEngine(serviceDescriptors, callback); break; default: throw new NotSupportedException(nameof(options.Mode)); } //判断是否开启编译时范围校验 if (options.ValidateOnBuild) { List<Exception> exceptions = null; foreach (var serviceDescriptor in serviceDescriptors) { try { _engine.ValidateService(serviceDescriptor); } catch (Exception e) { } } } } // 摘要: 获取指定类型的服务对象。 // 参数: serviceType:一个对象,它指定要获取的服务对象的类型。 // 返回结果:服务类型类型的服务对象。-or- null 如果没有服务对象类型服务类型。 public object? GetService(Type serviceType) { return _engine.GetService(serviceType) }; } ``` #### BuildServiceProvider()[#](https://www.cnblogs.com/boise/p/18034650#buildserviceprovider) 这个拓展方法 `IServiceCollection接口`,属于用于构建 `ServiceProvider对象` *ServiceCollectionContainerBuilderExtensions 拓展类源码* ```c# public static class ServiceCollectionContainerBuilderExtensions {// 用于创建ServiceProvider实例 public static ServiceProvider BuildServiceProvider(this IServiceCollection services, ServiceProviderOptions options) { if (services null) { throw new ArgumentNullException(nameof(services)); } if (options null) { throw new ArgumentNullException(nameof(options)); } IServiceProviderEngine engine; #if !NetCoreAPP engine = new DynamicServiceProviderEngine(services); #else if (RuntimeFeature.IsDynamicCodeCompiled) { engine = new DynamicServiceProviderEngine(services); } else { // Don't try to compile Expressions/IL if they are going to get interpreted engine = new RuntimeServiceProviderEngine(services); } #endif // 这个就是用来就是构建ServiceProvider的代码,我们可以看到它传入了服务、IServiceProviderEngine对象、以及ServiceProviderOptions对象, // 根据这几个参数ServiceProviderd的构造函数回实例化不同的_engine对象() return new ServiceProvider(services, engine, options); } } ``` #### IServiceProviderEngine 接口[#](https://www.cnblogs.com/boise/p/18034650#iserviceproviderengine%E6%8E%A5%E5%8F%A3) **服务引擎,用于 DI 容器当中服务的创建** `一个ServiceProvider = 一个ServiceProviderEngine = 一个ServiceProviderEngineScope` 因为 `IServiceProviderEngine接口` 同样继承了 `IServiceProvider接口`,所以 serviceProvider.GetService()`方法本质上就是调用的这个接口的` GetService()\` 来的创建服务。 *IServiceProviderEngine 接口源码* ```c# internal interface IServiceProviderEngine : IDisposable, IServiceProvider { IServiceScope RootScope { get; } } ``` 他的实现类 `ServiceProviderEngine` 因为继承了 `IServiceScopeFactory接口` 所以可以调用 `CreateScope()` 方法用于创建子容器,这个就是我们声明不同的服务生命周期的时候,比如 `AddScope()`,就会调用这个方法来创造一个子容器。 **他有很多实现类**,像 `DynamicServiceProviderEngine、RuntimeServiceProviderEngine` 等都是继承了这个抽象类来拥有这 2 个方法(也就是上面我们 `BuildServiceProvider()` 得到的不同对象的父类)。这些实现类有一个参数为 `IServiceCollection类型` 的构造函数。 *ServiceProviderEngine 抽象类源码* ```C# internal abstract class ServiceProviderEngine : IServiceProviderEngine, IServiceScopeFactory { // 构造函数是实例化服务的范围 protected ServiceProviderEngine(IEnumerable<ServiceDescriptor> serviceDescriptors, IServiceProviderEngineCallback callback) { Root = new ServiceProviderEngineScope(this); } // 获取服务实例 public object GetService(Type serviceType) => GetService(serviceType, Root); // 服务范围 public ServiceProviderEngineScope Root { get; } // 创造子容器 public IServiceScope CreateScope() { return new ServiceProviderEngineScope(this); } } ``` #### IServiceScope 接口[#](https://www.cnblogs.com/boise/p/18034650#iservicescope%E6%8E%A5%E5%8F%A3) 通过调用 `ServiceProviderEngine.CreateScope()` 来创建一个子容器,里面有一个 `IServiceProvider类型` 的私有属性,所以我们调用 `AddTransient() AddScoped()` 拓展方法注册的服务的时候,就会调用这个接口的一个私有属性也就是 `ServiceProvider` 的 `GetService()` 方法 #### IServiceProviderEngineScope 接口[#](https://www.cnblogs.com/boise/p/18034650#iserviceproviderenginescope%E6%8E%A5%E5%8F%A3) 看接口名字其实就知道这个接口实现了 `IServiceScope接口,IServiceProvider接口`,以及持有一个 `ServiceProviderEngine类型` 的属性 ```c# internal class ServiceProviderEngineScope : IServiceScope, IDisposable, IServiceProvider, IAsyncDisposable { // 服务范围 public ServiceProviderEngine Engine { get; } // 服务容器 public IServiceProvider ServiceProvider { get; } // 获取服务实例 public object GetService(Type serviceType); } ``` #### ServiceProviderOptions 类[#](https://www.cnblogs.com/boise/p/18034650#serviceprovideroptions%E7%B1%BB) 用于配置默认 System.IServiceProvider 的各种行为的选项实现。 *ServiceProviderOptions 类源码* ```c# // 摘要:用于配置默认 System.IServiceProvider 的各种行为的选项实现。 public class ServiceProviderOptions { public ServiceProviderOptions(); // 摘要:如果为 true,则执行检查,验证DI容器中已注册的所有服务,如果检查出异常了,则抛出异常现象 public bool ValidateOnBuild { get; set; } // 摘要:如果为 true,则执行检查,验证作用域内服务是否永远不会从根提供程序解析;否则为假。默认为 false。 public bool ValidateScopes { get; set; } // 枚举用于提供ServiceProvider的承载的具体实例 internal ServiceProviderMode Mode { get; set; } = ServiceProviderMode.Default; } ``` #### 这些接口的关系:[#](https://www.cnblogs.com/boise/p/18034650#%E8%BF%99%E4%BA%9B%E6%8E%A5%E5%8F%A3%E7%9A%84%E5%85%B3%E7%B3%BB) 一定要捋清楚这几个接口的关系,所以要多看源码!才能明白它整个构造的原理。最好自己写 demo 或者流程图来理解比较好 * [ ] IServiceCollection 和 IServiceProvider 的关系 * [ ] BuildServiceProvider()做了什么 * [ ] IServiceProvider、ServiceProviderEngine、IServiceScope、IServiceProviderEngineScope、ServiceProviderOption 的关系 `ServiceProvider对象` 是通过调用 `ServiceCollection.BuildServiceProvider()` 拓展方法构造出来的。这个方法传入 2 个参数,一个是代表服务描述集合的 `IServiceCollection`,一个是代表不同行为的 `ServiceProviderOption枚举`, 首先这个方法会根据枚举值来实例化不同的 `ServiceProviderEngine抽象类的实现类`,这个抽象类的作用是用于提供服务引擎。 这个抽象类继承了 3 个重要接口, 第一个是继承于 `IServiceProviderEngine`,这个接口持有一个类型为 `IServiceScope` 类型的 root 属性,在实例化抽象类的时候在构造函数中,会把当创造一个 `ServiceProviderEngineScope对象` 赋值,`ServiceProviderEngineScope` 有一个 `ServiceProvider类型` 的属性,用于存储顶级服务,也就是我们的单例模式的一些系统服务。 第二个是 `IServiceProvider接口`,目的是使用它的 `GetService()` 方法,所以一个 `ServiceProviderEngine对象` 其实就是一个 `ServiceProvider对象`。第三个继承了一个 `IServiceScopeFactory接口`,目的调用他的方法创造 `CreateScope()` 来创造 `IServiceScope子容器`。需要子容器的原因是,因为 IServiceScope 持有一个 `ServiceProvider类型属性`,是对 ServiceProvider 再次封装,目的是当我们请求如瞬时、作用域类型的服务,就会创造它用于保存所需服务,在需要服务的时候,从这个子容器里面拿,在请求完成后,就把整个 `IServiceScope` 释放掉,这就是能实现瞬时、作用域的原因。而 得到具体的 `ServiceProviderEngine对象` 后,就会把枚举参数、服务引擎、服务集合创造出一个 `ServiceProvider对象`。 所以创造一个 `ServiceProvider对象`,等于创造一个 `IServiceProviderEngine对象` 和一个 `IServiceScope对象`,而创建一个 `IServiceScope对象` 等于创造 `ServiceProviderEngineScope对象`,所以当我们需要单例服务的时候,则是调用 `ServiceProvider.GetService(XXX)` 方法其实是从 `ServiceProvider对象` 里面拿,如果我们调用注册为瞬时或者作用域的服务的时候,则是调用 `ServiceProvider.ServiceScoper.GetService(XXX)` 方法先创造一个 `ServiceProviderEngineScope对象`,然后把所需要的服务实例放到给子容器,此次请求内所需就从子容器拿。 ### 依赖注入的声明周期[#](https://www.cnblogs.com/boise/p/18034650#%E4%BE%9D%E8%B5%96%E6%B3%A8%E5%85%A5%E7%9A%84%E5%A3%B0%E6%98%8E%E5%91%A8%E6%9C%9F) ##### Singleton(单例模式): **原理:**每次请求都是从 `ServiceProvider属性` 中去拿实例。 **作用域:**服务在第一次请求的时候创建,之后的每次请求都沿用此次创建的服务,直到应用程序停止才释放在程序初始化的时候。一些大部分都是与请求无关的,比如系统配置的服务,注入控制器服务等,这类方法或者实例一般都是和程序声明周期保持一致,即单例模式 如: 1. 通过 `StartUp.cs` 构造函数注入的 `IHostEnvironment`、`IWebHostEnvironment`、`IConfiguration`; 2. 通过 `Startup.cs` 类中的 `Configure()` 方法注入的; 3. 自定义中间件; ##### Scoped(作用域模式): **原理:**是通过 `ServiceProvider` 的 `CreateScope()` 方法创造一个子容器,得到 `IServiceScoped对象`,然后调用这个子容器的 `ServiceProvider` 的 `GetService()` 方法来获取服务实例。 **作用域:**每次请求都会在当前容器创造一个作用域,即子容器,在这个请求期间每次获取服务都是从这个子容器里面去拿,当前子容器内唯一,请求结束后,子容器释放。 ##### Transient(瞬时模式): **原理:**即通过 `ServiceProvider.CreateScope()` 创造一个 `IServiceScope对象`,然后调用这个子容器的 `ServiceProvider.GetService()` 获取子容器作用域内的实例,每次获取注册为 Transient 类型的服务都是返回新的实例,虽然每次获取的都是新实例,但是释放的时候都是统一释放的,即当前 `IServiceScope.Dispose()` 的时候去释放。 **作用域:**服务每次请求都创建一次,服务容器不会保存它,直到请求结束才释放。 ### 在方法内手动获取服务实例[#](https://www.cnblogs.com/boise/p/18034650#%E5%9C%A8%E6%96%B9%E6%B3%95%E5%86%85%E6%89%8B%E5%8A%A8%E8%8E%B7%E5%8F%96%E6%9C%8D%E5%8A%A1%E5%AE%9E%E4%BE%8B) 1. 可以使用 `HttpContext.RequestServices()` 来从依赖容器中获取服务的实例,具体请看 HttpContext 章节。 2. 不通过 new 获取没有注册的实例,可以通过 ActivatorUtilities 这个静态类的方法,GetServiceOrCreateInstance(IServiceProvider provider, Type type)来获取 3. 没有在构造函数中注入,但是在 DI 中注入了既可以 ActivatorUtilities,也可以从 HttpContext.RequestServices.GetService 拿去。 ### 注入方式[#](https://www.cnblogs.com/boise/p/18034650#%E6%B3%A8%E5%85%A5%E6%96%B9%E5%BC%8F) * 构造函数注入:最常用,按需注入,依赖关系明确,缺点就是构造函数的方法体过去庞大 * 属性注入:很少用,测试复杂 * 方法注入:很少用,原因是依赖关系不明确 ### 简单来说的总结[#](https://www.cnblogs.com/boise/p/18034650#%E7%AE%80%E5%8D%95%E6%9D%A5%E8%AF%B4%E7%9A%84%E6%80%BB%E7%BB%93) 1. `WebApplicationBuilder` 里面有一个 `IServiceCollection services {get;}` 成员属性,这个接口有一个私有属性 list 集合用于存储 `ServiceScripts对象`。这个接口的目的是用于提供注册服务。 继承了 4 个泛型接口用于对服务集合进行 CURD 操作,如 `AddScope()、AddSingletion()` 等三个拓展方法的本质其实就是调用父类的 `Add()` 方法,通过传入 `ServiceLifeTime枚举` 的不同值,来实例化 `ServiceDescripts对象`,并添加到 list 集合中,是对父类 `Add()` 方法的再次封装。 2. `ServiceDescripts类` 就是服务描述,有 4 个重要的属性,服务类型、服务实例、服务对应接口、生命周期,三个构造函数和三个重载的 `Describe()` 方法来构建 ServiceDescripts 对象。 3. 在我们调用 `builder.Build()` 方法的时候,会调用 `IServiceCollection` 的一个拓展方法 `BuilderServiceProvider()` 方法来构造 `ServiceProvider对象` 赋值给 `WebApplication` 的 `IServiceProvider成员属性`,这是用于提供给在程序运行期间需要解析的服务。通过调用他的 `CreateScope()` 方法创造子容器,和 `GetService()` 方法来获取服务实例。 4. `BuilderServiceProvider()` 拓展方法其实就是 DI 容器当中服务的创建。通过传入 `ServiceProviderOptions对象` 和 `IServiceProviderEngine对象` 来传入 `ServiceProvider` 的构造函数中,构造函数会根据 `ServiceProviderOptions枚举` 的值,把服务描述集合实例化不同的 `IServiceProviderEngine对象` 赋值给一个私有属性\_engine,构造函数做完所有处理后得到的 `ServiceProvider对象` 就是我们的容器。`GetService()方法` 来获取服务实例在源码上体现就是调用的私有属性 engine 的 `GetService()`。 ### **一定要注意的事情**!! **原生只支持构造函数注入和 FromServices 这种形式,使用 UseServiceProviderFactory 拓展方法来应用第三方依赖注入服务。** **注册生命周期为 Singleton 的 Service 类里面的构造函数的方法即使注册为 Scoped 或者 Transient,也会是单例,所以要注意。我们只有在什么情况下设置为 Singleton,比如 helper 方法之类的,一成不变的,如果你用了内存也是 memercache 服务,那么也要谨记不要将调用了的服务注册为单例,否则,很有可能在另一个类读取缓存的数据无法读取到** ## [AutoFac 集成](https://autofac.readthedocs.io/en/latest/getting-started/index.html)[#](https://www.cnblogs.com/boise/p/18034650#autofac%E9%9B%86%E6%88%90) #### 为什么使用 AutoFac?[#](https://www.cnblogs.com/boise/p/18034650#%E4%B8%BA%E4%BB%80%E4%B9%88%E4%BD%BF%E7%94%A8autofac) 因为 NetCore 自带的依赖注入只支持构造函数注入,每有一个自定义服务就需要手动使用拓展方法来注入,虽然也可也通过反射循环批量实现。AutoFac 还额外提供方法、属性注入等多种方式。且 AutoFac 与 C#联系紧密,可以使用 lambda 表达式来注册组件,速度更加快、可以批量注入。 #### AutoFac 使用[#](https://www.cnblogs.com/boise/p/18034650#autofac%E4%BD%BF%E7%94%A8) 1. 首先安装 nuget 包 **Autofac** 和**Autofac.Extensions.DependencyInjection**; 2. 然后需要在最小托管模型中使用 UseServiceProviderFactory 方法来替换掉 NetCore 自带的容器; 3. 编写自己的注入模块类; 4. 然后调用 RegisterModule 注入; *demo* ```C# // 引入 AutofacServiceProvider 工厂 builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory()); // 注册模组 builder.Host.ConfigureContainer<ContainerBuilder>(p => { // 自定义模组 p.RegisterModule<ModuleRegister>(); }); ``` ```c# // 自定义的注入模块需要继承Autofac.Module类 public class ModuleRegister : Autofac.Module { private const string BLL_DLL_NAME = "BLL"; private const string SERVER_FUNTION_ENDNAME = "ServiceImpl"; /// <summary> /// 重写Load方法进行Autofac的初始化注入 /// </summary> /// <param name="builder"></param> protected override void Load(ContainerBuilder builder) { // 批量注入服务 // 通过加载程序集的名称注入 // 默认是构造函数注入,如果需要指定属性注入则需要手动使用PropertiesAutowired方法 builder.RegisterAssemblyTypes(GetAssembly(BLL_DLL_NAME)) .Where(p => p.Name.EndsWith(SERVER_FUNTION_ENDNAME)) .PropertiesAutowired()// 属性注入 .SingleInstance(); // 每次都是同一个对象。 builder.RegisterAssemblyTypes(GetAssembly(BLL_DLL_NAME)) .Where(p => p.Name.EndsWith(SERVER_FUNTION_ENDNAME)) .AsImplementedInterfaces()// 使用接口注入 .SingleInstance(); // 每次都是同一个对象。 // 手动注入服务 // 注入为单例 builder.RegisterType<UserIdProvider>().As<IUserIdProvider>().SingleInstance(); } /// <summary> /// 通过程序集名称加载对应程序集到内存中 /// </summary> /// <param name="assemblyName"></param> /// <returns></returns> private Assembly GetAssembly(string assemblyName) { return AssemblyLoadContext. Default. LoadFromAssemblyPath(AppContext.BaseDirectory + $"{assemblyName}.dll"); } } ``` #### AutoFac 多租户使用[#](https://www.cnblogs.com/boise/p/18034650#autofac%E5%A4%9A%E7%A7%9F%E6%88%B7%E4%BD%BF%E7%94%A8) **应用场景:** 一个系统中有 4 个租户,分别为 A 租户、B 租户、C 租户、D 租户,其中关于 ITestService 接口,原本它只有一个实现类 TestServiceImpl,4 个租户都正常使用这个实现类,直到某一天,A 租户这个接口需要特殊定制化、而 B 租户也是同样需要特殊处理,按照正常操作,我们应该继承 ITestService 分别实现 ATestServiceImpl 和 BTestServiceImpl,那么如何做到在运行过程中根据请求者的租户身份,动态获取对应的实例呢?比如如果是 A 请求那么我们就给 ATestServiceImpl,但是如果是 C 请求那么我们就要给 TestServiceImpl。 **流程** 1. 安装 nuget 包 **Autofac.AspNetCore.Multitenant(7.0)** 2. 在颁发 Token 的时候,请携带对应的代表租户信息的唯一 Code 信息。 3. 替换掉原始的依赖容器 4. 配置对应的服务 5. 解析 *demo* ```c# // AutoFac替换原始容器 builder.Services.AddAutofacMultitenantRequestServices(); // 配置多租户业务支持服务 builder.Host.UseServiceProviderFactory(new AutofacMultitenantServiceProviderFactory(MultitenantServiceRegister.ConfigureMultitenantContainer)); // AutoFac服务注入 builder.Host.ConfigureContainer<ContainerBuilder>(p => { p.RegisterModule<ServiceRegister>(); }); ``` ```c# /// <summary> /// 多租户特殊业务 /// </summary> public static class MultitenantServiceRegister { /// <summary> /// 自定义租户识别策略 /// </summary> /// <param name="container"></param> /// <returns></returns> /// <remarks>当Service需要需要特殊化的时候,在此方法内注入</remarks> public static MultitenantContainer ConfigureMultitenantContainer(IContainer container) { // 获取解析策略 MyTenantIdentificationStrategy tenantIdentifier = new(container.Resolve<IHttpContextAccessor>()); MultitenantContainer mtc = new(tenantIdentifier, container); mtc.ConfigureTenant("A", b => b.RegisterType<ATestServiceImpl>().As<ITestService>().SingleInstance().InstancePerTenant().WithAttributeFiltering()); mtc.ConfigureTenant("B", b => b.RegisterType<BTestServiceImpl>().As<ITestService>().SingleInstance().InstancePerTenant().WithAttributeFiltering()); // 注意:默认的实现需要放在最后一行,实现类不需要加任何的前缀 // 为什么这里的要写Default?原因是在根据策略注入资源的时候,如果在依赖注入容器内租户Code找不到对应的实例,就会默认拿这个实现。 // 比如A能找到,那就拿A对应的实例,但是你看我们在上方没有注入C,如果C请求过来,他在依赖注入容器里面找不到,就会拿这个。 mtc.ConfigureTenant("Default", b => b.RegisterType<TestServiceImpl>().As<ITestService>().SingleInstance().InstancePerTenant().WithAttributeFiltering()); return mtc; } } ``` ```c# /// <summary> /// 策略获取租户ID /// </summary> public class MyTenantIdentificationStrategy : ITenantIdentificationStrategy { private IHttpContextAccessor httpContextAccessor; /// <summary> /// 构造函数 /// </summary> /// <param name="httpContextAccessor"></param> public MyTenantIdentificationStrategy(IHttpContextAccessor httpContextAccessor) { this.httpContextAccessor = httpContextAccessor; } /// <summary> /// 尝试从当前上下文获取标识的租户。 /// </summary> /// <param name="SchemeName"></param> /// <returns></returns> public bool TryIdentifyTenant(out object schemeName) { SchemeName = null; try { if (httpContextAccessor.HttpContext != null) { // 从Token中获取携带的租户Code信息 string token = httpContextAccessor.HttpContext.Request.Headers[ClaimsUserConsts.HTTP_Token_Head].ToString(); var claims = TokenTool.GetClaims(token.Substring("bearer".Length).Trim()).ToList(); SchemeName = claims.FirstOrDefault(p => p.Type == ClaimsUserConsts.SCHEME_NAME).Value; } } catch (Exception) { } return SchemeName != null; } } ``` #### 拓展[#](https://www.cnblogs.com/boise/p/18034650#%E6%8B%93%E5%B1%95) 这里只介绍平常用的,更多的请看官网。 ###### **生命周期:** **如果不指定注册的服务生命周期,则默认是瞬时。** * `.SingleInstance()`:把服务注册为单例; * `.InstancePerLifetimeScope()`:注册为作用域; * `.InstancePerDependency()`:注册为瞬时; ###### 常用注册方式: ***builder 是指 ContainerBuilder,继承 Autofac.Module 重写 Load 方法的参数。*** * `builder.RegisterAssemblyTypes(“类库名称.dll”)`:通过程序集名称来注册,可以使用如 .PublicOnly()只注册公共方法; * `builder.RegisterType<Service>().As<IService>()`::通过接口注册,若要通过接口启用代理,组件必须仅通过接口提供服务,即构造函数中的参数以及接受的私有属性均为接口; * `builder.RegisterType<typeof(Service)>().As<typeof(IService)>()`:或者类型和接口注册; * `builder.RegisterInstance(new Service())`:通过实例注册; ###### 高级注册方式 * [多态注入](https://autofac-.readthedocs.io/en/latest/advanced/keyed-services.html):NetCore 虽然也可以实现一个接口多个服务注入,但是默认构造函数注入的是最后一个注册服务的实例,所以需要手动从 ServiceProvider 中获取,而不能动态解析获取。 AutoFac 使用 Keyed 枚举、Named 字符串来区分(不太推荐使用字符串,不好管理),这会导致将容器用作服务定位器,这是不鼓励的。需要对使用的类注册服务 WithAttributeFiltering()。 ```scss // 枚举服务,如果需要获取指定的服务则使用[KeyFilter("名称" | 枚举)] builder.RegisterType<AServiceImpl>().Keyed<IService>(VersionType.Test); builder.RegisterType<BServiceImpl>().Named<IService>("B"); // 因为在Test类中使用了多态注入,所以使用KeyFilterAttribute特性来区分到底是哪个实例,而且特地告诉autofac这个类使用了这个特性,以便容器知道要查找它 builder.RegisterType<ServiceCatProxyImpl>().WithAttributeFiltering() ``` **注意:** **如果你是想要在 Controller 层直接使用 KeyFilter 去判断获取具体的服务,是不可以的,因为 controller 的创建不是由 ioc 容器完成的,所以没办法这样做,可以新增一个代理类/装饰类来做这样的处理哦!** **如果你想用这个方法来做不同租户不同业务处理,不太推荐使用这种方式获取服务实例,Auto 的租户处理程序会更好。** ```csharp // 把这个Test作为代理类提供给控制层使用 public class ServiceCatProxyImpl { private readonly IService _aService; private readonly IService _bService; public ServiceCatProxyImpl( [KeyFilter(VersionType.Test)] IService AService, [KeyFilter("B")] IService BService ) { _aService = AService; _bService = BService; } public string ASay() => _aService.Say(); public string BSay() => _bService.Say(); } ``` ###### 特殊判断和处理: `.OnlyIf(判断条件)`:增加注册的时候的条件,如果满足条件才允许注册; `.IfNotRegistered(typeof(IService))`:如果么有人注册,则默认使用提供的接口注册; ###### 动态 AOP: 看官网怎么写[类型拦截器 — Autofac 4.0 文档 (autofac-.readthedocs.io)](https://autofac-.readthedocs.io/en/latest/advanced/interceptors.html) ###### 多租户应用程序: [多租户应用程序 — Autofac 4.0 文档 (autofac-.readthedocs.io)](https://autofac-.readthedocs.io/en/latest/advanced/multitenant.html) 可以用来实现不同的租户同一个业务实现不同的逻辑。也可也使用多态注入来实现这个效果咯,但是效果可能没有这个好,因为如果有很多租户的话,key 就要写很多。 ###### 注意: * AutoFac 对于循环依赖支持比较差,所以尽量不要在构造函数中出现循环依赖的问题,如果一定有或者存在,则自行看[官网关于循环依赖的章节](https://autofac-.readthedocs.io/en/latest/advanced/circular-dependencies.html)
个人天使
2025年2月10日 13:57
转发文档
收藏文档
上一篇
下一篇
手机扫码
复制链接
手机扫一扫转发分享
复制链接
Markdown文件
PDF文档(打印)
分享
链接
类型
密码
更新密码