重新整理 .net core 實踐篇————依賴注入應用之援軍[四]

不問前世發表於2021-05-29

前言

介紹第三方依賴注入框架Autofac,看看為我們解決什麼問題。

下面介紹4個點:

  1. 命名註冊

  2. 屬性註冊

  3. aop 注入

  4. 子容器命名

正文

為什麼我們需要使用第三方框架?第三方框架為我們做了什麼?第三方框架擴充套件了哪一個部分?

這裡主要介紹一下Autofac。
Autofac 主要是替換了我們ServiceProviderFactory 這個東西。

public interface IServiceProviderFactory<IContainerBuilder>

我們使用的時候這樣用:

Host.CreateDefaultBuilder(args).UseServiceProviderFactory(new AutofacServiceProviderFactory())
.ConfigureWebHostDefaults(webBuilder =>
{
	webBuilder.UseStartup<Startup>();
});

看下UseServiceProviderFactory 原始碼:

private IServiceFactoryAdapter _serviceProviderFactory = new ServiceFactoryAdapter<IServiceCollection>(new DefaultServiceProviderFactory());
public IHostBuilder UseServiceProviderFactory<TContainerBuilder>(IServiceProviderFactory<TContainerBuilder> factory)
{
	_serviceProviderFactory = new ServiceFactoryAdapter<TContainerBuilder>(factory ?? throw new ArgumentNullException(nameof(factory)));
	return this;
}

所以說是替換了我們的serviceProviderFactory。

原理篇<<從新整理1400篇>>介紹了這個東西,這裡再簡要介紹一下。

_serviceProviderFactory 通過代理模式下進行的,也就是一個適配過程,那麼我們直接看介面卡。

其實介面卡有一個小的隱藏資訊哈。比如說_serviceProviderFactory的命名上看,翻譯過來就是serviceProvider的構建工廠,也就是為我們提供serviceProvider的生產工廠。

直接看ServiceFactoryAdapter的原始碼。

internal class ServiceFactoryAdapter<TContainerBuilder> : IServiceFactoryAdapter
  {
    private IServiceProviderFactory<TContainerBuilder> _serviceProviderFactory;
    private readonly Func<HostBuilderContext> _contextResolver;
    private Func<HostBuilderContext, IServiceProviderFactory<TContainerBuilder>> _factoryResolver;

    public ServiceFactoryAdapter(
      IServiceProviderFactory<TContainerBuilder> serviceProviderFactory)
    {
      IServiceProviderFactory<TContainerBuilder> serviceProviderFactory1 = serviceProviderFactory;
      if (serviceProviderFactory1 == null)
        throw new ArgumentNullException(nameof (serviceProviderFactory));
      this._serviceProviderFactory = serviceProviderFactory1;
    }

    public ServiceFactoryAdapter(
      Func<HostBuilderContext> contextResolver,
      Func<HostBuilderContext, IServiceProviderFactory<TContainerBuilder>> factoryResolver)
    {
      Func<HostBuilderContext> func1 = contextResolver;
      if (func1 == null)
        throw new ArgumentNullException(nameof (contextResolver));
      this._contextResolver = func1;
      Func<HostBuilderContext, IServiceProviderFactory<TContainerBuilder>> func2 = factoryResolver;
      if (func2 == null)
        throw new ArgumentNullException(nameof (factoryResolver));
      this._factoryResolver = func2;
    }

    public object CreateBuilder(IServiceCollection services)
    {
      if (this._serviceProviderFactory == null)
      {
        this._serviceProviderFactory = this._factoryResolver(this._contextResolver());
        if (this._serviceProviderFactory == null)
          throw new InvalidOperationException("The resolver returned a null IServiceProviderFactory");
      }
      return (object) this._serviceProviderFactory.CreateBuilder(services);
    }

    public IServiceProvider CreateServiceProvider(object containerBuilder)
    {
      if (this._serviceProviderFactory == null)
        throw new InvalidOperationException("CreateBuilder must be called before CreateServiceProvider");
      return this._serviceProviderFactory.CreateServiceProvider((TContainerBuilder) containerBuilder);
    }
  }

看這個CreateServiceProvider方法,和猜想的一致。再來看下HostBuilder呼叫情況。

