容器的使用


  为了分离对象的实例化过程,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 方法用于将整个程序集中的相关类注册到容器中。它是基于 ISingletonServiceITransientServiceIScopedService 三个接口发现的。如下所示:

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.Transient);

    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 实例,因此在配置容器的时候应多加留意。