Unity IoC Container创建对象过程
Unity是微软P&P推出的一个开源的IoC框架,最新的官方版本是2.0。Unity之前的版本建立在一个称为ObjectBuild的组件上,熟悉EnterLib的读者,相信对ObjectBuild不会感到陌生。对于EnterLib 5.0之前的版本,ObjectBuild可以说是所有Application Block的基石。ObjectBuild提供一种扩展、可定制的对象创建方式,虽然微软官方没有将ObjectBuild和IoC联系在一起,其本质可以看成是一个IoC框架。在Unity 2.0中,微软直接将ObjectBuild(实际上是ObjectBuild的第二个版本ObjectBuild2)的绝大部分功能实现在了Unity中,而EnterLib则直接建立在Unity之上。由此可见Unity在EnterLib以及微软其他一些开源框架(比如Software Factory)中的重要地位。
之前园子里也有一些介绍EnterLib的文章,其中也不乏对Unity/ObjectBuild的介绍。虽然微软官方声称Unity是一个轻量级的IoC框架,但是并不意味着Unity会很简单。也正是因为Unity/ObjectBuild的复杂性,很多人撰文介绍Unity/ObjectBuild的时候,往往为了面面俱到,导致很多读者不知所云。最终的结果是,了解Unity/ObjectBuild的读者能够看懂,不懂的人读了还是不懂。在本篇文章中,我试着换一种介绍方式:抓住Unity/ObjectBuild最本质的东西,剔除一些细枝末节,希望以一种全新的视角让读者了解Unity的本质。
一、从管道+上下文(Pipeline+Context)模式说起
如果要说Unity Container采用的怎样的设计/架构模式的话,我的回答是“管道+上下文(Pipeline + Context)模式”(我不知道是否真的具有这样一种叫法)。在《WCF技术剖析(卷1)》中介绍Binding一章中,我曾经对该模式作了一个类比:“比如有一个为居民提供饮用水的自来水厂,它的任务就是抽取自然水源,进行必要的净化处理,最终输送到居民区。净化处理的流程可能是这样的:天然水源被汲取到一个蓄水池中先进行杂质的过滤(我们称这个池为过滤池);被过滤后的水流到第二个池子中进行消毒处理(我们称这个池为消毒池);被消毒处理的水流到第三个池子中进行水质软化处理(我们称这个池为软化池);最终水通过自来水管道流到居民的家中。”
当我们需要创建一个基础架构对某种元素(例子中需要进行处理的水)进行一系列处理的时候,我们就可以将相应的处理逻辑(例子中的过滤、消毒和软化)实现在相应“节点”(例子中的过滤池、消毒池和软化池 )中。根据需要(比如水质情况)对相应的节点进行有序组合(水质的不同决定了处理工序的差异)从而构成一个管道(自来水厂整个水处理管道)。由于每一个节点具有标准的接口,我们可以对组成管道的各个节点具有任意重组,也可以为某种需要自定义节点,从而使我们的“管道”变得能够适应所有的处理需要。
对于这样的设计,其实我们并不陌生。比如ASP.NET的运行时就可以看成是一个由若干HttpModule组成的处理HTTP请求的管道,WCF中Binding就是一个由若干信道(Channel)组成的处理Message的管道。相同的设计还体现在.NET Remoting, BizTalk等相关框架和产品的设计上。
基于相应标准的“节点”进行有序组合构成管道,但是各个相对独立的节点如何进行相应的协作呢?这就需要在整个管道范围内共享一些上下文(Context),上下文是对管道处理对象和处理环境的封装。ASP.NET运行时管道的上下文对象是HttpContext,而Binding管道的上下文是BindingContext。
二、UnityContainer是BuildStrategy的管道
作为一个IoC框架,Unity Container的最终目的动态地解析和注入依赖,最终提供(创建新对象或者提供现有对象)一个符合你要求的对象。为了让整个对象提供处理流程变得可扩展和可订制,整个处理过程被设计成一个管道。管道的每一个节点被称为BuilderStrategy,它们按照各自的策略参与到整个对象提供处理流程之中。
除了对象的提供功能之外,Unity Container还提供另一个相反的功能:对象的回收。我们暂且将两者称之为Build-Up和Tear-Down。Build-Up和Tear-Down采用相同的处理机制。
左图反映的就是Unity Container由若干BuilderStrategy组成的一个用于进行对象的Build-Up和Tear-Down的管道。每一个BuildStrategy具有相同的接口(这个接口是IBuilderStrategy),它们具有四个标准的方法:PreBuildUp、PostBuildUp、PreTearDown和PostTearDown。从名称我们不难看出,四个方法分别用于完成对象创建前/后和对象回收前后相应的操作。具体来说,当需要利用该管道创建某个对象的时候,先按照BuilderStrategy在管道中的顺序调用PreBuildUp方法,到管道底端后,按照相反的顺序调用PostBuildUp方法。
对于组成Unity Container管道的各个BuilderStrategy来说,它们彼此是相互独立的,一个BuilderStrategy只需要完成基于自身策略相应的操作,不需要知道其他BuilderStrategy的存在。只有这样才能实现对管道的灵活定制,真正实现可扩展。但是在真正工作的时候,彼此之间需要共享一些上下文以促进相互协作。在这里,BuilderContext起到了这样的作用。在Unity中,BuilderContext实现了IBuilderContext,我们不妨来看看IBuilderContext定义了些什么:
1: public interface IBuilderContext
2: {
3: //Others...
4: bool BuildComplete { get; set; }
5: NamedTypeBuildKey BuildKey { get; set; }
6: NamedTypeBuildKey OriginalBuildKey { get; }
7: object CurrentOperation { get; set; }
8: object Existing { get; set; }
9: }
上面对IBuilderContext的定义中,简单起见,我刻意省略了一些属性。在上述的属性列表中,BuildComplete表示Build操作是否被标识为结束,如果某个BuilderStrategy已经完成了Build的操作,可以将其设置为True,这样后续的BuilderStrategy就可以根据该值进行相应的操作(大部分将不作进行后续的Build);BuildKey和OriginalBuildKey是一个以Type + Name为组合的代表当前Build操作(通过CurrentOperation表示)的键;Existing属性表示已经生成的对象,一般来讲BuilderStrategy会将自己生成的对象赋给该值;而Strategies则代表了整个BuilderStrategy管道。
三、创建一个最简单的BuilderStrategy
现在我们编写一个最简单不过的例子,看看UnityContainer是如何借助于BuilderStrategy管道进行对象的提供的(你可以通过这里下载源代码)。我们先来创建一个最简单不过的BuilderStrategy,我们的策略就是通过反射的方式来创建对象,为此我们将该BuilderStrategy命名为ReflectionBuilderStrategy。
1: public class ReflectionBuilderStrategy:BuilderStrategy
2: {
3: public override void PreBuildUp(IBuilderContext context)
4: {
5: if (context.BuildComplete || null != context.Existing)
6: {
7: return;
8: }
9: var value = Activator.CreateInstance(context.BuildKey.Type);
10: if (null != value)
11: {
12: context.Existing = value;
13: context.BuildComplete = true;
14: }
15: }
16: }
ReflectionBuilderStrategy继承自统一的基类BuilderStrategy。由于我们只需要ReflectionBuildStrategy进行对象的创建,这里我们只需要重写PreBuildUp方法。在PreBuildUp方法中,如果需要提供的对象已经存在(通过BuilderContext的Existing属性判断)或者Build操作已经完成(通过BuilderContext的BuildComplete属性判断),则直接返回。否则通过BuilderContext的BuildKey属性得到需要创建对象的类型,通过反射的机制创建该对象,将其赋给BuilderContext的Existing属性,并将BuildComplete设置成True。
现在BuilderStrategy已经创建成功,如何将它添加到UnityContainer的BuilderStrategy管道呢?一般地,我们需要为BuilderStrategy创建相应的扩展对象。为此,下面我们创建了一个继承自UnityContainerExtension的类型ReflectionContainerExtension:
1: public class ReflectionContainerExtension : UnityContainerExtension
2: {
3: protected override void Initialize()
4: {
5: this.Context.Strategies.AddNew(UnityBuildStage.PreCreation);
6: }
7: }
在ReflectionContainerExtension中,我仅仅重写了Initialize方法。在该方法中,通过Context属性得到相应UnityContainer的BuilderStrategy管道,并调用AddNew方法将我们创建的ReflectionBuilderStrategy添加进取。泛型方法AddNew接受一个UnityBuildStage类型的枚举。UnityBuildStage代表整个Build过程的某个阶段,在这里决定了添加的BuilderStrategy在管道中的位置。现在我们假设需要通过UnityContainer来创建下面一个类型为Foo的对象:
1: public class Foo
2: {
3: public Guid Id { get; private set; }
4:
5: public Foo()
6: {
7: Id = Guid.NewGuid();
8: }
9: }
真正通过UnityContainer进行对象的提供实现在下面的代码中。由于UnityContainer在初始化的过程中会通过UnityDefaultStrategiesExtension这么一个扩展,所以我特意通过调用RemoveAllExtension将其清除。然后调用AddExtension将我们上面创建的ReflectionContainerExtension添加到UnityContainer的扩展列表中。最后3次调用UnityContainer的Resolve方法得到Foo对象,并将ID输出。
1: static void Main(string[] args)
2: {
3: IUnityContainer container = new UnityContainer();
4: container.RemoveAllExtensions();
5: container.AddExtension(new ReflectionContainerExtension());
6: Console.WriteLine(container.Resolve().Id);
7: Console.WriteLine(container.Resolve().Id);
8: Console.WriteLine(container.Resolve().Id);
9: }
10: }
下面是输出结果:
b38aa0b4-cc69-4d16-9f8c-8ea7baf1d853ef0cefc2-ffac-4488-ad96-907fb568360b08c538df-e208-4ef9-abe0-df7841d7ab60
四、通过自定义BuilderStrategy实现单例模式
上面的例子已经能够基本上反映出UnityContainer借助于BuilderStrategy管道的对象提供机制了。为了更加进一步的说明“管道”的存在,我们再自定义另一个简单的BuilderStrategy,实现我们熟悉的单例模式(基于UnityContainer对象来说是单例)。下面是是实现单例模式的BuilderStrategy:SingletonBuilderStrategy,和相应的Unity扩展。在SingletonBuilderStrategy中,我们通过一个静态字典用于缓存创建成功的对象,该对象在字典中的Key为创建对象的类型。被创建的对象在PostCreate方法中被缓存,在PreCreate中被返回。为了将该SingletonBuilderStrategy至于管道的前端,在添加的时候指定的UnityBuildStage为Setup。
1: public class SingletonBuilderStrategy : BuilderStrategy
2: {
3: private static IDictionary cachedObjects = new Dictionary();
4:
5: public override void PreBuildUp(IBuilderContext context)
6: {
7: if (cachedObjects.ContainsKey(context.OriginalBuildKey.Type))
8: {
9: context.Existing = cachedObjects[context.BuildKey.Type];
10: context.BuildComplete = true;
11: }
12: }
13:
14: public override void PostBuildUp(IBuilderContext context)
15: {
16: if (cachedObjects.ContainsKey(context.OriginalBuildKey.Type) || null == context.Existing)
17: {
18: return;
19: }
20:
21: cachedObjects[context.OriginalBuildKey.Type] = context.Existing;
22: }
23: }
24:
25: public class SingletonContainerExtension : UnityContainerExtension
26: { 27: protected override void Initialize()
28: {
29: this.Context.Strategies.AddNew(UnityBuildStage.Setup);
30: }
31: }
现在,我们将基于SingletonBuilderStrategy的扩展添加到之前的程序中。再次运行我们的程序,你会发现输出的ID都是一样的,由此可见三次创建的对象均是同一个。
1: class Program
2: {
3: static void Main(string[] args)
4: {
5: IUnityContainer container = new UnityContainer();
6: container.RemoveAllExtensions();
7: container.AddExtension(new ReflectionContainerExtension());
8: container.AddExtension(new SingletonContainerExtension());
9: Console.WriteLine(container.Resolve().Id);
10: Console.WriteLine(container.Resolve().Id);
11: Console.WriteLine(container.Resolve().Id);
12: }
13: }
输出结果:
254e1636-56e7-42f7-ad03-493864824d42254e1636-56e7-42f7-ad03-493864824d42254e1636-56e7-42f7-ad03-493864824d42
总结:虽然Unity具体的实现机制相对复杂,但是其本质就是本文所介绍的基于BuilderStrategy+BuilderContext的Pipeline+Context的机制。当你在研究Unity的具体实现原理的时候,抓住这个原则会让你不至于迷失方向。
Unity IoC Container创建对象过程的更多相关文章
- MVC中使用Unity Ioc Container
ASP.NET MVC中使用Unity Ioc Container 写在前面 安装Unity 添加服务层 IArticleRepository类型映射 服务注入到控制器 Global.asax初始 ...
- ASP.NET MVC中使用Unity Ioc Container
写在前面 安装Unity 添加服务层 IArticleRepository类型映射 服务注入到控制器 Global.asax初始化 后记 关于Unity的使用可以参照<Unity依赖注入使用详解 ...
- .NET Unity IOC框架使用实例
1.IOC简介 IOC(Inversion of Control), 控制反转 DI (Dependency Injection),依赖注入 IOC的基本概念是:不创建对象,但是描述创建它们的方式.在 ...
- Spring IOC Container
All the notes are from Spring Framework 5 Doc. 一.Introduction to the Spring IOC Container and Beans ...
- Spring IOC Container原理解析
Spring Framework 之 IOC IOC.DI基础概念 关于IOC和DI大家都不陌生,我们直接上martin fowler的原文,里面已经有DI的例子和spring的使用示例 <In ...
- 【.NET6+WPF】WPF使用prism框架+Unity IOC容器实现MVVM双向绑定和依赖注入
前言:在C/S架构上,WPF无疑已经是"桌面一霸"了.在.NET生态环境中,很多小伙伴还在使用Winform开发C/S架构的桌面应用.但是WPF也有很多年的历史了,并且基于MVVM ...
- 总结Unity IOC容器通过配置实现类型映射的几种基本使用方法
网上关于Unity IOC容器使用的方法已很多,但未能做一个总结,故我这里总结一下,方便大家选择. 首先讲一下通过代码来进行类型映射,很简单,代码如下: unityContainer = new Un ...
- AspNet Identity and IoC Container Registration
https://github.com/trailmax/IoCIdentitySample TL;DR: Registration code for Autofac, for SimpleInject ...
- Unity IOC容器通过配置实现类型映射的几种基本使用方法
网上关于Unity IOC容器使用的方法已很多,但未能做一个总结,故我这里总结一下,方便大家选择. 首先讲一下通过代码来进行类型映射,很简单,代码如下 unityContainer = new Uni ...
随机推荐
- 使用POSIX正则库匹配一行中多个结果
正则匹配与正则表达式是什么东西我就不说了,在这里说下POSIX这个c语言正则库在对字符串进行正则匹配时取出多个结果的问题. 首先简单说明下POSIX正则库的几个函数和使用方法 第一个函数:int re ...
- Android关联源码support-v4,v7,v13源码(转)
在Android实际开发过程中往往会遇到使用v4,v7或v13兼容包中的一些类如ViewPager,Fragment等,但却无法关联源码. 在网上搜索之后,有很多办法,这里只向大家介绍一种,我用的觉得 ...
- CentOS 基本设置
CentOS 基本设置 1.更改163源 在使用yum的时候,可能yum被锁,可用如下命令解锁:rm -rf /var/run/yum.id 2.编译安装开源软件 安装自己编译的开源软件一般都会在/u ...
- Sprint第二个冲刺(第十天)
一.Sprint 计划会议: 现在总结一下情况,正在做的3个功能的完成程度已经达到了80%,过几天就可以完成了.也把之前做的修改界面放入fragment中,方便修改管理.效果图如下: 二.Sprint ...
- LintCode Search For a Range (Binary Search)
Binary Search模板: mid 和 target 指针比较,left/ right 和 target 比较. 循环终止条件: 最后剩两数比较(while(left + 1 < righ ...
- 防刷新jq左侧滚动条导航展示
html代码: <div class="fangchan_navcont"> <div class="fangchan_nav" ...
- 最短路径问题——bellman算法
关于最短路径问题,最近学了四种方法——bellman算法.邻接表法.dijkstra算法和floyd-warshall算法. 这当中最简单的为bellman算法,通过定义一个边的结构体,存储边的起点. ...
- mysql source命令超大文件导入方法总结
本文章来给各位朋友介绍利用mysql source命令超大文件导入方法总结,下面收集了两种解决办法,一种是把数据库分文件导出然后再导入,另一种是修改my.ini配置文件,下面我一一给各位朋友介绍. 导 ...
- thrift demo
基于上一篇博客,安装thrift complier之后,就需要进行跑跑程序,来看看是否如同预期的那种效果. 前面的thrift compiler的主要作用,其实就是为了IDL的,就是防止客户端和服务端 ...
- OpenStack nova VM migration (live and cold) call flow
OpenStack nova compute supports two flavors of Virtual Machine (VM) migration: Cold migration -- mig ...