微软企业库5.0 学习之路——第十步、使用Unity解耦你的系统—PART2——了解Unity的使用方法(2)

  在前一篇文章中,我简单的介绍了如何使用代码及配置文件来进行Unity容器配置,今天则继续介绍Unity容器的使用方法。

本篇文章将主要介绍:

1、注册对象之间的关系。

2、为已存在的对象注册关系。

3、Unity中Lifetime Managers介绍。

 

一、注册对象之间的关系

上一篇文章中,已经简单介绍了如何使用Unity来注册对象与对象之间的关系,通过RegisterType方法来注册对象之间的关系。

首先来看下类关系图:

pic71

有2个接口类:IClass(班级接口)和ISubject(科目接口),其分别有2个实现类,现在首先要注册班级相关的对象关系,代码如下:

public static void RegisterTypeCode()
{
    //注册IClass与MyClass之间的关系
    //此处为IClass的默认注册,每次调用Resolve<IClass>都会返回MyClass
    container.RegisterType<IClass, MyClass>();
    //如果再次进行默认注册,将会覆盖原来的注册
    //container.RegisterType<IClass, YourClass>();
    //注册IClass与YourClass之间的关系
    //此处多了一个参数"your",表示一个命名注册,只有当调用Resolve<IClass>("your")时才会返回YourClass
    //这种注册方法解决了当需要为某个对象注册多个对应关系时的冲突
    container.RegisterType<IClass, YourClass>("your");
 
    //获取具体的对象并调用
    IClass myClass = container.Resolve<IClass>();
    IClass yourClass = container.Resolve<IClass>("your");
    myClass.ShowInfo();
    yourClass.ShowInfo();
 
    //ResolveAll<IClass>方法可以一次性获取与IClass有注册关系的非默认对象实例
    //也就是已命名注册的对象,所以列表中只有一个对象YourClass
    IEnumerable<IClass> classList = container.ResolveAll<IClass>();
    foreach (var item in classList)
    {
        item.ShowInfo();
    }
}

这段代码展示了使用RegisterType方法来注册对象之间的关系,需要注意的是,在进行命名注册的时候,所提供的命名参数是大小写敏感的,所以输入“your”和“Your”表示的不是一个对象注册。同时这边还展示了如何通过Resolve方法来获取所需的对象,以及使用ResolveAll方法来获取与指定对象关联的所有对象列表。

有关RegisterType方法的其他重载我这边就不详细介绍了,可以点此查看详细的重载方法介绍。

 

接下来看下如何使用配置文件来进行配置(和上一篇的例子类似),配置文件代码如下:

<unity xmlns="http://schemas.microsoft.com/practices/2010/unity">
  <alias alias="IClass" type="UnityStudyConsole.IDemo.IClass, UnityStudyConsole" />
  <alias alias="MyClass" type="UnityStudyConsole.Demo.MyClass, UnityStudyConsole" />
  <alias alias="YourClass" type="UnityStudyConsole.Demo.YourClass, UnityStudyConsole" />
   
  <container name="First">
    <register type="IClass" mapTo="MyClass" />
    <register type="IClass" mapTo="YourClass" name="your" />
  </container>
</unity>
<span style="font-family: Verdana;">读取并调用代码如下:</span>


public static void RegisterTypeCodeConfiguration()
{ 
    //获取指定名称的配置节
    UnityConfigurationSection section = (UnityConfigurationSection)ConfigurationManager.GetSection("unity");
 
    container.LoadConfiguration(section, "First");
 
    IClass classInfo = container.Resolve<IClass>();
    classInfo.ShowInfo();
}

二、为已存在的对象注册关系

在日常开发的过程中我们有时候会自己创建好一个对象,但是你又想对这个已经创建好的对象的生命周期进行管理,这个时候你可以使用Unity提供的RegisterInstance方法,代码示例如下:

public static void RegisterInstance()
{
    IClass myClass = new MyClass();
    IClass yourClass = new YourClass();
    //为myClass实例注册默认实例
    container.RegisterInstance<IClass>(myClass);
    //为yourClass实例注册命名实例,同RegisterType
    container.RegisterInstance<IClass>("yourInstance", yourClass);
 
    container.Resolve<IClass>().ShowInfo();
 
    container.Resolve<IClass>("yourInstance").ShowInfo();
}

这段代码很简单,就是通过RegisterInstance方法为已存在的对象进行注册,这样可以通过UnityContainer来管理这些对象实例的生命周期。

需要注意的是,使用RegisterInstance来将已存在的 实例注册到UnityContainer中,默认情况下其实用的是ContainerControlledLifetimeManager,这个生命周期 是由UnityContainer来进行管理,UnityContainer会维护一个对象实例的强引用,当你将已存在的实例注册到 UnityContainer后,每次通过Resolve方法获取对象都是同一对象,也就是单件实例(singleton instance),具体有关生命周期相关信息在下面进行介绍。