private void CreateServiceProvider()
{
	var services = new ServiceCollection();
#pragma warning disable CS0618 // Type or member is obsolete
	services.AddSingleton<IHostingEnvironment>(_hostingEnvironment);
#pragma warning restore CS0618 // Type or member is obsolete
	services.AddSingleton<IHostEnvironment>(_hostingEnvironment);
	services.AddSingleton(_hostBuilderContext);
	// register configuration as factory to make it dispose with the service provider
	services.AddSingleton(_ => _appConfiguration);
#pragma warning disable CS0618 // Type or member is obsolete
	services.AddSingleton<IApplicationLifetime>(s => (IApplicationLifetime)s.GetService<IHostApplicationLifetime>());
#pragma warning restore CS0618 // Type or member is obsolete
	services.AddSingleton<IHostApplicationLifetime, ApplicationLifetime>();
	services.AddSingleton<IHostLifetime, ConsoleLifetime>();
	services.AddSingleton<IHost, Internal.Host>();
	services.AddOptions();
	services.AddLogging();

	foreach (var configureServicesAction in _configureServicesActions)
	{
		configureServicesAction(_hostBuilderContext, services);
	}

	var containerBuilder = _serviceProviderFactory.CreateBuilder(services);

	foreach (var containerAction in _configureContainerActions)
	{
		containerAction.ConfigureContainer(_hostBuilderContext, containerBuilder);
	}

	_appServices = _serviceProviderFactory.CreateServiceProvider(containerBuilder);

	if (_appServices == null)
	{
		throw new InvalidOperationException($"The IServiceProviderFactory returned a null IServiceProvider.");
	}

	// resolve configuration explicitly once to mark it as resolved within the
	// service provider, ensuring it will be properly disposed with the provider
	_ = _appServices.GetService<IConfiguration>();
}

的卻如此。完全證實了這個猜想。

