[关闭]
@dasajia2lang 2019-05-30T04:32:27.000000Z 字数 16055 阅读 693

给框架加入依赖注入

Aop,.NetCore


依赖注入

依赖注入(DI)

字面意思需要什么就注入什么,而不是需要什么就创建什么。
组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。

  理解DI的关键是:“谁依赖谁,为什么需要依赖,谁注入谁,注入了什么”

  ●谁依赖于谁:应用程序依赖于IoC容器;

  ●为什么需要依赖:应用程序需要IoC容器来提供对象需要的外部资源;

  ●谁注入谁:很明显是IoC容器注入应用程序某个对象,应用程序依赖的对象;

  ●注入了什么:就是注入某个对象所需要的外部资源(包括对象、资源、常量数据),注入的三种类型:构造函数注入,属性注入,方法注入。

控制反转(IOC):

举个例子:
程序员都需要找女朋友,一般人的方式是我们到处去看哪里有长得漂亮身材又好的mm,然后打听她们的兴趣爱好、qq号、电话号、ip号、iq号………,想办法认识她们,投其所好送其所要。那么一般程序员都比较忙,加不完的班,他没有这么多时间,那他是怎么找女朋友的呢,他就是去相亲交友网站,去告诉他们他想找一个女朋友,具体的标准:身高190,体重120,等等,然后相亲交友网站就会按照程序员的标准给我们提供一个mm,然后程序就找到女朋友了。如果网站给了你个体重240的,那么不符合你的标准,那你就直接不要了。
那么这个例子中:IOC容器是相亲交友网站,程序员找女朋友之间就有了一层解耦,程序员不在直接找女朋友,而是通过IOC容器找女朋友这就是反转。DI注入:即一些mm去网站登记自己的信息,这就是注入。

依赖注入框架的介绍

专业的事情,交给专业的东西去做,依赖注入也有特定的框架。

AutoFac

Autofac 是一款适用于Microsoft .NET 4.5, Silverlight 5, Windows Store apps, and Windows Phone 8 apps的超赞的 IoC 容器 . 它可以管理类之间的依赖关系从而使 应用在规模及复杂性增长的情况下依然可以轻易地修改 . 通过将常规.net类当做 组件 处理实现 .
文档地址:https://autofaccn.readthedocs.io/zh/latest/
源码地址:https://github.com/autofac/Autofac

Spring.Net

Spring.net是一个开源应用程序框架,使构建企业.NET应用程序更加容易。
提供基于经验证的设计模式的组件,这些设计模式可以集成到应用程序体系结构的所有层中,Spring有助于提高开发效率,提高应用程序质量和性能。
官网:http://www.springframework.net/
源码地址:https://github.com/spring-projects/spring-net

.netCore自带的DI框架

DI注入的几种表现形式

  1. public class Foo : IFoo
  2. {
  3. public Foo(IBar bar)
  4. {
  5. Bar = bar;
  6. }
  7. public IBar Bar { get; private set; }
  8. }
  1. public class FooPro : IFoo
  2. {
  3. [Injection]
  4. public IBar Bar { get; set; }
  5. [Injection]
  6. public IBaz Baz { get; set; }
  7. }
  1. public class FooMethed
  2. {
  3. public IBar Bar { get; private set; }
  4. [Injection]
  5. public void Initialize(IBar bar)
  6. {
  7. this.Bar = bar;
  8. }
  9. }

Core的依赖注入--服务的注册和发现

  1. ServiceProvider与ServiceDescriptor
    IServiceProvider只有一个方法GetService().
  1. public interface IServiceProvider
  2. {
  3. object GetService(Type serviceType);
  4. }

