依赖注入
依赖注入
控制反转(inversion of control, IoC)是设计模式中非常重要的思想,而依赖注入(dependency iniection, DI)是控制反转思想的一种重要的实现方式。依赖注入简化了模块的组装过程,减小了模块之间的耦合性,因此.NET Core中大量应用了依赖注入的开发模式。
DI几个概念
服务(service):对象;
注册服务;
服务容器:负责管理注册的服务;
查询服务:创建对象及关联对象;
对象生命周期:Transient(瞬态);Scoped(范围);Singleton(单例);
微软把内置.NET Core的控制反转组件命名为DependencyInjection,但是它含了服务定位器的功能。把这个功能统一称为依赖注入。
1.瞬态(transient):每次被请求的时候都会创建一个新对象。这种生命周期适合有状态的对象,可以避免多段的代码用于同一个对象而造成状态混乱,其缺点是生成的对象比较多,会浪费内存。
2.范围(scoped):再给定的范围内,多次请求共享同一个服务对象,服务每次被请求的时候都会返回同一个对象;在不同的范围内,服务每次被我请求的时候对返回不同的对象。这个范围可以由框架定义,也可以由开发人员自定义。在ASP.NET Core中,服务默认的范围是第一次HTTP请求,也就是在同一次HTTP请求中,不同的注入会获得同一个对象;在不同的HTTP请求中,不同的注入会获得不同的对象。这种方式适合用于在同一个范围内共享同一个对象的情况。
3.单例(singleton):全局共享同一个服务对象。这种生命周期可以节省创建新对象的资源。为了避免并发修改等问题,单列的服务对象最好是无状态对象。
这三种生命周期中如何选择:如果一个类没有状态,建议把服务的声明周期设置为单列;如果一个类有状态,并且在框架环境中的范围控制(比如ASP.NET Core中有默认的请求相关的范围),在这种情况下建议把服务的生命周期设置为范围,因为通常在范围控制下,代码都是运行在同一个线程中的,没有并发修改的问题;在使用瞬态生命周期的时候要谨慎,尽量在子范围中使用它们,因为如果我们控制不好,容易造成程序中出现内存泄露的问题。
依赖注入框架是根据服务的类型来获取服务的,因此在获取服务的时候必须指定获取什么类型的服务。依赖注入注册服务的时候可以分别指定服务类型和实现类型,这两者可能相同,也可能不同。
比如在注册服务的时候,可以设定服务类型和实现类型都是SqlConnection,这样在获取SqlConnection类型服务的时候,容器就会返回注册的SqlConnection类型的对象;也可以在注册服务的时候,设定服务类型是IDbConnection,实现类型是SqlConnection,这样在获取IDbConnection类型服务的时候,容器就会返回注册的SqlConnection类型的对象。(在注册服务的时候只能要求注入IDbConnection)类型的服务,不能直接要求注入SqlConnection类型的服务。
在面向对象编程中,推荐使用面向接口编程,这样我们的代码就依赖于服务接口,而不是依赖于实现类,可以实现代码解耦。因此在使用依赖注入的时候,推荐服务类型用接口类型。
测试用的服务的接口
1 | public interface ITestService |
创建用于注册服务的容器。容器的接口是IServiceCollection,其默认实现类是ServiceCollection。IServiceCollecion接口中定义了AddTransient、AddScoped和AddSingleton这三组扩展方法,分别用来注册瞬态、范围和单例服务。注册完成后,我们调用IServiceCollection的BuildDerviceProvider方法创建一个ServiceProivider对象,这个ServiceProvider对象就是一个服务定位器。由于ServiceProvider对象实现了IDisposable接口,因此需要使用using对其进行资源的释放。在我们需要获取服务的时候,可以调用ServiceProvider类的GetRequiredService方法。
服务的注册及获取过程
1 | using Microsoft.Extensions.DependencyInjection; |
TestServiceImpl注册为瞬态服务,然后在第6行代码中通过GetRequiredService方法来获取TestServiceImpl对象,这种用法属于服务定位器方式。(如果一个被依赖注入容器管理的类实现了IDisposable接口,则离开作用域之后容器会自动调用对象的Dispose方法,这样就可以及时释放非托管资源)
不要再生命周期的对象中引用比它短的生命周期的对象。比如不能在单例服务中引用范围服务,否则可能会导致被引用的对象已经释放或这导致内存泄漏。在ASP.NET Core的默认依赖注入容器中,这种“在长生命周期的对象中引用短生命周期的对象”的代码会发生异常。
DI魅力渐显:依赖注入
1.依赖注入是有“传染性”的,如果一个类的对象是通过DI创建的,那么这个类的构造函数中声明的所有服务类型的参数都会被DI赋值;但是如果一个对象是程序员手动创建的,那么这个对象就和DI没有关系,它的构造函数中声明的服务类型参数就不会被自动赋值。
2..NET的DI默认是构造函数注入。
3.举例:编写一个类,连接数据库做插入操作,并且记录日志(模拟的输出),把Dao、日志都放入单独的服务类。connstr见备注。
1 | using Microsoft.Extensions.DependencyInjection; |