由于RegisterInstance是对已存在的实例进行注册,所以无法通过配置文件来进行配置。

有关RegisterInstance方法的其他重载我这边就不详细介绍了,可以点此查看详细的重载方法介绍。

 

三、Unity中Lifetime Managers介绍

我们在系统中引入Unity主要就是想通过Unity来解除对象之间的依赖关系,方便我们根据配置调用到所需的对象,而Unity默认情况下会自动 帮我们维护好这些对象的生命周期,可能Unity自动维护的生命周期并不是我们想要的,我们想要根据具体的需求来更改这些对象的生命周期,下面我就介绍一 下Unity中内置的生命周期管理器。

1、TransientLifetimeManager,瞬态生命周期,默认情况下,在使用RegisterType进行对象关系注册时如果没有指定生命周期管理器则默认使用这个生命周期管理器,这个生命周期管理器就如同其名字一样,当使用这种管理器的时候,每次通过ResolveResolveAll调用对象的时候都会重新创建一个新的对象。

需要注意的是,使用RegisterInstance对已存在的对象进行关系注册的时候无法指定这个生命周期,否则会报异常。

代码如下:

public static void TransientLifetimeManagerCode()
{
    //以下2种注册效果是一样的
    container.RegisterType<IClass, MyClass>();
    container.RegisterType<IClass, MyClass>(new TransientLifetimeManager());
    Console.WriteLine("-------TransientLifetimeManager Begin------");
    Console.WriteLine("第一次调用RegisterType注册的对象HashCode:" + 
        container.Resolve<IClass>().GetHashCode());
    Console.WriteLine("第二次调用RegisterType注册的对象HashCode:" + 
        container.Resolve<IClass>().GetHashCode());
    Console.WriteLine("-------TransientLifetimeManager End------");
}

配置文件如下:

<register type="IClass" mapTo="MyClass">
  <lifetime type="transient" />
  <!--<lifetime type="SessionLifetimeManager"
      value="Session#1" typeConverter="SessionLifetimeConverter" />-->
</register>

如果想在配置文件中在在注册关系的时候更改一个生命周期管理器只需在<register>配置节下新增<lifetime>既可(如果不新增则默认使用TransientLifetimeManager)。

其中<lifetime>有3个参数:

1)type,生命期周期管理器的类型,这边可以选择Unity内置的,也可以使用自定义的,其中内置的生命周期管理器会有智能提示。

2)typeConverter,生命周期管理器转换类,用户自定义一个生命周期管理器的时候所创建一个转换器。

3)value,初始化生命周期管理器的值。

配置文件读取代码:

public static void TransientLifetimeManagerConfiguration()
{
    //获取指定名称的配置节
    UnityConfigurationSection section = (UnityConfigurationSection)ConfigurationManager.GetSection("unity");
    container.LoadConfiguration(section, "First");
 
    Console.WriteLine("-------TransientLifetimeManager Begin------");
    Console.WriteLine("第一次调用RegisterType注册的对象HashCode:" +
        container.Resolve<IClass>("transient").GetHashCode());
    Console.WriteLine("第二次调用RegisterType注册的对象HashCode:" +
        container.Resolve<IClass>("transient").GetHashCode());
    Console.WriteLine("-------TransientLifetimeManager End------");
}

效果图如下,可以看出每次产生的对象都是不同的:

pic72

 

2、ContainerControlledLifetimeManager,容器控制生命周期管理,这个生命周期管理器是RegisterInstance默认使用的生命周期管理器,也就是单件实例,UnityContainer会维护一个对象实例的强引用,每次调用的时候都会返回同一对象,示例代码如下

public static void ContainerControlledLifetimeManagerCode()
{
    IClass myClass = new MyClass();
    //以下2种注册效果是一样的
    container.RegisterInstance<IClass>("ccl", myClass);
    container.RegisterInstance<IClass>("ccl", myClass, new ContainerControlledLifetimeManager());
 
    container.RegisterType<IClass, MyClass>(new ContainerControlledLifetimeManager());
    Console.WriteLine("-------ContainerControlledLifetimeManager Begin------");
    Console.WriteLine("第一次调用RegisterType注册的对象HashCode:" +
        container.Resolve<IClass>().GetHashCode());
    Console.WriteLine("第二次调用RegisterType注册的对象HashCode:" +
        container.Resolve<IClass>().GetHashCode());
    Console.WriteLine("第一次调用RegisterInstance注册的对象HashCode:" +
        container.Resolve<IClass>("ccl").GetHashCode());
    Console.WriteLine("第二次调用RegisterInstance注册的对象HashCode:" +
        container.Resolve<IClass>("ccl").GetHashCode());
    Console.WriteLine("-------ContainerControlledLifetimeManager End------");
}