ServiceDescriptor是注入的一个载体。所有的对于服务的注入最终都会转变成一个ServiceDescriptor注入到DI容器里面。
它的定义为:

  1. public class ServiceDescriptor
  2. {
  3. public ServiceDescriptor(Type serviceType, object instance);
  4. public ServiceDescriptor(Type serviceType, Func<IServiceProvider, object> factory, ServiceLifetime lifetime);
  5. public ServiceDescriptor(Type serviceType, Type implementationType, ServiceLifetime lifetime);
  6. //使用Type注入时,这里存储的是注入的服务的Type
  7. public Type ServiceType { get; }
  8. //注入的生命周期
  9. public ServiceLifetime Lifetime { get; }
  10. //这里存储的是注入的实例的Type(实例注入的时候这里为空)
  11. public Type ImplementationType { get; }
  12. //当实例注入时,这里存储的是注入的类的实例(使用的时候应该就会存在一个装箱拆箱的性能损失)
  13. public object ImplementationInstance { get; }
  14. //工厂注入的实例(只有通过工厂方法注入的时候才会赋值)
  15. public Func<IServiceProvider, object> ImplementationFactory { get; }
  16. }

服务的注册

服务注册的方式一般根据Lifetime分为3类。
Single(ServiceProvider创建的服务实例保存在作为根节点的ServiceProvider上,所有具有同一根节点的所有ServiceProvider提供的服务实例均是同一个对象)
Scoped(ServiceProvider创建的服务实例由自己保存,所以同一个ServiceProvider对象提供的服务实例均是同一个对象),
Transient(每获取一次服务就创建一个新的实例)
具体是调用:

  1. public static class ServiceCollectionExtensions
  2. {
  3. public static IServiceCollection AddScoped<TService>(this IServiceCollection services) where TService: class;
  4. //其他AddScoped<TService>重载
  5. public static IServiceCollection AddSingleton<TService>(this IServiceCollection services) where TService: class;
  6. //其他AddSingleton<TService>重载
  7. public static IServiceCollection AddTransient<TService>(this IServiceCollection services) where TService: class;
  8. //其他AddTransient<TService>重载
  9. }

具体讲一下工厂方法注入:

  1. public static IServiceCollection AddSingleton<TService>(this IServiceCollection services, Func<IServiceProvider, TService> implementationFactory) where TService : class;

工厂委托:Func<IServiceProvider, TService> implementationFactory.
输入一个IServiceProvider的作用我目前能想到的就是因为你在构造你的TService时候有可能要用到你之前注入到Collection里面的服务,所以你可以通过输入参数IServiceProvider去进行获取。

  1. //工厂方式注入
  2. collection.AddSingleton<IFoo>(_ =>new Foo(new Bar()));

当注入同一个服务时,后面注入的实例会把前面的给覆盖掉。如果想获取所有注册的实例请使用获取服务的集合

获取一个服务的集合

  1. public static class ServiceProviderExtensions
  2. {
  3. public static IEnumerable<T> GetServices<T>(this IServiceProvider provider);
  4. public static IEnumerable<object> GetServices(this IServiceProvider provider, Type serviceType);
  5. }

其实就是返回一个IEnumerable集合,当你注册多个服务的时候。

构造函数的选取

当通过Type进行注册的时候,如果一个接口的实例有多个构造函数,默认选取的是哪个构造函数?

原则:构造函数的所有参数必须要先被初始化,最终选择的构造函数是能通过ServiceProvider中获取到的所有的参数的构造函数。

  1. public class FooConstruction : IFoo
  2. {
  3. public FooConstruction()
  4. {
  5. Console.WriteLine("Using No Parmeter Construction!!");
  6. }
  7. public FooConstruction(IBar bar)
  8. {
  9. Console.WriteLine("Using IBar Construction!!");
  10. }
  11. public FooConstruction(IBar bar,IBaz baz)
  12. {
  13. Console.WriteLine("Using IBar,IBaz Construction!!");
  14. }
  15. public void SayFoo()
  16. {
  17. Console.WriteLine("It is FooConstruction!!");
  18. }
  19. }
  20. collection.AddSingleton(typeof(IFoo), typeof(FooConstruction));
  21. //无参构造函数
  22. var foo1 = collection.BuildServiceProvider().GetRequiredService<IFoo>();
  23. collection.AddSingleton<IBar>(new Bar());
  24. //参数为IBar的构造函数
  25. var foo2 = collection.BuildServiceProvider().GetRequiredService<IFoo>();
  26. collection.AddSingleton<IBaz>(new Baz());
  27. //参数为IBar和参数为IBaz的构造函数
  28. var foo3 = collection.BuildServiceProvider().GetRequiredService<IFoo>();

