1 DI概念
1.1 基本定义
依赖注入(Dependency Injection,DI)是控制反转(Inversion of Control,IOC)思想的实现方式
简化模块的组装过程,降低模块之间的耦合度。
1.2 理解及实现方式
代码控制反转的目的:“怎样创建xxx对象”—>”我要xxx对象”
两种实现方式:1)服务定位器(ServiceLocator) 2)依赖注入(Dependency Injection,DI),省略GetService步骤
1.3 几个关键概念
服务(service):要获得的对象
注册服务:将对象注册进去
服务容器:负责管理注册的服务
查询服务:创建对象及关联对象
对象生命周期(销毁的时间):Transient(瞬态)每次获取都获得一个新的对象 Scoped(范围) Singleton(单例)同一个对象
1.4 .NET中的DI
Interface ITest
TestInterfaceImplementA:ITest
TestInterfaceImplementB:ITest
- 根据类型来获取和注册服务:可以分别指定服务类型(service type)和实现类型(implementattion type)。服务类型尽可能的选择是接口,面向接口编程。
- .NET控制反转组件取名为DependencyInjection,但它包含ServiceLocator功能。
使用DI 2:
1.Install-Package Microsoft.Extensions.DependencyInjection2.using Microsoft.Extensions.DependencyInjection3.ServiceCollection 用来构造容器对象IServiceProvider 调用ServiceCollection的BuildServiceProvider()创建的ServiceProvider,可以用来获取BuildServiceProvider()之前ServiceCollection中的对象。
2 服务的生命周期
AddSingleton拿到的是同一个对象
Scope创建范围,创建一个CreateScope() 就是一个范围,通常using的范围就是Scope范围
当一个类没有成员变量,没有属性的类,建议声明为Singleton。如果类有状态且有Scope范围,建议为Scoped。因为通常这种Scope控制下的代码都是运行在同一线程中的,没有并发修改的问题;在使用Transient瞬态的时候要谨慎。

using System;using Microsoft.Extensions.DependencyInjection;namespace DIAndIOC{class Program{static void Main(string[] args){//创建一个容器var serviceCollection = new ServiceCollection();//将对象注入到瞬态中serviceCollection.AddTransient<TestServiceImpA>();//将对象创建成实例,服务定位器using (var sp = serviceCollection.BuildServiceProvider()){//我要容器里这个对象TestServiceImpAvar testServiceImpA = sp.GetService<TestServiceImpA>();testServiceImpA.Name = "Albert";testServiceImpA.ShowName();}//创建一个容器var serviceCollection2 = new ServiceCollection();//将对象注入到范围中,在范围内拿到的是同一个对象serviceCollection2.AddScoped<TestServiceImpA>();using (var sp = serviceCollection2.BuildServiceProvider()){using(var scope = sp.CreateScope()){var testServiceImpA = scope.ServiceProvider.GetService<TestServiceImpA>();testServiceImpA.Name = "Albert";testServiceImpA.ShowName();}}Console.ReadLine();}}interface ITestService{public string Name { get; set; }public void ShowName();}public class TestServiceImpA : ITestService{public string Name { get; set; }public void ShowName(){Console.WriteLine(this.Name);}}}
3 其他注册方法
- 服务类型和实现类型不一致的注册(尽量注册一个接口,服务类型为接口)
- 简单看看其他Add*方法:
- T GetService
() 如果找不到会返回Null object GetService(typeof(T)) - GetRequiredService
() 如果找不到直接异常 - IEnumerable
GetServices ()适用于可能有多个满足条件的服务
*T是根据注册服务类型来的,上面是ITestService,这边必须是ITestService,找不到服务返回null**
static void Main(string[] args){//new collection--addscope声明服务和对象--BuilderServicerProvider()创建服务提供者//--GetService<T>获取服务ServiceCollection serviceCollection = new ServiceCollection();//服务类型和实现类型serviceCollection.AddScoped<ITestService,TestServiceImpA>();using (var sp = serviceCollection.BuildServiceProvider()){//此处不关心你实现类是什么//GetService<T>() T是根据注册服务类型来的,上面是ITestService,这边必须是ITestService,找不到服务返回nullvar ts = sp.GetService<ITestService>();Console.WriteLine(ts.GetType());Console.ReadLine();}}
4 依赖注入DI显威力
- 依赖注入具有“传染性”,如果一个类的对象是通过DI创建,那么这个类的构造函数中声明的所有服务类型的参数都会被DI赋值;但是如果一个对象是成员手动创建的,那么这个对象就和DI没有关系,它的构造函数中声明的服务类型参数就不会被自动赋值。(服务相互依赖,多层依赖关系,自动创建其他依赖服务,根服务是自己创建的)
- .NET的DI默认是构造函数注入(Java中的Spring是属性注入)
- 例子:编写一个类,连接数据库做插入操作,并且记录日志(模拟的输出),把Dao、日志都放入单独的服务类,connstr见备注。
- 总结:依赖注入用于多个接口(服务)相互依赖的情景下,接口A依赖接口B,要在A中通过构造函数给接口B的属性(接口A)进行赋值,在使用BuilderServiceProvider之前要将所有的服务注册进去,服务和实现类一起注入其中。Controller必须是容器创建出来的,然后DI将服务传进去。 ```csharp using Microsoft.Extensions.DependencyInjection; using System;
namespace _210720_Demon02_InfectDI {
class Program {
static void Main(string[] args) {
ServiceCollection services = new ServiceCollection();
services.AddScoped
using (var sp = services.BuildServiceProvider()) {var controller = sp.GetRequiredService<Controller>();controller.Test();}Console.ReadLine();}}class Controller {private readonly ILog log;private readonly ICloud cloud;public Controller(ILog log, ICloud cloud) {this.log = log;this.cloud = cloud;}public void Test() {this.log.Log("开始上传");this.cloud.Save(".NET第一行代码", "albert.txt");this.log.Log("上传完毕");}}interface ILog {public void Log(string msg);}interface IConfig {public string GetValue(string name);}interface ICloud {public void Save(string content, string name);}class LogImp : ILog {public void Log(string msg) {Console.WriteLine($"日志:{msg}");}}class ConfigImp : IConfig {public string GetValue(string server) {return $"返回服务名为{server}";}}class CloudImp : ICloud {private readonly IConfig config;public CloudImp(IConfig config) {this.config = config;}public void Save(string content, string name) {string server = config.GetValue("albert_server");Console.WriteLine($"向服务{server}中上传的内容为{content},内容保存在{name}文件下");}}
}
5 DI综合案例1
需求说明:
1.目的:演示DI的能力
2.有配置服务、日志服务,然后再开发一个邮件发送器服务(MailK)。可以通过配置服务来从文件、环境变量、数据库等地方读取配置,可以通过日志服务来将程序运行过程中的日志信息写入文件、控制台、数据库等。
3.说明:案例中开发了自己的日志、配置等接口,这只是在揭示原理,.NET有现成的。
第一个根上对象只能用服务定位器的方式,如果需要属性赋值在构造函数中则需写成:service.AddScoped(typeof(IT),s=>new TImp(){xxx=xxx})

代码:https://gitee.com/hongyongzhao/dot-net
项目克隆:https://gitee.com/hongyongzhao/dot-net.git
6 DI综合案例2
实现一个“可覆盖的配置读取器”(集群:多台服务器—从单本地配置文件web.config从中心化配置服务器中一键修改所有的配置文件)
7 总结