配置文件如下:

<register type="IClass" mapTo="MyClass" name="ccl">
  <lifetime type="singleton" />
</register>

效果图如下,可以看出每次获取的对象都是同一对象:

pic73

 

3、HierarchicalLifetimeManager,分层生命周期管理器,这个管理器类似于ContainerControlledLifetimeManager,也是由UnityContainer来管理,也就是单件实例。不过与ContainerControlledLifetimeManager不 同的是,这个生命周期管理器是分层的,因为Unity的容器时可以嵌套的,所以这个生命周期管理器就是针对这种情况,当使用了这种生命周期管理器,父容器 和子容器所维护的对象的生命周期是由各自的容器来管理,代码如下(RegisterInstance情况也类似,这边就不展示了):

public static void HierarchicalLifetimeManagerCode()
{
    container.RegisterType<IClass, MyClass>(new HierarchicalLifetimeManager());
    //创建子容器
    var childContainer = container.CreateChildContainer();
    childContainer.RegisterType<IClass, MyClass>(new HierarchicalLifetimeManager());
 
    Console.WriteLine("-------ContainerControlledLifetimeManager Begin------");
    Console.WriteLine("第一次调用父容器注册的对象HashCode:" + 
        container.Resolve<IClass>().GetHashCode());
    Console.WriteLine("第二次调用父容器注册的对象HashCode:" + 
        container.Resolve<IClass>().GetHashCode());
    Console.WriteLine("第一次调用子容器注册的对象HashCode:" + 
        childContainer.Resolve<IClass>().GetHashCode());
    Console.WriteLine("第二次调用子容器注册的对象HashCode:" + 
        childContainer.Resolve<IClass>().GetHashCode());
    Console.WriteLine("-------ContainerControlledLifetimeManager End------");
}

由于配置文件不能配置这种层级效果,所以配置这种生命周期时只需要更改下生命周期名称:

<register type="IClass" mapTo="MyClass" name="hl">
  <lifetime type="hierarchical" />
</register>

具体的效果图如下,可以看出父级和子级维护不同对象实例:

pic74

这边需要提一下的就是,Unity这种分级容器的好处就在于我们可以对于有不同生命周期的对象放在不同的容器中,如果一个子容器被释放,不会影响到其它子容器中的对象,但是如果根节点处父容器释放后,所有的子容器都将被释放。

 

4、PerResolveLifetimeManager,这个生命周期是为了解决循环引用而重复引用的生命周期,先看一下微软官方给出的实例:

public interface IPresenter
{ }
 
public class MockPresenter : IPresenter
{
    public IView View { get; set; }
 
    public MockPresenter(IView view)
    {
        View = view;
    }
}
 
public interface IView
{
    IPresenter Presenter { get; set; }
}
 
public class View : IView
{
    [Dependency]
    public IPresenter Presenter { get; set; }
}

从这个例子中可以看出,有2个接口IPresenter和IView,还有2个类MockPresenter和View分别实现这2个接口,同时这2个类 中都包含了对另外一个类的对象属性,这个就是一个循环引用,而对应的这个生命周期管理就是针对这种情况而新增的,其类似于 TransientLifetimeManager,但是其不同在于,如果应用了这种生命周期管理器,则在第一调用的时候会创建一个新的对象,而再次通过 循环引用访问到的时候就会返回先前创建的对象实例(单件实例),代码如下:

public static void PerResolveLifetimeManagerCode()
{
    var container = new UnityContainer()
    .RegisterType<IPresenter, MockPresenter>()
    .RegisterType<IView, View>(new PerResolveLifetimeManager());
 
    var view = container.Resolve<IView>();
    var tempPresenter = container.Resolve<IPresenter>();
    var realPresenter = (MockPresenter)view.Presenter;
 
    Console.WriteLine("-------PerResolveLifetimeManager Begin------");
    Console.WriteLine("使用了PerResolveLifetimeManager的对象 Begin");
    Console.WriteLine("通过Resolve方法获取的View对象:" +
        view.GetHashCode());
    Console.WriteLine("View对象中的Presenter对象所包含的View对象:" +
        realPresenter.View.GetHashCode());
    Console.WriteLine("使用了PerResolveLifetimeManager的对象 End");
    Console.WriteLine("");
    Console.WriteLine("未使用PerResolveLifetimeManager的对象 Begin");
    Console.WriteLine("View对象中的Presenter对象:" +
        realPresenter.GetHashCode());
    Console.WriteLine("通过Resolve方法获取的View对象:" +
        tempPresenter.GetHashCode());
    Console.WriteLine("未使用PerResolveLifetimeManager的对象 End");
    Console.WriteLine("-------PerResolveLifetimeManager Begin------");
}