虽然IServiceProvider能够提供构造函数所有的参数,但是当没有一个构造函数的参数是其他所有构造函数的参数的超集时,则也会报错!!!

  1. /// <summary>
  2. /// 构造函数的选取
  3. /// </summary>
  4. public class FooNewConstruction : IFoo
  5. {
  6. public FooNewConstruction()
  7. {
  8. Console.WriteLine("Using No Parmeter Construction!!");
  9. }
  10. public FooNewConstruction(IBar bar)
  11. {
  12. Console.WriteLine("Using IBar Construction!!");
  13. }
  14. public FooNewConstruction(IBaz baz)
  15. {
  16. Console.WriteLine("Using IBaz Construction!!");
  17. }
  18. public void SayFoo()
  19. {
  20. Console.WriteLine("It is FooConstruction!!");
  21. }
  22. }
  23. collection.Clear();
  24. collection.AddSingleton<IBar>(new Bar()).AddSingleton<IBaz>(new Baz());
  25. collection.AddSingleton(typeof(IFoo),typeof(FooNewConstruction));
  26. //报错
  27. var foo4= collection.BuildServiceProvider().GetRequiredService<IFoo>();

虽然能够获取到IBar和IBaz,但是在调用FooNewConstruction时,不能够获得唯一的一个构造函数,因为程序不知道要调用IBar的构造函数,还是IBaz的构造函数,这就是为什么要一个超集的原因!!

服务周期(生命周期管理)

ServiceProvider具有三种基本的生命周期管理模式,分别对应着枚举类型ServiceLifetime的三个选项(Singleton、Scoped和Transient)。对于ServiceProvider支持的这三种生命周期管理模式,Singleton和Transient的语义很明确,前者(Singleton)表示以“单例”的方式管理服务实例的生命周期,意味着ServiceProvider对象多次针对同一个服务类型所提供的服务实例实际上是同一个对象;而后者(Transient)则完全相反,对于每次服务提供请求,ServiceProvider总会创建一个新的对象。那么Scoped又体现了ServiceProvider针对服务实例怎样的生命周期管理方式呢?

ServiceScope与ServiceScopeFactory
ServiceScope为每一个IServiceProvider圈定了一个作用域。换句话说:一个IServiceProvider有着一个对应的ServiceScpe。
ServiceProvider可以根据另外一个ServiceProvider进行创建。
ServiceScpe类

  1. {
  2. private readonly ServiceProvider _scopedProvider;
  3. public ServiceScope(ServiceProvider scopedProvider)
  4. {
  5. this._scopedProvider = scopedProvider;
  6. }
  7. public void Dispose()
  8. {
  9. _scopedProvider.Dispose();
  10. }
  11. public IServiceProvider ServiceProvider
  12. {
  13. get {return _scopedProvider; }
  14. }
  15. }
  16. internal class ServiceScopeFactory : IServiceScopeFactory
  17. {
  18. private readonly ServiceProvider _provider;
  19. public ServiceScopeFactory(ServiceProvider provider)
  20. {
  21. _provider = provider;
  22. }
  23. public IServiceScope CreateScope()
  24. {
  25. return new ServiceScope(new ServiceProvider(_provider));
  26. }
  27. }

就是通过Scoped注入的服务,不同的Provider获取的是不同的值。

服务的释放

如果我们在使用Transient和Scoped方式注入服务时,如果我们我们不显示的去回收的话,就有可能造成内存泄漏。
每一次提取一个service都会创建一个新的服务,但是IServiceProvider保持着对新创建的服务的引用导致服务不能够被GC回收。

