前言
介紹第三方依賴注入框架Autofac,看看為我們解決什麼問題。
下面介紹4個點:
-
命名註冊
-
屬性註冊
-
aop 注入
-
子容器命名
正文
為什麼我們需要使用第三方框架?第三方框架為我們做了什麼?第三方框架擴充套件了哪一個部分?
這裡主要介紹一下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
要這樣寫:
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());
}
}
這種隔離機制做專案的時候就能體現出來,因為可能有幾個服務共同用到了某個類,這樣解決管理困難問題。
結
上述只是個人整理,如有問題,望請指出,謝謝。
下一節:配置之盟約。