首页 > 其他分享 >控制反转IOC与依赖注入DI

控制反转IOC与依赖注入DI

时间:2024-02-26 16:47:23浏览次数:28  
标签:服务 DI 反转 接口 实例 ServiceProvider new IOC public

控制反转IOC与依赖注入DI

需要掌握

IOC控制反转--思想

  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依赖注入--实现技术

DI是一种技术,它是用来实现IOC的,他的全称叫Dependency Injection,即依赖注入。

依赖:依赖于IOC容器提供的实例而非New的资源、依赖于抽象而非具体的实现。

注入:由IOC容器来查找A类所需要的资源并注入到A类中。A类所需要的资源不再由A类自己创建,而是由容器统一给予。

NetCore原生依赖注入

想要知道我们在Program.cs文件中使用各种系统服务,或者注入自己写的服务是如何实现的,首先就需要了解以下几个接口和类。

IServiceCollection接口(WebApplicationBuilder):

之前我们在应用章节已经了解到WebApplicationBuilder类有一个类型为IServiceCollectionservices属性,用于提供注册服务接口。

IServiceCollection接口包含一个私有列表字段_descriptors用于保存服务描述,继承于四个列表集合接口,用于实现对于服务描述列表的CURD服务,所以IServiceCollection接口本质上就是服务的集合,里面存储的是我们配置和注入的服务。

IServiceCollection源码

// 指定服务描述符集合的协定。
public interface IServiceCollection : ICollection<ServiceDescriptor>, IEnumerable<ServiceDescriptor>, IEnumerable, IList<ServiceDescriptor>
{
    // 用于保存服务描述
    private readonly List<ServiceDescriptor> _descriptors = new List<ServiceDescriptor>();  
    
}

3个拓展方法:用来添加服务

这3个拓展方法的原理其实很简单,无论是单例或者瞬时,都是调用的IServiceCollection接口父类的Add()私有方法,通过传入ServiceLifetime枚举来控制服务的声明周期,简单来说就是往服务列表插入一条数据。

  • AddTransient():将服务注册为瞬时,在一次请求内,每使用一次服务都会创建一个新的实例;
  • AddScoped():将服务注册为域,在一次请求内,创建一次实例,整个请求过程中共享该实例;
  • AddSingleton():将服务注册为单例,在整个程序的生命周期内,每次请求都会使用同一个实例;

demo

// 常用的重载
// 把指定类型的单一实例服务注册到IServiceCollection
builder.Services.AddSingleton(new UserServiceImpl());
// 把指定类型的单一实例服务和对应的接口类型的实现注册到IServiceCollection
builder.Services.AddSingleton<IUserService, UserServiceImpl>();

Add()方法源码的本质是首先把创建一个新的ServiceDescriptor对象,然后添加到集合中。

IServiceCollection接口的Add()方法源码

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类:

作用:用于描述注册的服务,描述服务类型与服务实例或者接口与生命周期的关系。

三个属性

ServiceDescriptor类源码:

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构造函数源码

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方法源码