服务回收的次序:
Singleton注册服务只有在 root ServiceProvider被回收时才会被回收。
Transient和Scoped方式注册的时候,在其创建的SersviceProvider被回收时,会被自动回收。

  1. public interface IFoo {}
  2. public interface IBar {}
  3. public interface IBaz {}
  4. public class Foo : Disposable, IFoo {}
  5. public class Bar : Disposable, IBar {}
  6. public class Baz : Disposable, IBaz {}
  7. public class Disposable : IDisposable
  8. {
  9. public void Dispose()
  10. {
  11. Console.WriteLine("{0}.Dispose()", this.GetType());
  12. }
  13. }
  14. class Program
  15. {
  16. static void Main(string[] args)
  17. {
  18. var collection = new ServiceCollection();
  19. collection.AddSingleton<IBar, Bar>();
  20. collection.AddSingleton<IFoo, Foo>();
  21. var rootProvider = collection.BuildServiceProvider();
  22. var childProvider = rootProvider.GetService<IServiceScopeFactory>().CreateScope().ServiceProvider;
  23. //object root = childProvider.GetType().GetField("_root", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(childProvider);
  24. var child1Provider = childProvider.GetService<IServiceScopeFactory>().CreateScope().ServiceProvider;
  25. //object root1= child1Provider.GetType().GetField("_root", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(child1Provider);
  26. //Console.WriteLine("创建的两个子Provider是否共有一个root:{0}",object.ReferenceEquals(root, root1));
  27. var rootFoo = ((Foo)rootProvider.GetService<IFoo>()).Unicode;
  28. var childFoo = ((Foo)childProvider.GetService<IFoo>()).Unicode;
  29. var child1Foo = ((Foo)child1Provider.GetService<IFoo>()).Unicode;
  30. Console.WriteLine($"rootFoo:{rootFoo},childFoo:{childFoo},child1Foo:{child1Foo}");
  31. collection.Clear();
  32. collection.AddSingleton<IBar, Bar>();
  33. collection.AddScoped<IFoo, Foo>();
  34. var rootScopedProvider = collection.BuildServiceProvider();
  35. var childScopedProvider = rootScopedProvider.GetService<IServiceScopeFactory>().CreateScope().ServiceProvider;
  36. var root1ScopedProvider = collection.BuildServiceProvider();
  37. Console.WriteLine($"rootScopedProvider:{((Foo)rootScopedProvider.GetService<IFoo>()).Unicode},childScopedProvider:{((Foo)childScopedProvider.GetService<IFoo>()).Unicode},root1ScopedProvider:{((Foo)root1ScopedProvider.GetService<IFoo>()).Unicode}"); var collection = new ServiceCollection();
  38. collection.AddSingleton<IBar, Bar>();
  39. collection.AddSingleton<IFoo, Foo>();
  40. var rootProvider = collection.BuildServiceProvider();
  41. var childProvider = rootProvider.GetService<IServiceScopeFactory>().CreateScope().ServiceProvider;
  42. //object root = childProvider.GetType().GetField("_root", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(childProvider);
  43. var child1Provider = childProvider.GetService<IServiceScopeFactory>().CreateScope().ServiceProvider;
  44. //object root1= child1Provider.GetType().GetField("_root", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(child1Provider);
  45. //Console.WriteLine("创建的两个子Provider是否共有一个root:{0}",object.ReferenceEquals(root, root1));
  46. var rootFoo = ((Foo)rootProvider.GetService<IFoo>()).Unicode;
  47. var childFoo = ((Foo)childProvider.GetService<IFoo>()).Unicode;
  48. var child1Foo = ((Foo)child1Provider.GetService<IFoo>()).Unicode;
  49. Console.WriteLine($"rootFoo:{rootFoo},childFoo:{childFoo},child1Foo:{child1Foo}");
  50. collection.Clear();
  51. collection.AddSingleton<IBar, Bar>();
  52. collection.AddScoped<IFoo, Foo>();
  53. var rootScopedProvider = collection.BuildServiceProvider();
  54. var childScopedProvider = rootScopedProvider.GetService<IServiceScopeFactory>().CreateScope().ServiceProvider;
  55. var root1ScopedProvider = collection.BuildServiceProvider();
  56. Console.WriteLine($"rootScopedProvider:{((Foo)rootScopedProvider.GetService<IFoo>()).Unicode},childScopedProvider:{((Foo)childScopedProvider.GetService<IFoo>()).Unicode},root1ScopedProvider:{((Foo)root1ScopedProvider.GetService<IFoo>()).Unicode}");
  57. }
  58. }

