1、前言
面向对象设计(OOD)里有一个重要的思想就是依赖倒置原则(DIP),并由该原则牵引出依赖注入(DI)、控制反转(IOC)及其容器等概念。在学习Core依赖注入、服务生命周期之前,下面让我们先了解下依赖倒置原则(DIP)、依赖注入(DI)、控制反转(IOC)等概念,然后再深入学习Core依赖注入服务。
2、依赖倒置原则(Dependency Inversion Principle, DIP)
抽象不应该依赖于细节,细节应当依赖于抽象,高层模块不依赖于低层模块的实现,而低层模块依赖于高层模块定义的接口。一般来讲,就是高层模块定义接口,低层模块负责具体的实现。针对接口编程而不是针对细节编程
3、什么是依赖注入(Denpendency Injection)
3.1、依赖
人与人之间都有依赖(尤其我,就是离不开女人哈哈)何况软件呢?所谓依赖就是:当一个类需要另一个类协作来完成工作的时候就产生了依赖。比如用户登录,我们在控制器中UserController要完成用户登录、注册、修改密码等等事情、其中操作到数据库的(登录)我们用EF来完成,这里我们封装了一个EFLogin,这里的UserController就有一个ILogin的依赖。需要知道的是这里依赖于一个抽象为不是具体的某一个实现,所以给EFLogin定义了一个接口ILogin抽象了EFLogin的行为
3.2、注入
注入体现的是一个IOC(控制反转的的思想)。
public interface IUser
{
string BB();
}
public class User : IUser
{
public string BB()
{
return "LP整天只会BB";
}
}
public class ShowInfo
{
IUser user = new User();
public void UserBB()
{
user.BB();
}
}
当我们调用ShowInfo的时候,是通过IUser接口实例化一个User类去实现其方法的这叫控制正传, 但是大湿兄说,我们不应该创建User类,而是让调用者给你传递,于是你通过构造函数让外界把这两个依赖给你。把依赖的创建丢给其它人。自己只负责使用,其它人丢给你依赖的这个过程理解为注入其它人丢给你依赖的这个过程理解为注入。也叫控制反转(IOC)。
public interface IUser
{
string BB();
}
public class User : IUser
{
public string BB()
{
return "LP整天只会BB";
}
} public class ShowInfo2
{
private readonly IUser _user;
public ShowInfo2 (IUser user)
{
_user = user;
}
public void UserBB()
{
_user.BB();
}
}
3.3、为什么要使用依赖注入?
使用依赖注入我们可以很好的管理类跟类之间的依赖,在我们设计应用程序的时候遵循这几原则,确保代码的可维护性和扩展性;另外在Core的架构中依赖注入提供了对象创建和生命周期管理的核心能力,各个组件之间的相互协作也是由依赖注入框架来实现的
4、服务生命周期
在ConfigureServices方法中的容器注册每个应用程序的服务,Asp.Core都可以为每个应用程序提供三种服务生命周期:
Transient(暂时):每次请求都会创建一个新的实例。这种生命周期最适合轻量级,无状态服务。
Scoped(作用域):在同一个作用域内只初始化一个实例 ,可以理解为每一个请求只创建一个实例,同一个请求会在一个作用域内。在Scooped的生存周期内,如果容器释放 它也就被释放了
Singleton(单例):整个应用程序生命周期以内只创建一个实例,后续每个请求都使用相同的实例。如果应用程序需要单例行为,建议让服务容器管理服务的生命周期,而不是在自己的类中实现单例模式。
为了演示生命周期和注册选项之间的差异,请考虑以下代码:
IGuid接口返回一个Guid
public interface IGuid
{
Guid GetGuid { get; }
}
接口IScopedService、ISingletonService、ITransientService、都继承接口IGuid
public interface IScopedService:IGuid
{ }
public interface ISingletonService: IGuid
{ }
public interface ITransientService: IGuid
{ }
GuidShow类继承接口IScopedService、ISingletonService、ITransientService
public class GuidShow : IScopedService, ISingletonService, ITransientService
{ public GuidShow() : this(Guid.NewGuid())
{
}
public GuidShow(Guid id)
{
GetGuid = id;
}
public Guid GetGuid { get; private set; } }
在Starup里面注册
public void ConfigureServices(IServiceCollection services)
{
#region//注册不同生命周期的服务
services.AddSingleton<ISingletonService, SingletonService>();
services.AddTransient<ITransientService, TransientService>();
services.AddScoped<IScopedService, ScopedService>();
#endregion
services.AddControllers();
}
在WeatherForecastController Api里写一个Api
FromServices就是从容器里面获取我们的对象 每个对象都获取两边来来对比每个生命周期是怎么样的
[ApiController]
[Route("[controller]/[action]")]
//路由
//API
[HttpGet]
public string GetService(
[FromServices] IScopedService scoped1, [FromServices] IScopedService scoped2,
[FromServices] ITransientService transient1, [FromServices] ITransientService transient2,
[FromServices] ISingletonService singleton, [FromServices] ISingletonService singleton2)
{
Console.WriteLine();
Console.WriteLine(); Console.WriteLine($"作用域1-->{scoped1.GetGuid}");
Console.WriteLine($"作用域2-->{scoped2.GetGuid}"); Console.WriteLine();
Console.WriteLine();
Console.WriteLine($"瞬时1-->{transient1.GetGuid}");
Console.WriteLine($"瞬时2-->{transient2.GetGuid}"); Console.WriteLine();
Console.WriteLine();
Console.WriteLine($"单例1-->{singleton.GetGuid}");
Console.WriteLine($"单例2-->{singleton2.GetGuid}"); Console.WriteLine("===========分割线=====================");
Console.WriteLine();
Console.WriteLine(); return "成功";
}
修改应用程序启动
启动应用程序
可以看出来单例跟作用域的都是一样的Guid 只有瞬时的不一样 再次刷新浏览器
单例的没有改变所以
Transient(暂时):每次调用服务的时候都会创建一个新的实例
Scoped(作用域):一次请求(Action)内对象实例是相同的,但每次请求会产生一个新实例。
Singleton(单例):首次请求初始化同一个实例,后续每次请求都使用同一个实例。相当于在整个应用Application中只实例化一次实例,常见的单例模式。
下面是其他的注册
#region//工程模式注册 单例作用域、瞬时 都可以用
services.AddSingleton<ISingletonService>(s=> {
return new SingletonService();
});
#region//尝试注册
//注册过了就不在注册了
//using Microsoft.Extensions.DependencyInjection.Extensions;
services.TryAddScoped<IScopedService, ScopedService>();
#endregion
#region//移除注册 移除所有IScopedService的注册 不同实现的
services.RemoveAll<IScopedService>(); #endregion
注册泛型 先写一个泛型类
public interface ITypeT<T>
{
}
public class TypeT<T> : ITypeT<T>
{
public T GetT { get; }
public TypeT(T getT)
{
this.GetT = getT;
}
}
创建一个api Test
[Route("api/[controller]/[action]")]
[ApiController]
public class TestController : ControllerBase
{
public ITypeT<IScopedService> _typeT;
public TestController(ITypeT<IScopedService> typeT)
{
_typeT = typeT;
} [HttpGet]
public string TestGet()
{
return _typeT.GetHashCode().ToString();
}
}
注册一下 里面具体的参数不用谢 实现的时候只要带入某个具体的类就可以了,第一个参数服务的额类型,第二个参数服务的实现类型
services.AddScoped(typeof(ITypeT<>),typeof(TypeT<>));
地址栏输入https://localhost:5001/api/test/testGet
看断点
GetT他得到的是ScopedService
5、依赖注入的方式
5.1、构造函数注入
我们可以在定义的Controller中以构造函数注入的方式注入所需的服务。他的服务是大部分接口都需要的话就用它
public ITypeT<IScopedService> _typeT;
public TestController(ITypeT<IScopedService> typeT)
{
_typeT = typeT;
}
5.2、FromServices
上面的GetService就是这种方式注入的,这个服务只是在某一个接口下用FromServices
当然还有其他的注入方式就不在研究了。