容器的使用
为了分离对象的实例化过程,IOC
使用 Register
方法来注册接口与实现之间的联系,再使用 Resolve 方法从容器中反转(解析)出对象。
IOC
里的对象的生命周期分为单例(Singleton)、瞬时(Transient)和作用域(Scoped)三种。
1、IOC容器
Container
类是一个 IOC
容器,用于管理接口与实现的映射关系,并能够反转出对应生命周期的对象。它是由 ContainerUnity
类创建的,因此基于配置可以管理多个这样的容器,每个容器互不干涉,管理自己的关系和对象。详细 容器配置。
2、注册与反转
Register 方法用来注册接口与实现的映射关系,Resolve 方法用来从容器中反转出对象。如下所示:
public interface IMyService
{
}
public class MyService1 : IMyService
{
}
public class MyService2 : IMyService
{
}
public class MyThirdService
{
public string Id => Guid.NewGuid().ToString();
}
[TestMethod]
public void TestRegister()
{
var container = ContainerUnity.GetContainer();
container
.Register<IMyService, MyService1>(Lifetime.Transient)
.Register(typeof(IMyService), typeof(MyService2), Lifetime.Transient)
.Register<MyThirdService>(Lifetime.Transient);
var obj1 = container.Resolve<IMyService>(); // MyService2
var obj2 = container.Resolve<MyThirdService>(); // MyThirdService
var obj3 = container.Resolve<MyThirdService>(); // MyThirdService
Assert.IsFalse(obj2.Id == obj3.Id); //由于是瞬时对象,两个Id不相同
}
3、注册程序集
RegisterAssembly 方法用于将整个程序集中的相关类注册到容器中。它是基于 ISingletonService
、ITransientService
和 IScopedService
三个接口发现的。如下所示:
public interface IMyOneService
{
}
public class MyOneService : ITransientService, IMyOneService
{
}
[TestMethod]
public void TestRegisterAssembly()
{
var container = ContainerUnity.GetContainer();
container
.RegisterAssembly(this.GetType().Assembly);
var obj = container.Resolve<IMyOneService>();
Assert.IsNotNull(obj);
}
标记 IRepeatableService
接口,则表示可以通过 IEnumerable<>
注入多个定义的实现。如下所示:
public interface IMyOneService
{
}
public class MyOneService1 : ITransientService, IMyOneService, IRepeatableService
{
}
public class MyOneService2 : ITransientService, IMyOneService, IRepeatableService
{
}
[TestMethod]
public void TestRegisterAssembly()
{
var container = ContainerUnity.GetContainer();
container
.RegisterAssembly(this.GetType().Assembly);
var obj = container.Resolve<IEnumerable<IMyOneService>>();
Assert.IsNotNull(obj);
}
4、注册初始化器
RegisterInitializer 方法用于为服务类注册一个初始化器,在反转对象时,初始化器将被执行。如下所示:
public interface IMyOneService
{
string Name { get; set; }
}
public class MyOneService : IMyOneService
{
public string Name { get; set; }
}
[TestMethod]
public void TestInitializer()
{
var container = ContainerUnity.GetContainer();
container
.Register<IMyOneService, MyOneService>(Lifetime.Transient)
.RegisterInitializer<IMyOneService>(s => s.Name = "fireasy");
var obj = container.Resolve<IMyOneService>();
Assert.AreEqual("fireasy", obj.Name);
}
5、依赖注入
依赖注入可分为构造器注入、属性注入和方法注册,一般我们只需要考虑前面两种,在 MVC Core 中出现的 FromServiceAttribute
特性,可归为第三种。
- 构造器注入
顾名思义,依赖对象从构造函数里注入,.Net Core 使用的就是这种方式。如下所示:
public class MyOneService
{
}
public class MyTwoService
{
private readonly MyOneService _myOneService;
public MyTwoService(MyOneService myOneService)
{
_myOneService = myOneService;
}
public bool HasOneService()
{
return _myOneService != null;
}
}
[TestMethod]
public void TestConstructorDI()
{
var container = ContainerUnity.GetContainer();
container
.Register<MyOneService>(Lifetime.Transient)
.Register<MyTwoService>(Lifetime.Transient);
var obj = container.Resolve<MyTwoService>();
Assert.IsNotNull(obj);
Assert.IsTrue(obj.HasOneService);
}
- 属性注入
顾名思义,依赖对象从公共属性里注入,注意属性拥有公共的 set 方法。如下所示:
public class MyOneService
{
}
public class MyTwoService
{
public MyOneService OneService { get; set; }
}
[TestMethod]
public void TestPropertyDI()
{
var container = ContainerUnity.GetContainer();
container
.Register<MyOneService>(Lifetime.Transient)
.Register<MyTwoService>(Lifetime.Transient);
var obj = container.Resolve<MyTwoService>();
Assert.IsNotNull(obj);
Assert.IsNotNull(obj.OneService);
}
可以使用属性上标记 IgnoreInjectPropertyAttribute
特性来忽略属性的注入。如修改以上代码中的 MyTwoService
类,给 OneService 属性加上此特性,则反转时无法注入该属性。
public class MyOneService
{
}
public class MyTwoService
{
[IgnoreInjectProperty]
public MyOneService OneService { get; set; }
}
[TestMethod]
public void TestIgnoreInjectProperty()
{
var container = ContainerUnity.GetContainer();
container
.Register<MyOneService>(Lifetime.Transient)
.Register<MyTwoService>(Lifetime.Transient);
var obj = container.Resolve<MyTwoService>();
Assert.IsNotNull(obj);
Assert.IsNull(obj.OneCService);
}
💡 特点提醒
在使用属性注入时,保证两个类之间不要出现双向引用。
6、作用域
作用域是一个抽象的概念,具体表现为在同一线程的某个范围内,瞬时对象是可重复使用的,这就提升了资源的利用率,同时当作用域结束时,对于实现了 IDispose
接口的对象,将进行资源回收。
CreateScope 方法用于创建一个作用域。如下所示:
public class MyThirdService
{
public string Id => Guid.NewGuid().ToString();
}
[TestMethod]
public void TestScope()
{
var container = ContainerUnity.GetContainer();
container
.Register<MyThirdService>(Lifetime.Scoped);
using (var scope = container.CreateScope())
{
var obj1 = container.Resolve<MyThirdService>();
var obj2 = container.Resolve<MyThirdService>();
Assert.IsTrue(obj2.Id == obj2.Id); //实为同一个对象,Id相同
}
}
7、扩展方法
Container
类针对不同的生命周期提供了相应的扩展方法,如:RegisterTransient、RegisterSingleton、 RegisterScoped 等等。
💡 不得不说的 IServiceProvider
Container
类实现了 IServiceProvider
接口,因此可以使用 GetService 方法来获取反转对象。在后续的很多组件中,都会有 IServiceProvider
的身影,它默认使用缺省的 Container
实例,因此在配置容器的时候应多加留意。