结果:

  1. rootFoo:c1c3a6ec-13bb-4828-954b-a2e73de1620c,childFoo:c1c3a6ec-13bb-4828-954b-a2
  2. e73de1620c,child1Foo:c1c3a6ec-13bb-4828-954b-a2e73de1620c
  3. rootScopedProvider:036ad9eb-057f-4731-83d1-4e22116e3847,childScopedProvider:e097
  4. ab38-0066-4a37-a7ac-5cdd9f6733ee,root1ScopedProvider:88fdbe64-78dd-4d25-be8b-f78
  5. ef5458d68

有可能会想到,我们每个服务都自己继承IDispose接口,然后使用的时候用using包裹着。
public void Do()
{
using(IFoo foo=serviceProvider.GetService()){}
}
或者是try finally{((IDispose)foo).Dispose() }

这些都是不推荐的编程方法,这样是没什么用的,因为他们的ServiceProvider还保持着对于服务的引用。

只有当ServiceProvider进行释放时才会正真的释放。

 class Program
 {
   static void Main(string[] args)
   {

       IServiceProvider serviceProvider = new ServiceCollection()

           .AddTransient<IFoobar, Foobar>()

           .BuildServiceProvider();

       serviceProvider.GetService<IFoobar>().Dispose();
       GC.Collect();

       Console.WriteLine("----------------");
       using (IServiceScope serviceScope = serviceProvider.GetService<IServiceScopeFactory>().CreateScope())
       {
           serviceScope.ServiceProvider.GetService<IFoobar>();
       }
       GC.Collect();

       Console.Read();
   }
}

结果:
Foobar.Dispose()
----------------
Foobar.Dispose()
Foobar.Finalize()

服务的验证

Singleton和Scoped这两种不同生命周期是通过将提供的服务实例分别存放到作为根容器的IServiceProvider对象和当前IServiceProvider对象来实现,这意味着作为根容器的IServiceProvider对象提供的Scoped服务实例也是不能被释放的。如果某个Singleton服务以来另一个Scoped服务,那么Scoped服务实例将被一个Singleton服务实例所引用,意味着Scoped服务实例也成了一个不会被释放的服务实例。

在ASP.NET Core应用中,当我们将某个服务注册的生命周期设置为Scoped的真正意图是希望DI容器根据请求上下文来创建和释放服务实例,但是一旦出现上述的情况下,意味着Scoped服务实例将变成一个Singleton服务实例,这样的Scoped服务实例直到应用关闭的哪一个才会得到释放。如果某个Scoped服务实例引用的资源(比如数据库连接)需要被及时释放,这可能会对应用造成灭顶之灾。为了避免这种情况下,我们在利用IServiceProvider提供服务过程开启针对服务范围的验证。