从代码中可以看出,在注册对象的时候,仅对IView和View应用了PerResolveLifetimeManager,所以第二次访问View对象会返回同一实例。

具体配置文件如下,有关构造函数注入和属性注入的内容在下一篇文章中进行介绍

<alias alias="IPresenter" type="UnityStudyConsole.IPresenter, UnityStudyConsole" />
<alias alias="IView" type="UnityStudyConsole.IView, UnityStudyConsole" />
<alias alias="MockPresenter" type="UnityStudyConsole.MockPresenter, UnityStudyConsole" />
<alias alias="View" type="UnityStudyConsole.View, UnityStudyConsole" />
<container name="Second">
  <register type="IPresenter" mapTo="MockPresenter">
    <constructor>
      <param name ="view" type="IView">
      </param>
    </constructor>
  </register>
  <register type="IView" mapTo="View" >
    <lifetime type="perresolve"/>
    <property name="Presenter" dependencyType="IPresenter"></property>
  </register>
</container>

 

读取配置文件代码类似于前面其他配置文件读取代码,这里就不展示,具体请看示例代码。

具体的效果图如下:

pic75

可以看出2次调用View对象的HashCode都是一样的,而Presenter对象的HashCode不同。

 

5、PerThreadLifetimeManager,每线程生命周期管理器,就是保证每个线程返回同一实例,具体代码如下:

public static void PerThreadLifetimeManagerCode()
{
    container.RegisterType<IClass, MyClass>(new PerThreadLifetimeManager());
    var thread = new Thread(new ParameterizedThreadStart(Thread1));
    Console.WriteLine("-------PerResolveLifetimeManager Begin------");
    Console.WriteLine("默认线程 Begin");
    Console.WriteLine("第一调用:" +
        container.Resolve<IClass>().GetHashCode());
    Console.WriteLine("第二调用:" +
        container.Resolve<IClass>().GetHashCode());
    Console.WriteLine("默认线程 End");
    thread.Start(container);
}
 
public static void Thread1(object obj)
{
    var tmpContainer = obj as UnityContainer;
    Console.WriteLine("新建线程 Begin");
    Console.WriteLine("第一调用:" + 
        tmpContainer.Resolve<IClass>().GetHashCode());
    Console.WriteLine("第二调用:" + 
        tmpContainer.Resolve<IClass>().GetHashCode());
    Console.WriteLine("新建线程 End");

Console.WriteLine("-------PerResolveLifetimeManager End------"); }

有关配置相关的代码与前面的生命周期管理器差不多,这边就不贴代码了,请看示例代码。

具体效果图如下:

pic76

同时需要注意的是,一般来说不建议在使用RegisterInstance对已存在的对象注册关系时使用PerThreadLifetimeManager,因为此时的对象已经在一个线程内创建了,如果再使用这个生命周期管理器,将无法保证其正确调用。

 

6、ExternallyControlledLifetimeManager,外部控制生命周期管理器,这个 生命周期管理允许你使用RegisterType和RegisterInstance来注册对象之间的关系,但是其只会对对象保留一个弱引用,其生命周期 交由外部控制,也就是意味着你可以将这个对象缓存或者销毁而不用在意UnityContainer,而当其他地方没有强引用这个对象时,其会被GC给销毁 掉。

在默认情况下,使用这个生命周期管理器,每次调用Resolve都会返回同一对象(单件实例),如果被GC回收后再次调用Resolve方法将会重新创建新的对象,示例代码如下:

public static void ExternallyControlledLifetimeManagerCode()
{
    container.RegisterType<IClass, MyClass>(new ExternallyControlledLifetimeManager());
    var myClass1 = container.Resolve<IClass>();
    var myClass2 = container.Resolve<IClass>();
    Console.WriteLine("-------ExternallyControlledLifetimeManager Begin------");
    Console.WriteLine("第一次调用:" +
        myClass1.GetHashCode());
    Console.WriteLine("第二次调用:" +
        myClass2.GetHashCode());
    myClass1 = myClass2 = null;
    GC.Collect();
    Console.WriteLine("****GC回收过后****");
    Console.WriteLine("第一次调用:" +
       container.Resolve<IClass>().GetHashCode());
    Console.WriteLine("第二次调用:" +
       container.Resolve<IClass>().GetHashCode());
 
    Console.WriteLine("-------ExternallyControlledLifetimeManager End------");
}

有关配置相关的代码与前面的生命周期管理器差不多,这边就不贴代码了,请看示例代码。

效果图如下:

pic77

 

以上就是本文的全部内容了,主要介绍了使用UnityContainer来注册对象之间的关系、注册已存在的对象之间的关系和Unity内置的生命周期管理器。

 

示例代码下载:点我下载

 

posted on 2016-08-09 16:15  邬兴亮  阅读(193)  评论(0编辑  收藏  举报

导航