那麼serviceProver 用來做什麼的呢?

   1: internal class ServiceProvider : IServiceProvider, IDisposable
   2: {
   3:     public ServiceProvider Root { get; private set; }
   4:     public ServiceTable ServiceTable { get; private set; }
   5:     public ConcurrentDictionary<Type, Func<ServiceProvider, object>> RealizedServices { get; private set; } = new ConcurrentDictionary<Type, Func<ServiceProvider, object>>();
   6:     public IList<IDisposable> TransientDisposableServices { get; private set; } = new List<IDisposable>();
   7:     public ConcurrentDictionary<IService, object> ResolvedServices { get; private set; } = new ConcurrentDictionary<IService, object>();
   8:     
   9:     public ServiceProvider(IServiceCollection services)
  10:     {
  11:         this.Root         = this;
  12:         this.ServiceTable     = new ServiceTable(services);
  13:     }
  14:  
  15:     public object GetService(Type serviceType)
  16:     {
  17:         Func<ServiceProvider, object> serviceAccessor;
  18:         if (this.RealizedServices.TryGetValue(serviceType, out serviceAccessor))
  19:         {
  20:             return serviceAccessor(this);
  21:         }
  22:  
  23:         IServiceCallSite serviceCallSite = this.GetServiceCallSite(serviceType, new HashSet<Type>());
  24:         if (null != serviceCallSite)
  25:         {
  26:             var providerExpression = Expression.Parameter(typeof(ServiceProvider), "provider");
  27:             this.RealizedServices[serviceType] = Expression.Lambda<Func<ServiceProvider, object>>(serviceCallSite.Build(providerExpression), providerExpression).Compile();
  28:             return serviceCallSite.Invoke(this);
  29:         }
  30:  
  31:         this.RealizedServices[serviceType] = _ => null;
  32:         return null;
  33:     }
  34:  
  35:     public IServiceCallSite GetServiceCallSite(Type serviceType, ISet<Type> callSiteChain)
  36:     {
  37:             try
  38:             {
  39:                 if (callSiteChain.Contains(serviceType))
  40:                 {
  41:                     throw new InvalidOperationException(string.Format("A circular dependency was detected for the service of type '{0}'", serviceType.FullName);
  42:                 }
  43:                 callSiteChain.Add(serviceType);
  44:  
  45:                 ServiceEntry serviceEntry;
  46:                 if (this.ServiceTable.ServieEntries.TryGetValue(serviceType, 
  47:                     out serviceEntry))
  48:                 {
  49:                     return serviceEntry.Last.CreateCallSite(this, callSiteChain);
  50:                 }
  51:  
  52:                 //省略其他程式碼
  53:  
  54:                 return null;
  55:             }
  56:             finally
  57:             {
  58:                 callSiteChain.Remove(serviceType);
  59:             }
  60:     }    
  61:  
  62:     public void Dispose()
  63:     {
  64:         Array.ForEach(this.TransientDisposableServices.ToArray(), _ => _.Dispose());
  65:         Array.ForEach(this.ResolvedServices.Values.ToArray(), _ => (_ as IDisposable)?.Dispose());
  66:         this.TransientDisposableServices.Clear();
  67:         this.ResolvedServices.Clear();
  68:     }
  69:     //其他成員
  70: }

看到GetService 是否特別的眼熟?這個就是通過我們的註冊資訊來產生不同的物件的。

那麼Autofac的作用就是替換了我們的ServiceProvider。也就是替換了,往容器注入的方式了。

那麼他給我們擴充套件了什麼功能,後面就不介紹具體原始碼了,應為是實踐篇,主要解釋用法。

命名註冊

為什麼有命名註冊的方式呢?有什麼痛點呢?

比如說:

services.AddSingleton<IMySingletonService>(new MySingletonService());
services.AddSingleton<IMySingletonService>(ServiceProvider =>
{
	return new MySingletonService();
});

那麼可以肯定一點的是,這時候通過IMySingletonService 可以獲取第一個,也可以獲取全部。

但是假如我要獲取指定的一個呢?那麼要給他們加上標記,但是.net core 自帶的並沒有並沒有。

這時候使用autofac。

在startUp 中加入:

public void ConfigureContainer(ContainerBuilder builder)
{
	builder.RegisterType<TestService>().As<ITestService>();
	builder.RegisterType<TestService>().Named<ITestService>("service2");
}

然後在startUp 的Configure 中加入:

this.AutofacContainer = app.ApplicationServices.GetAutofacRoot();

var testServiceNameDefault = this.AutofacContainer.Resolve<ITestService>();
Console.WriteLine(testServiceNameDefault.GetHashCode());

var testServiceName2= this.AutofacContainer.ResolveNamed<ITestService>("service2");
Console.WriteLine(testServiceName2.GetHashCode());

發現他們的hashcode並不一致,故而獲取了不同物件。

屬性註冊

把這個TestService 修改一下:

public interface ITestService
{
       public void ShowAttributeState();
}

public class TestService:ITestService,IDisposable
{
	public AttributeService Attribute { get; set; }

	public void ShowAttributeState()
	{
		Console.WriteLine($"attribute is null?{(Attribute == null ? "true":"false")}");
	}

	public void Dispose()
	{
		Console.WriteLine($"DisposableTestService Disposed:{this.GetHashCode()}");
	}
}

AttributeService 如下:

public class AttributeService
{
}

AttributeService 的註冊資訊:
然後新增:

services.AddTransient<AttributeService>();
builder.RegisterType<TestService>().As<ITestService>();

注:這裡我特意用services 來註冊AttributeService 是為了證明autofac 相容了.net core 原生的註冊資訊,證明前面的替換serviceProvider 的推導過程,這樣我們就可以在我們的老專案中直接使用。

獲取資訊:

this.AutofacContainer = app.ApplicationServices.GetAutofacRoot();

var testServiceNameDefault = this.AutofacContainer.Resolve<ITestService>();
testServiceNameDefault.ShowAttributeState();

TestService的註冊資訊修改一下:

builder.RegisterType<TestService>().As<ITestService>().PropertiesAutowired();

設定自動注入屬性,這樣就可以了。如果物件屬性有註冊資訊的話,會幫我們自動填充。

aop 注入

aop 如果沒怎麼看的話,可以簡單理解可以理解為攔截器。

一般我們看到屬性的方式來寫加入攔截器:

[Attribuite]
public void method(){
}

這樣是顯式注入攔截器,那麼autofac 為我們隱式注入。

安裝一下Castle.core。然後我們寫一個攔截器。

public class TestInterceptor : IInterceptor
{
	public void Intercept(IInvocation invocation)
	{
		Console.WriteLine($"Invocation before Method:{invocation.Method.Name}");
		invocation.Proceed();
		Console.WriteLine($"Invocation after Method:{invocation.Method.Name}");
	}
}

然後我們註冊的時候注入攔截器。

安裝一下:Autofac.Extras.DynamicProxy

寫入註冊資訊:

builder.RegisterType<TestInterceptor>();
builder.RegisterType<TestService>().As<ITestService>().PropertiesAutowired().InterceptedBy(typeof(TestInterceptor)).EnableInterfaceInterceptors();

然後呼叫:

this.AutofacContainer = app.ApplicationServices.GetAutofacRoot();

var testServiceNameDefault = this.AutofacContainer.Resolve<ITestService>();
testServiceNameDefault.ShowAttributeState();

看下結果:

給容器命名

builder.RegisterType<TestService>().As<ITestService>().InstancePerMatchingLifetimeScope("selfScope");

然後呼叫:

this.AutofacContainer = app.ApplicationServices.GetAutofacRoot();
using (var myScope = AutofacContainer.BeginLifetimeScope())
{ 
	var obj = myScope.Resolve<ITestService>();
	Console.WriteLine(obj==null?"true":"false");
}

這裡會報錯myScope.Resolve();,因為獲取不到,指定了指定容器selfScope。

要這樣寫:

using (var myScope = AutofacContainer.BeginLifetimeScope("selfScope"))
{ 
	var obj = myScope.Resolve<ITestService>();
	Console.WriteLine(obj==null?"true":"false");
}

這麼寫好像是不能體現出這個容器命名有什麼作用。

畫一張概念圖:

上述是原先.net core 的一個隔離思路。

如果給容器命名的話,相當於每個scope可以繼續套娃,起一個隔離作用。

程式碼演示:

this.AutofacContainer = app.ApplicationServices.GetAutofacRoot();
using (var myScope = AutofacContainer.BeginLifetimeScope("selfScope"))
{ 
	var obj = myScope.Resolve<ITestService>();
	using (var myChildScope = myScope.BeginLifetimeScope())
	{
		var obj1 = myChildScope.Resolve<ITestService>();
		Console.WriteLine(obj.GetHashCode());
		Console.WriteLine(obj1.GetHashCode());
	}
}

這種隔離機制做專案的時候就能體現出來,因為可能有幾個服務共同用到了某個類,這樣解決管理困難問題。

上述只是個人整理,如有問題,望請指出,謝謝。

下一節:配置之盟約。

相關文章