如果希望IServiceProvider在提供服务的过程中对服务范围作有效性检验,我们只需要在调用ServiceCollection的BuildServiceProvider方法的时候将一个布尔类型的True值作为参数即可。在如下所示的演示程序中,我们定义了两个服务接口(IFoo和IBar)和对应的实现类型(Foo和Bar),其中Foo依赖IBar。我们将IFoo和IBar分别注册为Singleton和Scoped服务,当我们在调用BuildServiceProvider方法创建代表DI容器的IServiceProvider对象的时候将参数设置为True以开启针对服务范围的检验。我们最后分别利用代表根容器和子容器的IServiceProvider来分别提供这两种类型的服务实例。

  1. #region 总结:当开启验证时,并且两个接口之间有依赖关系,根节点的provider不能拿到scope的服务,子节点不能拿到single的
  2. Console.WriteLine("================No validate===========================");
  3. new ValidateScopeService().ValidateScopeTest(false);
  4. Console.WriteLine("================Singleton IA,Scope IB Transient IC===========================");
  5. new ValidateScopeService().ValidateScopeTest(true);
  6. Console.WriteLine("================Transient IA,Scope IB Singleton IC===========================");
  7. new ValidateScopeService().ValidateScopeTest1(true);
  8. Console.WriteLine("================IFoo, IBar,IBaz no reference===========================");
  9. new ValidateScopeService().ValidateScopeTest2(true);
  10. Console.WriteLine("================Scope IA,Scope IB Singleton IC===========================");
  11. new ValidateScopeService().ValidateScopeTest3(true);
  12. //结果
  13. ================No validate===========================
  14. Status: Success; Service Type: IA; Root: Yes
  15. Status: Success; Service Type: IA; Root: No
  16. Status: Success; Service Type: IB; Root: Yes
  17. Status: Success; Service Type: IB; Root: No
  18. Status: Success; Service Type: IC; Root: Yes
  19. Status: Success; Service Type: IC; Root: No
  20. ================Singleton IA,Scope IB Transient IC===========================
  21. Status: Fail; Service Type: IA; Root: Yes
  22. Error: Cannot consume scoped service 'tc.gl.architect.progress.di.IB' from singl
  23. eton 'tc.gl.architect.progress.di.IA'.
  24. Status: Fail; Service Type: IA; Root: No
  25. Error: Cannot consume scoped service 'tc.gl.architect.progress.di.IB' from singl
  26. eton 'tc.gl.architect.progress.di.IA'.
  27. Status: Fail; Service Type: IB; Root: Yes
  28. Error: Cannot resolve scoped service 'tc.gl.architect.progress.di.IB' from root
  29. provider.
  30. Status: Success; Service Type: IB; Root: No
  31. Status: Success; Service Type: IC; Root: Yes
  32. Status: Success; Service Type: IC; Root: No
  33. ================Transient IA,Scope IB Singleton IC===========================
  34. Status: Fail; Service Type: IA; Root: Yes
  35. Error: Cannot resolve 'tc.gl.architect.progress.di.IA' from root provider becaus
  36. e it requires scoped service 'tc.gl.architect.progress.di.IB'.
  37. Status: Success; Service Type: IA; Root: No
  38. Status: Fail; Service Type: IB; Root: Yes
  39. Error: Cannot resolve scoped service 'tc.gl.architect.progress.di.IB' from root
  40. provider.
  41. Status: Success; Service Type: IB; Root: No
  42. Status: Success; Service Type: IC; Root: Yes
  43. Status: Success; Service Type: IC; Root: No
  44. ================IFoo, IBar,IBaz no reference===========================
  45. Status: Success; Service Type: IA; Root: Yes
  46. Status: Success; Service Type: IA; Root: No
  47. Status: Success; Service Type: IB; Root: Yes
  48. Status: Success; Service Type: IB; Root: No
  49. Status: Success; Service Type: IC; Root: Yes
  50. Status: Success; Service Type: IC; Root: No
  51. ================Scope IA,Scope IB Singleton IC===========================
  52. Status: Fail; Service Type: IA; Root: Yes
  53. Error: Cannot resolve scoped service 'tc.gl.architect.progress.di.IA' from root
  54. provider.
  55. Status: Success; Service Type: IA; Root: No
  56. Status: Fail; Service Type: IB; Root: Yes
  57. Error: Cannot resolve scoped service 'tc.gl.architect.progress.di.IB' from root
  58. provider.
  59. Status: Success; Service Type: IB; Root: No
  60. Status: Success; Service Type: IC; Root: Yes
  61. Status: Success; Service Type: IC; Root: No

框架加入依赖注入

在写代码的过程中,我们最好是能够满足软件架构设计的五大原则

  1. 开闭原则-OCP:对修改关闭,对拓展开放
  2. 单一原则:一个类只有一个用处
  3. 依赖倒置:不依赖实际类,依赖抽象
  4. 接口隔离
  5. 迪米特原则:一个对应应该对其他对象保持最少的了解,又叫最少知道原则。强调只和朋友交流,不和陌生人说话
    如果我们的代码能够很好的满足软件设计的原则,那么我们使用DI框架就很简单了。第三点标明我们是依赖于接口,DI推荐的是注入一个契约和一个实现类。然后获取时,满足条件5的话,我们会改较少的代码,因为尽量少的和外部通信导致我们的类就很少依赖外部的资源,自然就会少修改为DI获取实例的过程。
    最重要的是目前的core的依赖注入需要手动的一个一个去注入,我们可以自己去写代码进行对程序集的批量注入,同时对接口标注上注入需要的属性即可达到批量注入的要求。

QA

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注