2.2. 如何配置Unity2.0容器
配置容器包括注册类型、类型映射以及解决对象之间的依赖关系的各种所需信息。Unity可以通过XML配置文件获取配置信息,也可以通过一系列的方法调用进行运行时的容器配置(这也是我选择Unity作为我常用的IoC容器的原因,配置文件实在是太令人头痛了)。
2.2.1. 设计时配置
2.2.1.1. 开启XML智能感应
在Visual Studio中能够通过XSD文件开启智能感应,有如下两种方法可以开启智能感应:
l 在Visual Studio中选择XML选项卡,然后选择Schemas选项,在里面找到UnityConfiguration20.xsd文件,选择使用即可;
l 直接在XML配置文件中输入<unity xmlns=",然后在弹出的提示中选择http://schemas.microsoft.com/practices/2010/unity即可。
这样就能够避免在输入XML时的一些错误。
2.2.1.2. Unity配置文件的基本结构
<?xml version="1.0" encoding="utf-8" ?> <configuration> <configSections> <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Microsoft.Practices.Unity.Configuration"/> </configSections> <unity xmlns="http://schemas.microsoft.com/practices/2010/unity"> <alias alias="ISayHello" type="HelloWorld.ISayHello, HelloWorld" /> <namespace name="HelloWorld" /> <assembly name="HelloWorld" /> <container> <register type="ISayHello" name="mother" mapTo="MotherSayHello" /> <register type="ISayHello" name="child" mapTo="ChildSayHello" > <constructor> <param name="count" value="2"/> </constructor> </register> <instance type="ConnectionSettings" value="port=1234" typeConverter="ConnectionSettingsTypeConverter" /> <extension type="Microsoft.Practices.Unity.Interception.InterceptionExtension, Microsoft.Practices.Unity.Interception" /> </container> </unity> </configuration> |
从上面的配置文件中可以看出Unity容器配置的基本结构,在第一层可以有alias、namespace、assembly、container这几种节点,它们都可以在配置文件中出现多次;然后在container节下又有register、instance、extension三种节点,而容器的配置也主要是针对container节进行,在下面的章节中会针对这些配置节的用法进行注意解释。
2.2.1.3. 自动类型查找机制
在大部分的框架的配置文件中,要求类型一定要写成型如:“namespace.typename,assemblyname”的类型全名。这使得的配置文件一下子变的非常臃肿,难以维护。在Unity中提供了一种名为Automatic Type Lookup的机制来解决这一问题,在之前的Hello World的例子中也已经有提到过这种机制。
在Unity中使用namespace和assembly配置节来进行自动类型查找的配置。这里通过一个例子来说明自动查找的步骤,参考如下的一份配置文件:
<unity xmlns="http://schemas.microsoft.com/practices/2010/unity"> <namespace name="MyApp.Interfaces" /> <namespace name="System" /> <assembly name="MyApp"/> <assembly name="mscorlib, 2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" /> ... </unity> |
如果要查找ILogger接口,那么容器会按照下面的顺序进行匹配:
1. MyApp.Interfaces.ILogger, MyApp
2. System.ILogger, MyApp
3. MyApp.Interfaces.ILogger, mscorlib, 2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
4. System.ILogger, mscorlib, 2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
即按照在配置文件中出现的先后顺序,分别对namespace.typename,assembly的各种组合进行匹配,当找到第一个匹配的项时,查找就会停止;如果找不到那么容器会抛出一个异常。
2.2.1.4. 注册类型映射
<register> 配置节是在Unity容器配置中使用最多的配置节,用来配置类型之间的映射关系,以及类型所需要的依赖注入。这个节点具有type、name、mapTo三个属性,其中type属性是必填的,它表示要注册的类型(在Resolve方法中传入的类型)。name属性是可选参数,如果不填那么这个映射就是个默认类型映射,Unity容器对于默认类型映射和有名字的类型映射会区别对待,尤其是在调用ResolveAll方法时,只能获取到所有的有名字的类型映射。另外如果有多个类型映射使用同一个name那么后注册的映射关系会覆盖之前的映射关系。mapTo属性则表示了类型要映射到的实际类型,当调用Resolve方法时,会返回实际的类型。在实际使用中一般type属性中填的是一个接口或抽象的基类,然后mapTo属性中则是在运行时需要的具体类型,这样就通过依赖注入实现了解耦。
在注册一个类型映射的时候,同时还可以配置这个类型对应的Lifetime以及所需要的各种依赖关系,包括构造函数注入(Constructor)、属性注入(Property)和方法调用注入(Method):
l Lifetime:这个属性用来控制实例在容器中的生命周期,默认的是TransientLifetimeManager,即每次都请求都会返回一个新的实例;比较常用的是ContainerControlledLifetimeManager,每次请求都会返回同一个实例,即我们平常所说的单例模式。在Unity中内置了很多种在开发过程中可能会用到的管理器,有兴趣的同学可以查看对应的MSDN帮助,除此之外开发者还能够按照自己的需要开发自己的LifetimeManager来满足不同的需求。配置的样例如下:
<register type="ILogger" mapTo="SerialPortLogger"> <!-- Simple use of lifetime - singleton instance --> <lifetime type="singleton" /> </register> <register type="TypeWithCustomLifetime"> <!-- Use a Lifetime manager instance created by a type converter. Type converter gets passed extra information provided in in value attribute --> <lifetime type="SessionLifetimeManager" value="Session#1" typeConverter="SessionLifetimeConverter" /> </register> |
l Constructor:这个属性用来在实例的构造期间进行构造函数注入,Unity容器在创建实例时默认会使用参数列表最长的构造函数,而使用这个属性则可以改变容器的默认行为,而且可以对构造函数中的各个参数的值进行指定。在之前的HelloWorld示例中已经使用过了构造函数注入,当时的配置如下:
<register type="ISayHello" name="child" mapTo="ChildSayHello" > <constructor> <param name="count" value="2"/> </constructor> </register> |
其中param表示构造函数的参数,可以重复多个,里面使用name和value属性来制定参数的名字和值,这里就是调用参数名为count,类型为Int型的构造函数;当然你也可以使用type属性来指定参数的具体类型,使用dependencyName属性来指定参数注入所要用到的依赖的名字(就是它在register节点中注册时的name),使用dependencyType属性来指定参数注入所要用到的依赖类型,如果设置了这个属性而不设置dependencyName属性,那么容器会去查找这个类型的默认实例进行注入,否则使用指定的实例。
l Property:这个属性用来对实例的属性进行注入,可以有一个或多个配置,具体的配置内容与构造函数中的param类同,需要注意的是,如果在配置中只指明要注入的属性的name,那么容器会根据该属性类型在容器中的默认映射关系进行注入,配置样例如下:
<register type="ILogger" mapTo="SerialPortLogger"> <property name="Settings" dependencyType="PortSettings" dependencyName="highSpeedSettings" /> </register> |
l Method:这个属性用来在实例创建后调用指定的方法,并按照配置对该方法的参数进行注入,配置的方法跟constructor属性完全相同,只是将constructor替换成method并加上对应方法的name即可,配置样例如下:
<register type="MyLogger"> <method name="Initialize"> <param name="loggerSettings" /> </method> </register> |
2.2.1.5. 注册类型实例
<instance>节用来向容器中注册一个不是通过容器创建的类型实例,它可以是一个简单的字符串表示,也可以是一个复杂的字符串,然后通过一个typeConverter进行转换。这个配置节包括name、type、value、typeConverter四个属性,其中type属性的默认值为System. String,如果需要进行自定义的类型转换,那么只要对typeConverter赋上相应的值即可。
Tip:因为实例的创建并不是通过容器进行的,所以无法在此配置各种依赖注入。
2.2.1.6. 读取配置创建Unity容器
通过上面的章节,已经能够对Unity容器的配置有了主观的了解,接下来要做的就是把配置文件跟具体的Unity容器关联在一起,映射到代码中只有一行,如下:
IUnityContainer container = new UnityContainer().LoadConfiguration();
Tip:LoadConfiguration是Microsoft.Practices.Unity.Configuration;名字空间下的扩展方法,在使用前请不要忘了添加对应的名字空间引用。