ASP.NETCore中的依赖注⼊(4):构造函数的选择与服务⽣命周期管理ServiceProvider最终提供的服务实例都是根据对应的ServiceDescriptor创建的,对于⼀个具体的ServiceDescriptor对象来说,如果它的ImplementationInstance和ImplementationFactory属性均为Null,那么ServiceProvider最终会利⽤其ImplementationType属性返回的真实类型选择⼀个适合的构造函数来创建最终的服务实例。我们知道服务服务的真实类型可以定义了多个构造函数,那么ServiceProvider针对构造函数的选择会采⽤怎样的策略呢?
writeline函数⽬录
⼀、构造函数的选择
⼆、⽣命周期管理
ServiceScope与ServiceScopeFactory
三种⽣命周期管理模式
服务实例的回收
⼀、构造函数的选择
如果ServiceProvider试图通过调⽤构造函数的⽅式来创建服务实例,传⼊构造函数的所有参数必须先被初始化,最终被选择出来的构造函数必须具备⼀个基本的条件:ServiceProvider能够提供构造函数的所有参数。为了让读者朋友能够更加真切地理解ServiceProvider在构造函数选择过程中采⽤的策略,我们不让也采⽤实例演⽰的⽅式来进⾏讲解。
我们在⼀个控制台应⽤中定义了四个服务接⼝(IFoo、IBar、IBaz和IGux)以及实现它们的四个服务类(Foo、Bar、Baz和Gux)。如下⾯的代码⽚段所⽰,我们为Gux定义了三个构造函数,参数均为我们定义了服务接⼝类型。为了确定ServiceProvider最终选择哪个构造函数来创建⽬标服务实例,我们在构造函数执⾏时在控制台上输出相应的指⽰性⽂字。
1:public interface IFoo {}
2:public interface IBar {}
3:public interface IBaz {}
4:public interface IGux {}
5:
6:public class Foo : IFoo {}
7:public class Bar : IBar {}
8:public class Baz : IBaz {}
9:public class Gux : IGux
10: {
11:public Gux(IFoo foo)
12: {
13: Console.WriteLine("Gux(IFoo)");
14: }
15:
16:public Gux(IFoo foo, IBar bar)
17: {
18: Console.WriteLine("Gux(IFoo, IBar)");
19: }
20:
21:public Gux(IFoo foo, IBar bar, IBaz baz)
22: {
23: Console.WriteLine("Gux(IFoo, IBar, IBaz)");
24: }
25: }
我们在作为程序⼊⼝的Main⽅法中创建⼀个ServiceCollection对象并在其中添加针对IFoo、IBar以及IGux这三个服务接⼝的服务注册,针对服务接⼝IBaz的注册并未被添加。我们利⽤由它创建的ServiceProvider来提供针对服务接⼝IGux的实例,究竟能否得到⼀个Gux对象呢?如果可以,它⼜是通过执⾏哪个构造函数创建的呢?
1:class Program
2: {
3:static void Main(string[] args)
4: {
5:new ServiceCollection()
6: .AddTransient<IFoo, Foo>()
7: .AddTransient<IBar, Bar>()
8: .AddTransient<IGux, Gux>()
9: .BuildServiceProvider()
10: .GetServices<IGux>();
11: }
12: }
对于定义在Gux中的三个构造函数来说,ServiceProvider所在的ServiceCollection包含针对接⼝IFoo和IBar的服务注册,所以它能够提供前⾯两个构造函数的所有参数。由于第三个构造函数具有⼀个类型为IBaz的参数,这⽆法通过ServiceProvider来提供。根据我们上⾯介绍的第⼀个原则(ServiceProvider能够提供构造函数的所有参
数),Gux的前两个构造函数会成为合法的候选构造函数,那么ServiceProvider最终会选择哪⼀个呢?
在所有合法的候选构造函数列表中,最终被选择出来的构造函数具有这么⼀个特征:每⼀个候选构造函数的参数类型集合都是这个构造函数参数类型集合的⼦集。如果这样的构造函数并不存在,⼀个类型为InvalidOperationException的异常会被跑出来。根据这个原则,Gux的第⼆个构造函数的参数类型包括IFoo和IBar,⽽第⼀个构造函数仅仅具有⼀个类型为IFoo的参数,最终被选择出来的会是Gux的第⼆个构造函数,所有运⾏我们的实例程序将会在控制台上产⽣如下的输出结果。
1: Gux(IFoo, IBar)
接下来我们对实例程序略加改动。如下⾯的代码⽚段所⽰,我们只为Gux定义两个构造函数,它们都
具有两个参数,参数类型分别为IFoo&IBar和IBar&IBaz。在Main⽅法中,我们将针对IBaz/Baz的服务注册添加到创建的ServiceCollection上。
1:class Program
2: {
3:static void Main(string[] args)
4: {
5:new ServiceCollection()
6: .AddTransient<IFoo, Foo>()
7: .AddTransient<IBar, Bar>()
8: .AddTransient<IBaz, Baz>()
9: .AddTransient<IGux, Gux>()
10: .BuildServiceProvider()
11: .GetServices<IGux>();
12: }
13: }
14:
15:public class Gux : IGux
16: {
17:public Gux(IFoo foo, IBar bar) {}
18:public Gux(IBar bar, IBaz baz) {}
19: }
对于Gux的两个构造函数,虽然它们的参数均能够由ServiceProvider来提供,但是并没有⼀个构造函数的参数类型集合能够成为所有有效构造函数参数类型集合的超集,所以ServiceProvider⽆法选择出⼀个最佳的构造函数。如果我们运⾏这个程序,⼀个InvalidOperationException异常会被抛出来,控制
台上将呈现出如下所⽰的错误消息。
1: Unhandled Exception: System.InvalidOperationException: Unable to activate type 'Gux'. The following constructors are ambigious:
2: Void .ctor(IFoo, IBar)
3: Void .ctor(IBar, IBaz)
4: ...
⼆、⽣命周期管理
⽣命周期管理决定了ServiceProvider采⽤怎样的⽅式创建和回收服务实例。ServiceProvider具有三种基本的⽣命周期管理模式,分别对应着枚举类型ServiceLifetime的三个选项(Singleton、Scoped和Transient)。对于ServiceProvider⽀持的这三种⽣命周期管理模式,Singleton和Transient的语义很明确,前者(Singleton)表⽰以“单例”的⽅式管理服务实例的⽣命周期,意味着ServiceProvider对象多次针对同⼀个服务类型所提供的服务实例实际上是同⼀个对象;⽽后者(Transient)则完全相反,对于每次服务提供请求,ServiceProvider总会创建⼀个新的对象。那么Scoped⼜体现了ServiceProvider针对服务实例怎样的⽣命周期管理⽅式呢?
ServiceScope与ServiceScopeFactory
ServiceScope为某个ServiceProvider对象圈定了⼀个“作⽤域”,枚举类型ServiceLifetime中的Scoped选项指的就是这么⼀个ServiceScope。在依赖注⼊的应⽤编程接⼝中,ServiceScope通过⼀个名为IServiceScope的接⼝来表⽰。如下⾯的代码⽚段所⽰,继承⾃IDisposable接⼝的IServiceScope具有⼀个唯⼀的只读属性ServiceProvider 返回确定这个服务范围边界的ServiceProvider。表⽰ServiceScope由它对应的⼯⼚ServiceScopeFactory来创建,后者体现为具有如下定义的接⼝IServiceScopeFactory。 1:public interface IServiceScope : IDisposable
2: {
3: IServiceProvider ServiceProvider { get; }
4: }
5:
6:public interface IServiceScopeFactory
7: {
8: IServiceScope CreateScope();
9: }
若要充分理解ServiceScope和ServiceProvider之间的关系,我们需要简单了解⼀下ServiceProvider的层级结构。除了直接通过⼀个ServiceCollection对象创建⼀个独⽴的ServiceProvider对象之外,⼀个ServiceProvider还可以根据另⼀个ServiceProvider对象来创建,如果采⽤后⼀种创建⽅式,我们指定的ServiceProvider与创建的ServiceProvider将成为⼀种“⽗⼦”关系。
1:internal class ServiceProvider : IServiceProvider, IDisposable
2: {
3:private readonly ServiceProvider _root;
4:internal ServiceProvider(ServiceProvider parent)
5: {
6: _root = parent._root;
7: }
8://其他成员
9: }
虽然在ServiceProvider在创建过程中体现了ServiceProvider之间存在着⼀种树形化的层级结构,但是ServiceProvider对象本⾝并没有⼀个指向“⽗亲”的引⽤,它仅仅会保留针对根节点的引⽤。如上⾯的代码⽚段所⽰,针对根节点的引⽤体现为ServiceProvider类的字段_root。当我们根据作为“⽗亲”的ServiceProvider创建⼀个新的ServiceProvider的时候,⽗⼦均指向同⼀个“根”。我们可以将创建过程中体现的层级化关系称为“逻辑关系”,⽽将ServiceProvider对象⾃⾝的引⽤关系称为“物理关系”,右图清楚地揭⽰了这两种关系之间的转化。
由于ServiceProvider⾃⾝是⼀个内部类型,我们不能采⽤调⽤构造函数的⽅式根据⼀个作为“⽗亲”的ServiceProvider创建另⼀个作为“⼉⼦”的ServiceProvider,但是这个⽬的可以间接地通过创建ServiceScope的⽅式来完成。如下⾯的代码⽚段所⽰,我们⾸先创建⼀个独⽴的ServiceProvider并调⽤其GetService<T>⽅法获得⼀个ServiceScopeFactory对象,然后调⽤后者的CreateScope⽅法创建⼀个新的ServiceScope,它的ServiceProvider就是前者的“⼉⼦”。
1:class Program
2: {
3:static void Main(string[] args)
4: {
5: IServiceProvider serviceProvider1 = new ServiceCollection().BuildServiceProvider();
6: IServiceProvider serviceProvider2 = serviceProvider1.GetService<IServiceScopeFactory>().CreateScope().ServiceProvider;
7:
8:object root = serviceProvider2.GetType().GetField("_root", BindingFlags.Instance| BindingFlags.NonPublic).GetValue(serviceProvider2);
9: Debug.Assert(object.ReferenceEquals(serviceProvider1, root));
10: }
11: }
如果读者朋友们希望进⼀步了解ServiceScope的创建以及它和ServiceProvider之间的关系,我们不妨先来看看作为IServiceScope接⼝默认实现的内部类型ServiceScope的定义。如下⾯的代码⽚段所⽰,ServiceScope仅仅是对⼀个ServiceProvider对象的简单封装⽽已。值得⼀提的是,当ServiceScope的Dispose⽅法被调⽤的时候,这个被封装的ServiceProvider的同名⽅法同时被执⾏。
1: {
2:private readonly ServiceProvider _scopedProvider;
3:public ServiceScope(ServiceProvider scopedProvider)
4: {
5:this._scopedProvider = scopedProvider;
6: }
7:
8:public void Dispose()
9: {
10: _scopedProvider.Dispose();
11: }
12:
13:public IServiceProvider ServiceProvider
14: {
15: get {return _scopedProvider; }
16: }
17: }
IServiceScopeFactory接⼝的默认实现类型是⼀个名为ServiceScopeFactory的内部类型。如下⾯的代码⽚段所⽰,ServiceScopeFactory的只读字段“_provider”表⽰当前的ServiceProvider。当CreateScope⽅法被调⽤的时候,这个ServiceProvider的“⼦ServiceProvider”被创建出来,并被封装成
返回的ServiceScope对象。
1:internal class ServiceScopeFactory : IServiceScopeFactory
2: {
3:private readonly ServiceProvider _provider;
4:public ServiceScopeFactory(ServiceProvider provider)
5: {
6: _provider = provider;
7: }
8:
9:public IServiceScope CreateScope()
10: {
11:return new ServiceScope(new ServiceProvider(_provider));
12: }
13: }
三种⽣命周期管理模式
只有在充分了解ServiceScope的创建过程以及它与ServiceProvider之间的关系之后,我们才会对ServiceProvider⽀持的三种⽣命周期管理模式(Singleton、Scope和Transient)具有深刻的认识。就服务实例的提供⽅式来说,它们之间具有如下的差异:
Singleton:ServiceProvider创建的服务实例保存在作为根节点的ServiceProvider上,所有具有同⼀根节点的所有ServiceProvider提供的服务实例均是同⼀个对象。
Scoped:ServiceProvider创建的服务实例由⾃⼰保存,所以同⼀个ServiceProvider对象提供的服务实例均是同⼀个对象。
Transient:针对每⼀次服务提供请求,ServiceProvider总是创建⼀个新的服务实例。
为了让读者朋友们对ServiceProvider⽀持的这三种不同的⽣命周期管理模式具有更加深刻的理解,我们照例来做⼀个简单的实例演⽰。我们在⼀个控制台应⽤中定义了如下三个服务接⼝(IFoo、IBar和IBaz)以及分别实现它们的三个服务类(Foo、Bar和Baz)。
1:public interface IFoo {}
2:public interface IBar {}
3:public interface IBaz {}
4:
5:public class Foo : IFoo {}
6:public class Bar : IBar {}
7:public class Baz : IBaz {}
现在我们在作为程序⼊⼝的Main⽅法中创建了⼀个ServiceCollection对象,并采⽤不同的⽣命周期管理模式完成了针对三个服务接⼝的注册(IFoo/Foo、IBar/Bar和IBaz/Baz 分别Transient、Scoped和Sin
gleton)。我们接下来针对这个ServiceCollection对象创建了⼀个ServiceProvider(root),并采⽤创建ServiceScope的⽅式创建了它的两个“⼦ServiceProvider”(child1和child2)。
1:class Program
2: {
3:static void Main(string[] args)
4: {
5: IServiceProvider root = new ServiceCollection()
6: .AddTransient<IFoo, Foo>()
7: .AddScoped<IBar, Bar>()
8: .AddSingleton<IBaz, Baz>()
9: .BuildServiceProvider();
10: IServiceProvider child1 = root.GetService<IServiceScopeFactory>().CreateScope().ServiceProvider;
11: IServiceProvider child2 = root.GetService<IServiceScopeFactory>().CreateScope().ServiceProvider;
12:
13: Console.WriteLine("ReferenceEquals(root.GetService<IFoo>(), root.GetService<IFoo>() = {0}",ReferenceEquals(root.GetService<IFoo>(), root.GetService<IFoo>()));
14: Console.WriteLine("ReferenceEquals(child1.GetService<IBar>(), child1.GetService<IBar>() = {0}",ReferenceEquals(child1.GetService<IBar>(), child1.GetService<IBar>()));
15: Console.WriteLine("ReferenceEquals(child1.GetService<IBar>(), child2.GetService<IBar>() = {0}",ReferenceEquals(child1.GetService<IBar>(), child2.GetService<IBar>()));
16: Console.WriteLine("ReferenceEquals(child1.GetService<IBaz>(), child2.GetService<IBaz>() = {0}",ReferenceEquals(child1.GetService<IBaz>(), child2.GetService<IBaz>()));
17: }
18: }
为了验证ServiceProvider针对Transient模式是否总是创建新的服务实例,我们利⽤同⼀个ServiceProvider(root)获取针对服务接⼝IFoo的实例并进⾏⽐较。为了验证ServiceProvider针对Scope模式是否仅仅在当前ServiceScope下具有“单例”的特性,我们先后⽐较了同⼀个ServiceProvider(child1)和不同ServiceProvider(child1和child2)两次针对服务接⼝IBar获取的实例。为了验证具有“同根”的所有ServiceProvider针对Singleton模式总是返回同⼀个服务实例,我们⽐较了两个不同child1和child2两次针对服务接⼝IBaz获取的服务实例。如下所⽰的输出结构印证了我们上⾯的论述。
1: ReferenceEquals(root.GetService<IFoo>(), root.GetService<IFoo>() = False
2: ReferenceEquals(child1.GetService<IBar>(), child1.GetService<IBar>() = True
3: ReferenceEquals(child1.GetService<IBar>(), child2.GetService<IBar>() = False
4: ReferenceEquals(child1.GetService<IBaz>(), child2.GetService<IBaz>() = True
服务实例的回收
ServiceProvider除了为我们提供所需的服务实例之外,对于由它提供的服务实例,它还肩负起回收之责。这⾥所说的回收与.NET⾃⾝的垃圾回收机制⽆关,仅仅针对于⾃⾝类型实现了IDisposable接⼝的服务实例,所谓的回收仅仅体现为调⽤它们的Dispose⽅法。ServiceProvider针对服务实例所采⽤的收受策略取决于服务注册时采⽤的⽣命周期管理模式,具体采⽤的服务回收策略主要体现为如下两点:
如果注册的服务采⽤Singleton模式,由某个ServiceProvider提供的服务实例的回收⼯作由作为根的ServiceProvider负责,后者的Dispose⽅法被调⽤的时候,这些服务实例的Dispose⽅法会⾃动执⾏。
如果注册的服务采⽤其他模式(Scope或者Transient),ServiceProvider⾃⾏承担由它提供的服务实例的回收⼯作,当它的Dispose⽅法被调⽤的时候,这些服务实例的Dispose⽅法会⾃动执⾏。
我们照例使⽤⼀个简单的实例来演⽰ServiceProvider针对不同⽣命周期管理模式所采⽤的服务回收策略。我们在⼀个控制台应⽤中定义了如下三个服务接⼝(IFoo、IBar和IBaz)以及三个实现它们的服务类(Foo、Bar和Baz),这些类型具有相同的基类Disposable。Disposable实现了IDisposable接⼝,我们在Dispose⽅法中输出相应的⽂字以确定对象回收的时机。
1:public interface IFoo {}
2:public interface IBar {}
3:public interface IBaz {}
4:
5:public class Foo : Disposable, IFoo {}
6:public class Bar : Disposable, IBar {}
7:public class Baz : Disposable, IBaz {}
8:
9:public class Disposable : IDisposable
10: {
11:public void Dispose()
12: {
13: Console.WriteLine("{0}.Dispose()", this.GetType());
14: }
15: }
我们在作为程序⼊⼝的Main⽅法中创建了⼀个ServiceCollection对象,并在其中采⽤不同的⽣命周期管理模式注册了三个相应的服务(IFoo/Foo、IBar/Bar和IBaz/Baz分别采⽤Transient、Scoped和Singleton模式)。我们针对这个ServiceCollection创建了⼀个ServiceProvider(root),以及它的两个“⼉⼦”(child1和child2)。在分别通过child1和child2提供了两个服务实例(child1:IFoo, child2:IBar/IBaz)之后,我们先后调⽤三个ServiceProvider(child1=>child2=>root)的Dispose⽅法。
1:class Program
2: {
3:static void Main(string[] args)
4: {
5: IServiceProvider root = new ServiceCollection()
6: .AddTransient<IFoo, Foo>()
7: .AddScoped<IBar, Bar>()
8: .AddSingleton<IBaz, Baz>()
9: .BuildServiceProvider();
10: IServiceProvider child1 = root.GetService<IServiceScopeFactory>().CreateScope().ServiceProvider;
11: IServiceProvider child2 = root.GetService<IServiceScopeFactory>().CreateScope().ServiceProvider;
12:
13: child1.GetService<IFoo>();
14: child1.GetService<IFoo>();
15: child2.GetService<IBar>();
16: child2.GetService<IBaz>();
17:
18: Console.WriteLine("child1.Dispose()");
19: ((IDisposable)child1).Dispose();
20:
21: Console.WriteLine("child2.Dispose()");
22: ((IDisposable)child2).Dispose();
23:
24: Console.WriteLine("root.Dispose()");
25: ((IDisposable)root).Dispose();
26: }
27: }
该程序运⾏之后会在控制台上产⽣如下的输出结果。从这个结果我们不难看出由child1提供的两个采⽤Transient模式的服务实例的回收实在child1的Dispose⽅法执⾏之后⾃动完成的。当child2的Dispose⽅法被调⽤的时候,对于由它提供的两个服务对象来说,只有注册时采⽤Scope模式的Bar对象被⾃动回收了,⾄于采⽤Singleton模式的Baz 对象的回收⼯作,是在root的Dispose⽅法被调⽤之后⾃动完成的。
1: child1.Dispose()
2: Foo.Dispose()
3: Foo.Dispose()
4: child2.Dispose()
5: Bar.Dispose()
6: root.Dispose()
7: Baz.Dispose()
了解ServiceProvider针对不同⽣命周期管理模式所采⽤的服务回收策略还会帮助我们正确的使⽤它。具体来说,当我们在使⽤⼀个现有的ServiceProvider的时候,由于我们并不能直接对它实施回收(因为它同时会在其它地⽅被使⽤),如果直接使⽤它来提供我们所需的服务实例,由于这些服务实例可能会在很长⼀段时间得不到回收,进⽽导致⼀些内存泄漏的问题。如果所⽤的是⼀个与当前应⽤具有相同⽣命周期(ServiceProvider在应⽤终⽌的时候才会被回收)的ServiceProvider,⽽且提供的服务采⽤Transient模式,这个问题就更加严重了,这意味着每次提供的服务实例都是⼀个全新的对象,但是它永远得不到回收。
为了解决这个问题,我想很多⼈会想到⼀种解决⽅案,那就是按照如下所⽰的⽅式显式地对提供的每个服务实例实施回收⼯作。实际上这并不是⼀种推荐的编程⽅式,因为这样的做法仅仅确保了服务实例对象的Dispose⽅法能够被及时调⽤,但是ServiceProvider依然保持着对服务实例的引⽤,后者依然不能及时地被GC回收。
1:public void DoWork(IServiceProvider serviceProvider)
2: {
3:using (IFoobar foobar = serviceProvider.GetService<IFoobar>())
4: {
5: ...
6: }
7: }
或者
1:public void DoWork(IServiceProvider serviceProvider)
2: {
3: IFoobar foobar = serviceProvider.GetService<IFoobar>();
4:try
5: {
6: ...
7: }
8:finally
9: {
10: (foobar as IDisposable)?.Dispose();
11: }
12: }
由于提供的服务实例总是被某个ServiceProvider引⽤着(直接提供服务实例的ServiceProvider或者是它的根),所以服务实例能够被GC从内存及时回收的前提是引⽤它的ServiceProvider及时地变成垃圾对象。要让提供服务实例的ServiceProvider成为垃圾对象,我们就必须创建⼀个新的ServiceProvider,通过上⾯的介绍我们知道ServiceProvider的创建可以通过创建ServiceScope的⽅式来实现。除此之外,为我们可以通过回收ServiceScope的⽅式来回收对应的ServiceProvider,进⽽进⼀步回收由它提供的服务实例(仅限Transient和Scoped模式)。下⾯的代码⽚段给出了正确的编程⽅式。
1:public void DoWork(IServiceProvider serviceProvider)
2: {
3:using (IServiceScope serviceScope = serviceProvider.GetService<IServiceScopeFactory>().CreateScope())
4: {
5: IFoobar foobar = serviceScope.ServiceProvider.GetService<IFoobar>();
6: ...
7: }
8: }
接下来我们通过⼀个简单的实例演⽰上述这两种针对服务回收的编程⽅式之间的差异。我们在⼀个控制台应⽤中定义了⼀个继承⾃IDisposable的服务接⼝IFoobar和实现它的服务类Foobar。如下⾯的代码⽚段所⽰,为了确认对象真正被GC回收的时机,我们为Foobar定义了⼀个析构函数。在该析构函数和Dispose⽅法中,我们还会在控制台上输出相应的指导性⽂字。
1:public interface IFoobar: IDisposable
2: {}
3:
4:public class Foobar : IFoobar
5: {
6: ~Foobar()
7: {
8: Console.WriteLine("Foobar.Finalize()");
9: }
10:
11:public void Dispose()
12: {
13: Console.WriteLine("Foobar.Dispose()");
14: }
15: }
在作为程序⼊⼝的Main⽅法中,我们创建了⼀个ServiceCollection对象并采⽤Transient模式将IFoobbar/Foobar注册其中。借助于通过该ServiceCollection创建的ServiceProvider,我们分别采⽤上述的两种⽅式获取服务实例并试图对它实施回收。为了强制GC试试垃圾回收,我们显式调⽤了GC的Collect⽅法。
1:class Program
2: {
3:static void Main(string[] args)
4: {
5: IServiceProvider serviceProvider = new ServiceCollection()
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论