public static ServiceDescriptor Describe(            
    Type serviceType,            
    [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type implementationType,           
    ServiceLifetime lifetime
)        
{            
    return new ServiceDescriptor(serviceType, implementationType, lifetime);        
}

IServiceProvider接口(WebApplication):

接口的作用是在程序运行的时候提供服务的实例

来源:

WebApplication类上有一个IServiceProvider类型的的一个属性,他是由调用WebApplicationBuilder.build()方法的时候,会调用BuildServiceProvider()拓展方法来把WebApplicationBuilder对象中的services属性生成服务描述的实体赋值给它。

它内部有一个方法:

GetService()来获取对象实例,查看源码发现,它调用的其实是IServiceProviderEngine接口里面的方法

IServiceProvider接口源码

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()

这个拓展方法IServiceCollection接口,属于用于构建ServiceProvider对象

ServiceCollectionContainerBuilderExtensions拓展类源码

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接口

服务引擎,用于DI容器当中服务的创建

一个ServiceProvider = 一个ServiceProviderEngine = 一个ServiceProviderEngineScope

因为IServiceProviderEngine接口同样继承了IServiceProvider接口,所以serviceProvider.GetService()方法本质上就是调用的这个接口的GetService()`来的创建服务。

IServiceProviderEngine接口源码

internal interface IServiceProviderEngine : IDisposable, IServiceProvider
 {
 	  IServiceScope RootScope { get; }
 }

他的实现类ServiceProviderEngine因为继承了IServiceScopeFactory接口所以可以调用CreateScope()方法用于创建子容器,这个就是我们声明不同的服务生命周期的时候,比如AddScope(),就会调用这个方法来创造一个子容器。

他有很多实现类,像DynamicServiceProviderEngine、RuntimeServiceProviderEngine等都是继承了这个抽象类来拥有这2个方法(也就是上面我们BuildServiceProvider()得到的不同对象的父类)。这些实现类有一个参数为IServiceCollection类型的构造函数。

ServiceProviderEngine抽象类源码

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接口

通过调用ServiceProviderEngine.CreateScope()来创建一个子容器,里面有一个IServiceProvider类型的私有属性,所以我们调用AddTransient() AddScoped() 拓展方法注册的服务的时候,就会调用这个接口的一个私有属性也就是ServiceProviderGetService()方法

IServiceProviderEngineScope接口

看接口名字其实就知道这个接口实现了IServiceScope接口,IServiceProvider接口,以及持有一个ServiceProviderEngine类型的属性

internal class ServiceProviderEngineScope : IServiceScope, IDisposable,  IServiceProvider, IAsyncDisposable
{
    // 服务范围
    public ServiceProviderEngine Engine { get; }
    // 服务容器
    public IServiceProvider ServiceProvider { get; }
    // 获取服务实例
    public object GetService(Type serviceType);
}

ServiceProviderOptions类

用于配置默认 System.IServiceProvider 的各种行为的选项实现。

ServiceProviderOptions类源码


    // 摘要:用于配置默认 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;
        
    }

这些接口的关系:

一定要捋清楚这几个接口的关系,所以要多看源码!才能明白它整个构造的原理。最好自己写demo或者流程图来理解比较好

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对象,然后把所需要的服务实例放到给子容器,此次请求内所需就从子容器拿。

依赖注入的声明周期

Singleton(单例模式):

原理:每次请求都是从ServiceProvider属性中去拿实例。

作用域:服务在第一次请求的时候创建,之后的每次请求都沿用此次创建的服务,直到应用程序停止才释放在程序初始化的时候。一些大部分都是与请求无关的,比如系统配置的服务,注入控制器服务等,这类方法或者实例一般都是和程序声明周期保持一致,即单例模式 如:

  1. 通过StartUp.cs构造函数注入的IHostEnvironmentIWebHostEnvironmentIConfiguration
  2. 通过Startup.cs类中的Configure()方法注入的;
  3. 自定义中间件;
Scoped(作用域模式):

原理:是通过ServiceProviderCreateScope()方法创造一个子容器,得到IServiceScoped对象,然后调用这个子容器的ServiceProviderGetService()方法来获取服务实例。

作用域:每次请求都会在当前容器创造一个作用域,即子容器,在这个请求期间每次获取服务都是从这个子容器里面去拿,当前子容器内唯一,请求结束后,子容器释放。

Transient(瞬时模式):

原理:即通过ServiceProvider.CreateScope()创造一个IServiceScope对象,然后调用这个子容器的ServiceProvider.GetService()获取子容器作用域内的实例,每次获取注册为Transient类型的服务都是返回新的实例,虽然每次获取的都是新实例,但是释放的时候都是统一释放的,即当前IServiceScope.Dispose()的时候去释放。

作用域:服务每次请求都创建一次,服务容器不会保存它,直到请求结束才释放。

在方法内手动获取服务实例

可以使用HttpContext.RequestServices() 来从依赖容器中获取服务的实例,具体请看HttpContext章节。

注入方式

  • 构造函数注入:

  • 属性注入:

  • 方法注入:

简单来说的总结

  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对象赋值给WebApplicationIServiceProvider成员属性,这是用于提供给在程序运行期间需要解析的服务。通过调用他的CreateScope()方法创造子容器,和GetService()方法来获取服务实例。

  4. BuilderServiceProvider()拓展方法其实就是DI容器当中服务的创建。通过传入ServiceProviderOptions对象IServiceProviderEngine对象来传入ServiceProvider的构造函数中,构造函数会根据ServiceProviderOptions枚举的值,把服务描述集合实例化不同的IServiceProviderEngine对象赋值给一个私有属性_engine,构造函数做完所有处理后得到的ServiceProvider对象就是我们的容器。GetService()方法来获取服务实例在源码上体现就是调用的私有属性engine的 GetService()

一定要注意的事情!!!

原生只支持构造函数注入,使用UseServiceProviderFactory拓展方法来应用第三方依赖注入服务。

注册生命周期为Singleton的Service类里面的构造函数的方法即使注册为Scoped或者Transient,也会是单例,所以要注意。我们只有在什么情况下设置为Singleton,比如helper方法之类的,一成不变的,如果你用了内存也是memercache服务,那么也要谨记不要将调用了的服务注册为单例,否则,很有可能在另一个类读取缓存的数据无法读取到

AutoFac集成

为什么使用AutoFac?

因为NetCore自带的依赖注入只支持构造函数注入,每有一个自定义服务就需要手动使用拓展方法来注入,虽然也可也通过反射循环批量实现。AutoFac还额外提供方法、属性注入等多种方式。且AutoFac与C#联系紧密,可以使用lambda表达式来注册组件,速度更加快、可以批量注入。

AutoFac使用

  1. 首先安装nuget包AutofacAutofac.Extensions.DependencyInjection
  2. 然后需要在最小托管模型中使用UseServiceProviderFactory方法来替换掉NetCore自带的容器;
  3. 编写自己的注入模块类;
  4. 然后调用RegisterModule注入;

demo

// 引入 AutofacServiceProvider 工厂
builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());

// 注册模组
builder.Host.ConfigureContainer<ContainerBuilder>(p =>
{
    // 自定义模组
    p.RegisterModule<ModuleRegister>();
});
// 自定义的注入模块需要继承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多租户使用

应用场景:

一个系统中有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

// AutoFac替换原始容器
builder.Services.AddAutofacMultitenantRequestServices();

// 配置多租户业务支持服务
builder.Host.UseServiceProviderFactory(new AutofacMultitenantServiceProviderFactory(MultitenantServiceRegister.ConfigureMultitenantContainer));

// AutoFac服务注入
builder.Host.ConfigureContainer<ContainerBuilder>(p =>
{    
    p.RegisterModule<ServiceRegister>();
});
    /// <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;
        }
    }
    /// <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;
        }
    }

拓展

这里只介绍平常用的,更多的请看官网。

生命周期:

如果不指定注册的服务生命周期,则默认是瞬时。

  • .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()):通过实例注册;
高级注册方式
  • 多态注入:NetCore虽然也可以实现一个接口多个服务注入,但是默认构造函数注入的是最后一个注册服务的实例,所以需要手动从ServiceProvider中获取,而不能动态解析获取。

    AutoFac使用Keyed枚举、Named字符串来区分(不太推荐使用字符串,不好管理),这会导致将容器用作服务定位器,这是不鼓励的。需要对使用的类注册服务WithAttributeFiltering()。

    // 枚举服务,如果需要获取指定的服务则使用[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的租户处理程序会更好。

    // 把这个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)

多租户应用程序:

多租户应用程序 — Autofac 4.0 文档 (autofac-.readthedocs.io)

可以用来实现不同的租户同一个业务实现不同的逻辑。也可也使用多态注入来实现这个效果咯,但是效果可能没有这个好,因为如果有很多租户的话,key就要写很多。

注意:
  • AutoFac对于循环依赖支持比较差,所以尽量不要在构造函数中出现循环依赖的问题,如果一定有或者存在,则自行看官网关于循环依赖的章节

标签:服务,DI,反转,接口,实例,ServiceProvider,new,IOC,public
From: https://www.cnblogs.com/boise/p/18034650

相关文章

  • redis自学(5)QuickList
    问题1:ZipList虽然节省内存,但申请内存必须是连续空间,如果内存占用较多,申请内存效率很低。怎么办?为了缓解这个问题,我们必须限制ZipList的长度和entry大小。问题2:但是我们要存储大量数据,超出了ZipList最佳的上限怎么办?我们可以创建多个ZipList来分片存储数据。问题3:数据拆分后比......
  • redis常见的五种类型
    https://www.cnblogs.com/xkqwy/p/16353029.html 总结1.string类型写命令通过set关键字实现,set[key][value]读命令通过get关键字实现,get[key]2.list列表类型通过rpush、lpush,将一个或多个值向右或向左推入。rpush[key][value1][value2],将value值推入到列表的右端......
  • Redis扩展数据类型&命令
    StreamRedisStream是Redis5.0版本引入的一种新的数据类型,它是一个持久化的、可查询的、可扩展的消息队列服务。Stream类型的数据结构类似于一个日志系统,数据被添加到Stream的末尾,并且每个数据都会被分配一个唯一的序列号,这个序列号是按照时间顺序递增的。主体队列:Stre......
  • 在Docker中设置Redis的密码
    目录1,介绍2,实现“DockerRedis设置密码”的整体流程3,具体实现步骤4,结论1,介绍Docker是一个开源的应用容器引擎,可以自动化部署、扩展应用程序。它可以帮助开发人员将应用程序及其依赖项打包到一个可移植的容器中,然后在任何环境中运行。Redis是一个开源的内存数据结构存储系统,它可以......
  • 在MFC MDI项目中创建子窗口并显示
    该项目是基于MFCMDI向导创建的项目,建议创建MDI项目的类向导中,在最后选择生成的类中,view类基于CFormView,要在MDI项目中创建自定义的子窗口并显示在矿建窗口中使用以下步骤:1、使用类向导创建基于MFC的类,基类选择CFormView(这个有类似对话框的功能),当然也可以使用CView。2、创建完成......
  • idea 报错 Directory '/Users/codes/other/tool-box/tool-box' does not contain a Gr
    idea报错Directory'/Users/codes/other/tool-box/tool-box'doesnotcontainaGradlebuild. Gradlebuild时提示IDEAThespecifiedprojectxxxxdirectorydoesnotexist.前提因为我之前想自己开发一个将sql文件格式化的插件,中途放弃直接用程序写完了,插件开发时用的......
  • redis-深入分析redis之listpack,取代ziplist?
    ziplist的不足主要在于当ziplist中元素个数过多,它的查找效率就会降低。而且如果在ziplist里新增或修改数据,ziplist占用的内存空间还需要重新分配;更糟糕的是,ziplist新增某个元素或修改某个元素时,可能会导致后续元素的prevlen占用空间都发生变化,从而引起连锁更新问题,导致......
  • MMFN论文阅读笔记(Multi-modal Fake News Detection on Social Media via Multi-graine
    论文标题:Multi-modalFakeNewsDetectiononSocialMediaviaMulti-grainedInformationFusion论文作者:YangmingZhou,YuzhouYang,QichaoYing,ZhenxingQian,XinpengZhang论文来源:ICMR2023,paper论文代码:暂无介绍目前的多模态方法主要集中在文本和视觉特征的融......
  • [超实用插件]在Visual Studio中查看EF Core查询计划
    前言EFCore是我们.NET开发中比较常用的一款ORM框架,今天我们分享一款可以直接在VisualStudio中查看EFCore查询计划调试器可视化工具(帮助开发者分析和优化数据库查询性能):EFCore.Visualizer。值得推荐的.NETORM框架对于还不知道怎么选择.NETORM框架的同学可以看下面这两篇文......
  • Educational Codeforces Round 162 (Rated for Div. 2)
    目录写在前面ABCDE写在最后写在前面比赛地址:https://codeforces.com/contest/1923。为唐氏儿的寒假带来了一个构式的结局,飞舞一个。天使骚骚不太行啊妈的,推了三条线了感觉剧情太白开水了,咖啡馆也是这个熊样子、、、A签到。显然最优的策略是不断地选择最右侧的1进行操作,每......