Code Copied

Unity教程系列(一) DI的生命周期

1. 介绍

DI对象的生命周期(或者说叫执行过程)有3个阶段:注册(Register)、解析(Resolve)、销毁(Dispose)。

image

对象的注册、解析和销毁都由Container来实现。

2.对象的Register, Resolve和Dispose

2.1. Register

通过Container可以注册一组抽象和具体类型的映射关系,Container可以将抽象类型注入到构造函数(或属性或方法)中。

简单地说,通过依赖注入的方式在构造函数(或属性或方法)中我们将只依赖于抽象。

在使用抽象类型或接口的方法时,由于抽象类型和接口无法被实例化,通常我们需要先实例化它们的继承类型的后再调用,就像下面这样:

public class UserManagementController : Controller
{
    private IUserService _userService;
    public UserManagementController()
    {
        _userService = new UserService();
    }
}

这里UserManagementController不仅依赖于具体的UserService类,而且还要负责接口IUserService的实例化。
但是如果使用依赖注入,通过Container来实例化抽象类型的具体类型,UserManagementController将不会依赖于UserService,也不需要负责IUserService的实例化。
这也符合OO中的2个原则:1.单一职责原则(SRP) 2.依赖倒置原则(DIP)

public class UserManagementController : Controller
{
    private IUserService _userService;
    public UserManagementController(IUserService userService)
    {
        _userService = userService;
    }
}

2.1.1.RegisterType和RegisterInstance方法

我们可以通过以下两种方法给Unity container中创建映射:

  • RegisterType:这个方法可以往container中注册一种类型或映射关系,当我们需要调用该类型的实例时,container会自动实例化该类型的对象,无需通过new someName方法实例化对象(例如:使用ResolveResolveAll方法获取注册类型的实例),当没有指定实例化对象的生命周期,将使用默认的TransientLifetimeManager(每次调用ResolveResolveAll方法时都会实例化一个新的对象)。
  • RegisterInstance:这个方法用于往container中注册单件实例(Singleton Instance),通过ResolveResolveAll方法获取该类型的实例,默认使用ContainerControlledLifetimeManager管理对象生命周期,而且container中会保持对象的引用(简而言之每次调用ResolveResolveAll方法都会调用同一个对象的引用)。

2.1.2. 在应用程序中添加Unity

Untiy可以通过在Package Manage Console中运行Nuget Command加入到项目中。
http://www.nuget.org/packages/Unity能够查看Unity的Nuget版本历史。

image

image

2.1.3 简单示例

通常情况下,无论是在WinForm, WebForm还是在MVC应用程序中,实例的注册都是集中放在一个class中或者一个config文件中。
由于具体类型和接口类型之间的映射只需要做一次就够了,所以执行对象的注册一般都是在应用程序启动时,例如:MVC中在global.ascx的Application_Start方法中进行注册。
在这样的class或者config文件中统一管理了整个应用程序所有需要DI的注册。
就如下面定义了一个ContainerBootstrapper类(下面的示例是一个Console应用程序,仅仅作为示例):

public class ContainerBootstrapper
{
    public static void RegisterTypes(IUnityContainer container)
    {
        Trace.WriteLine(string.Format("Called RegisterTypes in ContainerBootstrapper"), "UNITY");

        // 单例注册
        StorageAccount account =
          ApplicationConfiguration.GetStorageAccount("DataConnectionString");
        container.RegisterInstance(account);

        // IUserService注册
        container.RegisterType<IUserService, UserService>();
    }
}

由于这是一个Console应用程序,它并没有如MVC中global.asxc文件(在Application_Start中调用ContainerBootstrapper.RegisterTypes方法),所以我们还需要显示地调用注册方法:

class Program
{
    static void Main(string[] args)
    {
        var tr1 = new TextWriterTraceListener(System.Console.Out);
        Debug.Listeners.Add(tr1);

        using (var container = new UnityContainer())
        {
            Console.WriteLine("# 执行实例注册...");
            ContainerBootstrapper.RegisterTypes(container);
            Console.WriteLine("Container有{0}个已注册实例:",
                  container.Registrations.Count());

            foreach (ContainerRegistration item in container.Registrations)
            {
                Console.WriteLine(item.GetMappingAsString());
            }
        }

        Console.WriteLine("完成!");
        Console.ReadLine();
    }
}
运行结果如下:
image

在ContainerBootstrapper.RegisterTypes中虽然仅仅只注册了StorageAccount的单例实例和IUserService的实例,但是在实际运行时container本身是最先被注册的,所以在运行结果中能够看到3个注册实例。

2.2. Resolve

Register告诉了应用程序具体类型和接口类型之间的映射关系,当要使用接口类型来调用方法时,必须先实例化该接口。
实例化接口是通过container的Resolve或者ResolveAll方法实现的,container会根据Register中的映射关系创建接口对应的实例。
例如:IUserService接口的具体实现类是UserService,在执行Resolve操作时,container会实例化一个UserService实例。
这就是DI神奇的地方,我们再也不需要显示地用new操作符去创建对象了,container帮我们完成了这个操作,并给了我们一个已经“实例化”的抽象接口,应用程序接收的就是接口类型。

class Program
{
    static void Main(string[] args)
    {
        var tr1 = new TextWriterTraceListener(System.Console.Out);
        Debug.Listeners.Add(tr1);

        using (var container = new UnityContainer())
        {
            Console.WriteLine("# 执行实例注册...");
            ContainerBootstrapper.RegisterTypes(container);
            Console.WriteLine("Container有{0}个已注册实例:",
                  container.Registrations.Count());

            foreach (ContainerRegistration item in container.Registrations)
            {
                Console.WriteLine(item.GetMappingAsString());
            }

            // 实例化IUserService接口,Program不依赖于具体的UserService类,而仅依赖于IUserService接口
            var userService = container.Resolve<IUserService>();
            // 调用IUserService的方法
            bool auth = userService.Auth("Admin", "Admin");
            Console.WriteLine("用户验证是否通过?{0}", auth ? "是" : "否");
        }
        
        Console.WriteLine("完成!");
        Console.ReadLine();
    }
}

运行结果:
image

2.3. Dispose

在使用完IUserService的实例后,它不可能一直存在于内存中,它什么时候会Dispose呢?
在这里个示例中,IUserService的实例会在Main()方法执行完成后Dispose,然后通过垃圾回收机制回收。
通常情况下,接口在实例化后,这个实例化的对象会在依赖于这个接口的类的对象(在示例中是Program的实例)资源释放后Dispose。