3.1依赖注入

控制反转

传统开发中,对象都是开发者创建组装,开发者必须了解各类的使用方法且某些类的耦合度较高,例如想把sql serve数据库改为MySql数据库则需要更改某些代码。

控制反转的目的是让框架完成对象的创建和组装。从“我创建对象”编程“我要对象”,实现控制反转主要有两种方式:

服务定位器

假设框架中有个ServiceLocator类,可以直接调用GetService方法便可以获得想要的对象。

​IDbConnection con = ServiceLocator.GetService<IDbConnection>();​

依赖注入

一个对象中需要包含其他类型的对象,则在创建该对象的时候,框架会自动创建所需类型的对象。

容器:负责提供对象的注册和获取功能的框架

服务:注册到容器中的对象

依赖注入的基本使用

声明周期:获取服务的时候是创建一个新对象还是用之前对象

  1. 瞬态(transient):每次请求都创建一个新对象。
  2. 范围(scoped):在给定范围内,多次请求共享一个对象;在不同范围内,服务每次被请求的时候返回不同对象。ASP.NET Core中,同一次http请求,不同的注入会获得同一对象。
  3. 单例(singleton):全局共享一个服务对象。

使用建议:

如果一个类无状态,建议设置为单例;否则,框架环境有范围控制,则周期设置为范围;使用瞬态周期时要尽可能在自范围中使用,否则容易造成内存泄漏。

不同服务之间具有依赖关系,A服务有一个B服务的属性,那么B的声明周期不能比A短

依赖注入框架中注册服务的时候,可以设定服务类型和实现类型,这两者可以不相同。例:

  • 服务和实现类型都是SqlConnection时,在获取SqlConnection服务时,会返回SqlConnection对象
  • 服务是IDbConnection接口类型,实现类型都是SqlConnection时,在获取IDbConnection接口服务时,会返回SqlConnection对象

服务定位器案例:

using Microsoft.Extensions.DependencyInjectiony;


public interface ITestService
{
public string Name { get; set; }
public void SayHi();
}

public class TestServiceImpl : ITestService
{
public string Name { get; set; }
public void SayHi()
{
Console.WriteLine($"Hi, I'm {Name}");
}
}
//获取服务之前要先注册服务

ServiceCollection services = new ServiceCollection();//1.创建用于注册服务的容器
//AddTransient、AddScoped、和AddSingleton分别注册瞬态、范围、单例服务
services.AddTransient<TestServiceImpl>();//2.注册一个瞬时服务,注册的服务必须涵盖程序中所有的所需服务
//ServiceProvider服务定位器
using (ServiceProvider sp = services.BuildServiceProvider())//3.获取服务定位器
{
//4.通过调用GetRequiredService方法获得对象
TestServiceImpl testService = sp.GetRequiredService<TestServiceImpl>();
testService.Name = "tom";
testService.SayHi();
}

依赖注入案例

接口—要注册的服务

interface IUserBiz{public bool CheckLogin(string userName, string password);}
interface IUserDAO{public User? GetByUserName(string userName);}

实现类

class UserBiz : IUserBiz
{
private readonly IUserDAO userDao;//所依赖的对象

public UserBiz(IUserDAO userDao) //构造函数中要求容器中必须注入IUserDAO服务
{
this.userDao = userDao;
}

public bool CheckLogin(string userName, string password)
{...
}
}

class UserDAO: IUserDAO
{
private readonly IDbConnection conn;//所依赖的对象

public UserDAO(IDbConnection conn)//构造函数中要求容器中必须注入IDbConnection服务
{
this.conn = conn;
}

public User? GetByUserName(string userName)
{...
}
}

组装服务

ServiceCollection services = new ServiceCollection();//1.创建用于注册服务的容器
//2.注册的服务必须涵盖程序中所有的所需服务
//注册IDbConnection服务
services.AddScoped<IDbConnection>(sp => {
string connStr = "Data Source=.;Initial Catalog=DI_DB;Integrated Security=true";
var conn = new SqlConnection(connStr);
conn.Open();
return conn;
});
services.AddScoped<IUserDAO, UserDAO>();//2.1.上面指定需要注入IUserDAO服务,但是要用UserDAO类来实现
services.AddScoped<IUserBiz, UserBiz>();
using (ServiceProvider sp = services.BuildServiceProvider())//3.获取服务定位器
{
var userBiz = sp.GetRequiredService<IUserBiz>();
bool b = userBiz.CheckLogin("yzk", "123456");
Console.WriteLine(b);
}

依赖注入的传染性:一个对象是通过依赖注入创建的,那么这个类的构造函数中所有的参数都是依赖注入赋值。但是如果一个类是手动创建,那么构造函数中所有的参数不是依赖注入。所以一旦使用依赖注入,则应避免使用new来创建。

如果一个服务有多个实现对象,可以被参数声明为​​IEnumerable<T>​​类型