3.1依赖注入
控制反转
传统开发中,对象都是开发者创建组装,开发者必须了解各类的使用方法且某些类的耦合度较高,例如想把sql serve数据库改为MySql数据库则需要更改某些代码。
控制反转的目的是让框架完成对象的创建和组装。从“我创建对象”编程“我要对象”,实现控制反转主要有两种方式:
服务定位器
假设框架中有个ServiceLocator类,可以直接调用GetService方法便可以获得想要的对象。
IDbConnection con = ServiceLocator.GetService<IDbConnection>();
依赖注入
一个对象中需要包含其他类型的对象,则在创建该对象的时候,框架会自动创建所需类型的对象。
容器:负责提供对象的注册和获取功能的框架
服务:注册到容器中的对象
依赖注入的基本使用
声明周期:获取服务的时候是创建一个新对象还是用之前对象
- 瞬态(transient):每次请求都创建一个新对象。
- 范围(scoped):在给定范围内,多次请求共享一个对象;在不同范围内,服务每次被请求的时候返回不同对象。ASP.NET Core中,同一次http请求,不同的注入会获得同一对象。
- 单例(singleton):全局共享一个服务对象。
使用建议:
如果一个类无状态,建议设置为单例;否则,框架环境有范围控制,则周期设置为范围;使用瞬态周期时要尽可能在自范围中使用,否则容易造成内存泄漏。
不同服务之间具有依赖关系,A服务有一个B服务的属性,那么B的声明周期不能比A短
依赖注入框架中注册服务的时候,可以设定服务类型和实现类型,这两者可以不相同。例:
- 服务和实现类型都是SqlConnection时,在获取SqlConnection服务时,会返回SqlConnection对象
- 服务是IDbConnection接口类型,实现类型都是SqlConnection时,在获取IDbConnection接口服务时,会返回SqlConnection对象
服务定位器案例:
usingMicrosoft.Extensions.DependencyInjectiony;
publicinterfaceITestService
{
publicstringName { get; set; }
publicvoidSayHi();
}
publicclassTestServiceImpl : ITestService
{
publicstringName { get; set; }
publicvoidSayHi()
{
Console.WriteLine($"Hi, I'm {Name}");
}
}
//获取服务之前要先注册服务
ServiceCollectionservices=newServiceCollection();//1.创建用于注册服务的容器
//AddTransient、AddScoped、和AddSingleton分别注册瞬态、范围、单例服务
services.AddTransient<TestServiceImpl>();//2.注册一个瞬时服务,注册的服务必须涵盖程序中所有的所需服务
//ServiceProvider服务定位器
using (ServiceProvidersp=services.BuildServiceProvider())//3.获取服务定位器
{
//4.通过调用GetRequiredService方法获得对象
TestServiceImpltestService=sp.GetRequiredService<TestServiceImpl>();
testService.Name="tom";
testService.SayHi();
}
依赖注入案例
接口---要注册的服务
interfaceIUserBiz{publicboolCheckLogin(stringuserName, stringpassword);}
interfaceIUserDAO{publicUser?GetByUserName(stringuserName);}
实现类
classUserBiz : IUserBiz
{
privatereadonlyIUserDAOuserDao;//所依赖的对象
publicUserBiz(IUserDAOuserDao) //构造函数中要求容器中必须注入IUserDAO服务
{
this.userDao=userDao;
}
publicboolCheckLogin(stringuserName, stringpassword)
{...
}
}
classUserDAO: IUserDAO
{
privatereadonlyIDbConnectionconn;//所依赖的对象
publicUserDAO(IDbConnectionconn)//构造函数中要求容器中必须注入IDbConnection服务
{
this.conn=conn;
}
publicUser?GetByUserName(stringuserName)
{...
}
}
组装服务
ServiceCollectionservices=newServiceCollection();//1.创建用于注册服务的容器
//2.注册的服务必须涵盖程序中所有的所需服务
//注册IDbConnection服务
services.AddScoped<IDbConnection>(sp=> {
stringconnStr="Data Source=.;Initial Catalog=DI_DB;Integrated Security=true";
varconn=newSqlConnection(connStr);
conn.Open();
returnconn;
});
services.AddScoped<IUserDAO, UserDAO>();//2.1.上面指定需要注入IUserDAO服务,但是要用UserDAO类来实现
services.AddScoped<IUserBiz, UserBiz>();
using (ServiceProvidersp=services.BuildServiceProvider())//3.获取服务定位器
{
varuserBiz=sp.GetRequiredService<IUserBiz>();
boolb=userBiz.CheckLogin("yzk", "123456");
Console.WriteLine(b);
}
依赖注入的传染性:一个对象是通过依赖注入创建的,那么这个类的构造函数中所有的参数都是依赖注入赋值。但是如果一个类是手动创建,那么构造函数中所有的参数不是依赖注入。所以一旦使用依赖注入,则应避免使用new来创建。
如果一个服务有多个实现对象,可以被参数声明为IEnumerable<T>
类型