编辑4.1. 简介
(Available in 1.0)
Spring.Core程序集是Spring.NET控制反转(IoC,也叫做依赖注入)功能的基础(可参见3.1节,控制反转,其中提到了一些相关的参考资源)。Spring.Core程序集中的
IObjectFactory接口为Spring.NET提供了一种高级的配置机制,可用所有可能的存储介质保存任意对象的配置信息。同位于此程序内的
IApplicationContext接口则扩展了IObjectFactory,增加了面向方面编程(AOP)和消息资源处理(用于国际化)等功能。
简单的说,IObjectFactory接口提供了配置框架和基本功能,IApplicationContext接口又在其基础上扩展了许多企业级特性。可以说IApplicationContext是IObjectFactory的一个超集,具备IObjectFactory所有的功能与行为。
本章分为两部分,第一部分介绍IObjectFactory和IApplicationContext接口共通的基本原理,第二部分则专门讨论IApplicationContext接口所特有的功能。
Spring.NET或IoC容器的新用户可以考虑从第二十七章,IoC快速入门看起,其中有一些入门级的例子实际上是本章许多内容的示例。如果一开始理解不了,也不要着急——这些例子只是用来让读者了解Spring.NET是如何与实际开发相结合的,看完了这些例子,再回头阅读本章的内容就会容易的多了。
顶部编辑4.2. IObjectFactory,IApplicationContext和IObjectDefinition接口介绍
顶部编辑4.2.1. IObjectFactory和IApplicationContext
IObjectFactory是初始化、配置及管理对象的实际容器(按:它是所有容器的父接口)。对象间通常会相互协作,我们也可以说它们相互间具有依赖性。这些依赖性通过IObjectFactory的配置数据反映出来。(但某些依赖性从配置数据中是看不到的,比如运行时对象之间的方法调用。)
Spring.Objects.Factory.IObjectFactory接口有多个实现类。最常用的是 Spring.Objects.Factory.Xml.XmlObjectFactory。关于如何在代码中与IObjectFactory交互,请参见4.7,与IObjectFactory交互。IApplicationContext接口所定义的增强功能将在4.11,IApplicationContext简介中讨论。
前文(按:第一章)提到过,Spring.NET框架的核心原则是非侵入性。简单的说,就是应用程序的代码不需要对Spring.NET的API有任何依赖。然而,如果要通过IObjectFactory或Spring.Context.IApplicationContext接口充分利用IoC容器的功能,有时候还必须要初始化这两个接口的某个实现类。为此,可以在代码中使用new操作符来显式创建容器(在C#中,VB开发人员则使用New操作符。按:后文中,举凡涉及到含义为“管理对象的容器”而非特指接口的名称时,将原文中的IObjectFactory或IApplicationContext称为 “容器”或“IoC容器”);另一种更为简单的方式是在.NET应用程序的标准配置文件中用一个自定义节点来配置容器。一旦容器建立,应用程序代码就可能不再需要与之发生显式的交互了。
下面代码创建了XmlObjectFactory类的一个实例,XmlObjectFactory是IObjectFactory的实现类之一。我们假定在objects.xml文件中定义了要装配(按:装配的概念见后文)和发布的服务对象。将该文件的信息传递给XmlObjectFactory的构造器,即可创建一个容器,参见如下代码:
[C#]
IResource input = new FileSystemResource ("objects.xml");
IObjectFactory factory = new XmlObjectFactory(input);
代码中使用了Spring.NET的
IResource接口。IResource能以简单统一的方式访问许多可用System.IO.Stream表示的IO资源。在第六章, IResource接口中将对IResource接口展开讨论。这些IO资源一般是独立的文件或者URL,但也可以是.NET程序集的内嵌资源。通过IResource接口,可以用简单的URI格式来描述资源的位置,比如可用file://object.xml来表示一个文件。此外,IResource也支持很多其它协议,如 http等。
前文提到IApplicationContext是IObjectFactory的超集,我们一般都会用IApplicationContext来作为容器。在创建容器时可以像上例一样用IResource实例化IApplicationContext接口的任何一个实现类。另外, IApplicationContext支持用多个配置文件创建容器(按:此处的配置文件是指包括了Spring.NET对象定义的XML文件,而非特指.config文件,下同):
IApplicationContext context = new XmlApplicationContext(
"file://objects.xml",
"assembly://MyAssembly/MyProject/objects-dal-layer.xml");
// of course, an IApplicationContext is also an IObjectFactory...
IObjectFactory factory = (IObjectFactory) context;
下面是引用.NET程序集内嵌资源时的URI语法:assembly://<AssemblyName>/<NameSpace>/<ResourceName>
注意:
若要在VS中创建一个内嵌的资源,必须在文件属性编辑器中将xml文件的Build Action设为Embedded Resource。并且,如果自上次成功建立项目之后,该属性的变更是本次所做的唯一更改,则需要显式的重新生成项目。如果使用NAnt建立项目,需要在 csc任务中添加一个<resources>节点。可参见Spring.Core.Tests项目中的build文件,该项目随 Spring.NET一起发布。
更好的创建方式是在标准.NET应用程序配置文件中(App.config或Web.config)添加自定义配置节点。以下的XML节点可以创建与前例相同的容器:
<spring>
<context type="Spring.Context.Support.XmlApplicationContext, Spring.Core">
<resource uri="file://objects.xml"/>
<resource uri="assembly://MyAssembly/MyProject/objects-dal-layer.xml"/>
</context>
</spring>
<context>节点的type属性是可选的,在Windows应用中,其默认值就是Spring.Context.Support.XmlApplicationContext,所以下面的配置和上面完全相同:
<spring>
<context>
<resource uri="file://objects.xml"/>
<resource uri="assembly://MyAssembly/MyProject/objects-dal-layer.xml"/>
</context>
</spring>
spring和context节点的名称不是任意的,必须是"spring"和"context",Spring.NET本身将 "spring/context"作为字符串常量定义在了AbstractApplicationContext类中以表示上下文的节点名称。若要引用由以上配置创建的容器,可使用下面的代码:
IApplicationContext ctx = ContextRegistry.GetContext();
ContextRegistry类既可用来初始化应用程序上下文,也可用来以服务定位器风格对容器中的对象进行访问(4.16节,服务定位器访问会详细讨论有关服务定位器访问的内容)。注意,使这一切成为可能的是Spring.Context.Support.ContextHandler类,该类实现了FCL的IConfigurationSectionHandler接口。必须在.NET配置文件的<configSections> 节点中注册这个类,如下所示:
<configSections>
<sectionGroup name="spring">
<section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core"/>
</sectionGroup>
</configSections>
注册了这个节点处理器后,配置文件中的<spring>节点才能起作用。
在某些情况下,用户不需要以任何方式显式创建容器,Spring.NET可以自行创建。例如,Spring.NET中的Spring.Web模块可以将 IApplicationContext作为Web应用程序正常启动进程的一部分进行自动装载。目前正在研究如何为WinForms应用程序提供类似的支持。(按:请参考19.2.1节,以了解Spring.Web是如何自动创建容器的。)
关于如何通过编程方式管理IObjectFactory,稍后再讲。下面我们先讨论IObjectFactory接口所使用的对象配置格式。(按:凡适用于IObjectFactory的内容,必定也适用于IApplicationContext)
基本上,IObjectFactory的配置信息由一个或多个对象定义构成。在基于XML的工厂中,这些对象定义表现为一个或多个< object>子节点,它们的父节点必须是<objects>(按:objects节点的xmlns元素是必需的,必须根据不同的应用添加不同的命名空间,请留意各个章节中的相关内容。
<objects xmlns="http://www.springframework.net"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.net
http://www.springframework.net/xsd/spring-objects.xsd">
<object id="..." type="...">
...
</object>
<object id="...." type="...">
...
</object>
...
</objects>
随Spring.NET一起发布的schema文档可以简化对XML对象定义的验证过程。该文档是完全开放的(参见附录A,Spring.NET的spring-objects.xsd)。目前,除了验证XML文档外,该文档还有一个用途,就是在具备XSD感知能力的编辑器(比如VS.NET)内进行代码提示。可参考第二十四章,与Visual Studio.NET集成。在Spring.NET的网站上可以下载到spring-objects.xsd的最新版本。
XML对象定义也可以放在.NET的标准应用程序配置文件中。此时也需要为<objects>节点预先注册相应的节点处理器,类型为 Spring.Context.Support.DefaultSectionHandler。然后,就可以在.NET的.config文件中为一或多个容器配置对象定义了,如下所示:
<configuration>
<configSections>
<sectionGroup name="spring">
<section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core"/>
<section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
</sectionGroup>
</configSections>
<spring>
<context>
<resource uri="config://spring/objects"/>
</context>
<objects xmlns="http://www.springframework.net">
...
</objects>
</spring>
</configuration>
4.13.1,上下文嵌套和4.15,从其它文件中导入对象定义会讨论创建配置文件时可用的其它节点。
我们也可以在配置文件中为IApplicationContext注册自定义的资源处理器、schema解析器、类型转换器和类型别名等等。在4.12,IApplicationContext配置一节中将讨论这些内容。
顶部
编辑4.2.2.对象定义
容器所管理的对象由对象定义来配置,一个对象定义包含以下信息:
- 对象类型,即所定义对象的实际类型。
- 对象行为,用来规定对象在IoC容器中的行为(例如,是否布署为singleton,自动装配的模式,依赖检查的模式,初始化和销毁方法等)。
- 对象创建后要设置的属性值。例如,一个线程池管理对象的可用线程数,或者用来创建线程池的类型信息,* 对象所需要的其它对象,例如一个对象的协作对象(同样可通过属性或构造器设置)。这些对象也可以叫做依赖项。
上面提到了用属性和构造器参数来设置依赖项。Spring.NET支持两种类型的IoC:类型2和类型3(分别是构造器参数注入和属性注入)。也就是说,当一个对象被IoC容器创建时,既可以使用常规的属性设值方法为属性设值,也可以直接向构造器传递参数来为属性赋值。(按:对.NET来说,“属性注入” 似乎比“设值方法注入”更贴切)
上述概念直接对应对象定义中的一系列xml子节点,这些节点及相关的章节如下表所示:
表4.1 对象定义内容| 内容 | 详细信息 |
|---|
| 对象类型 | 4.2.3,对象的创建 |
| id和name | 4.2.5,对象标识符(id和name) |
| singleton或prototype | 4.2.6,Singleton和Prototype |
| 对象属性 | 4.3.1,设置对象的属性和协作对象 |
| 构造器参数 | 4.3.1,设置对象的属性和协作对象 |
| 自动装配模式 | 4.3.8,自动装配协作对象 |
| 依赖检查模式 | 4.3.9,检查依赖项 |
| 初始化方法 | 4.5.1,生命周期接口 |
| 销毁(destruction)方法 | 4.5.1,生命周期接口 |
顶部
编辑4.2.3.对象的创建
对象定义会包含对象的类型信息(也有例外,参见4.2.3.3,通过实例工厂方法创建对象和4.6,抽象及子对象定义)。多数情况下,容器会根据对象定义中的type属性值去直接调用相应类型的某个构造器。另外,容器也可以调用工厂方法来创建对象,这时type属性的值就应该是包含工厂方法的类型(按:而不是要创建的类型,但通过该对象定义的名称获取的则是由工厂方法所创建的对象)。工厂方法的产品对象可以是工厂方法所在的类型,也可以是其它类型(按:很多情况下工厂方法位于单独的类型中),这无关紧要。
顶部编辑4.2.3.1.通过构造器创建对象
使用构造器创建对象时,并不要求对象必须是某种特定的类型,也不需要了解它的实现方式(按:也就是说,类型不必去实现某个接口或扩展某个基类以求和 Spring.NET兼容,任何对象都可以布署在容器中)。只要指明对象类型(以及它所在的程序集名称)就可以了。不过,根据不同IoC容器的要求,可能需要为类型(显式的)定义默认构造器(即无参的构造器)。(按:由于.NET只会为没有构造器的类型自动添加默认构造器,所以Spring.NET允许类型不定义任何构造器;但如果在定义了含参构造器后仍需使用无参构造器,则必须进行显式定义。)
XmlObjectFactory类实现了IObjectFactory接口,它可以处理XML文件中的对象定义,例如:
<object id="exampleObject" type="Examples.ExampleObject, ExamplesLibrary"/>
这个节点定义了一个名为exampleObject的对象,它的类型是位于ExamplesLibrary程序集中的 Examples.ExampleObject类。请特别留心一下type属性的格式:类型的全名,然后是一个逗号,最后是类型所在的程序集名称。在上面的例子中,ExampleObject类定义在Examples命名空间,且位于ExamplesLibrary程序集中。
type属性值必须包含类型所在的程序集名称。另外,如果需要确保Spring.NET能按照预期的类型创建对象,则推荐使用程序集的强命名。不过,一般只有在用到GAC中的程序集时,才需要使用强命名。(按,例如,type="System.Windows.Forms.Form, System.Windows.Forms, Version=2.0.0.0, Culture=neutral,PublicKeyToken=b77a5c561934e089")
如果需要为嵌套类型创建对象,可以使用+号。例如,如果在类型Examples.ExampleObject嵌套定义了类型Person,就可以用下面的方式创建对象定义:
<object id="exampleObject" type="Examples.ExampleObject+Person, ExamplesLibrary"/>
如果应用程序能够以标准的程序集探测机制访问程序集(例如ASP.NET中的bin文件夹),那么type属性的值只需包括类型全名即可。这样,当程序集改变后,不需要去修改每个对象定义的type属性(主要是改些版本号等等),Spring.NET就会自动使用最新的程序集。
顶部编辑4.2.3.2.通过静态工厂方法创建对象
在使用静态工厂方法创建对象时,除了要将对象定义的type属性设为包含静态工厂方法的类型外,还要设置一个名为factory-method的属性来指定静态工厂方法的名称。Spring.NET会调用这个方法(稍后会看到,静态工厂方法还可以包含一个可选的参数表)来创建对象,结果和通过构造器创建对象是一样的。在实际项目中,可以用这种方法去调用原有代码中的静态工厂。
下面的对象就是通过静态工厂方法创建的。注意:对象定义中的type并非是要创建的对象的类型,而是包含了工厂方法的类型;同时,CreateInstance必须是静态方法。
<object id="exampleObject"
type="Examples.ExampleObjectFactory, ExamplesLibrary"
factory-method="CreateInstance"/>
稍后会简单的讨论如何为工厂方法配置(可选的)参数,以及如何设置产品对象的属性。(按:见4.3.1,设置对象的属性和协作对象一节的最后一部分)
顶部
编辑4.2.3.3.通过实例工厂方法创建对象
通过实例工厂方法创建对象与通过静态工厂方法创建对象的配置方式类似。此时,实例工厂方法所在的对象必须也要配置在同一容器(或父容器)中。
如果要通过实例工厂方法创建对象,对象定义就不能包含type属性,而要用factory-object属性引用工厂方法所在的对象;注意,该属性值必须是包含工厂方法的对象的名称,且该对象必须定义在当前容器或父容器中。工厂方法的方法名则通过factory-method属性指定。
请看下面的例子:
<!-- the factory object, which contains an instance method called 'CreateInstance' -->
<object id="exampleFactory" type="..."/>
<!-- the object that is to be created by the factory object -->
<object id="exampleObject"
factory-method="CreateInstance"
factory-object="exampleFactory"/>
虽然我们还没有讲过依赖注入机制,但请记住,为对象定义设置factory-object属性这一行为就是依赖注入。工厂对象本身也是由容器管理并配置的对象。
顶部
编辑4.2.4.泛型类的对象创建
泛型对象的创建方法和普通对象是一样的。
顶部
编辑4.2.4.1.通过构造器创建泛型对象
下面是一个泛型类的代码:
namespace GenericsPlay
{
public class FilterableList<T>
{
private List<T> list;
private String name;
public List<T> Contents
{
get { return list; }
set { list = value; }
}
public String Name
{
get { return name; }
set { name = value; }
}
public List<T> ApplyFilter(string filterExpression)
{
/// should really apply filter to list ;)
return new List<T>();
}
}
}
下面是该类的对象定义:
<object id="myFilteredIntList" type="GenericsPlay.FilterableList<int>, GenericsPlay">
<property name="Name" value="My Integer List"/>
</object>
在为泛型类对象指定type属性的时候要注意:第一,左尖括号<要替换成字符串“<”,因为在XML中左尖括号会被认为是小于号。从可读性来讲,我们都知道这并不是理想的方式。第二,type参数值中不能包含程序集的名称,因为程序集名称要求和类型全名用逗号隔开,而在这里逗号已经被用来分隔泛型类的类型参数了。将来可能会用其它字符代替这两个符号,但目前还没找到更具可读性的方案。若要提高可读性,建议使用类型别名,如下所示:
<typeAliases>
<alias name="GenericDictionary" type=" System.Collections.Generic.Dictionary<,>" />
<alias name="myDictionary" type="System.Collections.Generic.Dictionary<int,string>" />
</typeAliases>
然后,下面的对象定义:
<object id="myGenericObject"
type="GenericsPlay.ExampleGenericObject<System.Collections.Generic.Dictionary<int , string>>, GenericsPlay" />
就可以缩短为:
<object id="myOtherGenericObject"
type="GenericsPlay.ExampleGenericObject<GenericDictionary<int , string>>, GenericsPlay" />
或者更短:
<object id="myOtherOtherGenericObject"
type="GenericsPlay.ExampleGenericObject<MyIntStringDictionary>, GenericsPlay" />
关于类型别名,可以参考4.12,配置IApplicationContext 。
顶部
编辑4.2.4.2.通过静态工厂方法创建泛型类
请看一个例子。下面是一个泛型类:
public class TestGenericObject<T, U>
{
public TestGenericObject()
{
}
private IList<T> someGenericList = new List<T>();
private IDictionary<string, U> someStringKeyedDictionary =
new Dictionary<string, U>();
public IList<T> SomeGenericList
{
get { return someGenericList; }
set { someGenericList = value; }
}
public IDictionary<string, U> SomeStringKeyedDictionary
{
get { return someStringKeyedDictionary; }
set { someStringKeyedDictionary = value; }
}
}
对应的工厂类为:
public class TestGenericObjectFactory
{
public static TestGenericObject<V, W> StaticCreateInstance<V, W>()
{
return new TestGenericObject<V, W>();
}
public TestGenericObject<V, W> CreateInstance<V, W>()
{
return new TestGenericObject<V, W>();
}
}
使用静态工厂方法创建TestGenericObject对象的对象定义如下,其中V是一个整型List,W是整型:
<object id="myTestGenericObject"
type="GenericsPlay.TestGenericObjectFactory, GenericsPlay"
factory-method="StaticCreateInstance<System.Collections.Generic.List<int>,int>"/>
StaticCreateInstance方法的职责就是创建名为“myTestGenericObject”的对象。
顶部
编辑4.2.4.3.通过实例工厂方法创建泛型对象
以下是使用实例工厂方法创建泛型对象的对象定义:
<object id="exampleFactory" type="GenericsPlay.TestGenericObject<int,string>, GenericsPlay"/>
<object id="anotherTestGenericObject"
factory-object="exampleFactory"
factory-method="CreateInstance<System.Collections.Generic.List<int>,int>"/>
这样就可以创建一个类型为TestGenericObject<List<int>,int>的对象。
顶部
编辑4.2.5. 对象标识符(id和name)
每个对象都有一个或多个id(就是所谓的标识符或名称,这些术语的含义是相同的)。id在容器中应该是唯一的。一个对象通常只有一个标识符,如果指定了一个以上名称,其余的就会被认为是别名。
在XML对象定义中,用id或者name属性来定义对象的标识符。每个对象都需要用id或name属性定义至少一个标识符。id属性允许为对象定义指定一个唯一的id,因为在Spring.NET的shcema文档中,id被标识为XML元素的ID属性,XML解析器可以在其它元素引用它的时候进行验证,在配置对象标识符时,应该优先使用id属性。但是,id属性值不能包含任何XML ID不允许使用的字符。如果一定要使用这些字符,应该使用name属性,在name属性中也可以通过逗号或分号为对象指定一个或多个别名。
顶部编辑4.2.6.Singleton和Prototype
对象可以通过两种模式布署:singleton和非singleton(或者叫做prototype,只是用在这里不是很合适)。当一个对象被定义为 singleton时,容器中就只会有一个共享的实例,任何时候通过id或别名请求该对象都会返回这个共享实例的引用(也就是说这个对象只会被创建一次)。
当使用非singleton,或者说原型模式布署时,每次请求对象都会创建新的实例。在某些场合,如果需要为每个用户返回单独的用户对象或其它对象,非singlton布署模式就比较理想。
如果没有显式指定,对象的布署模式默认为singleton。注意非singleton(原型)模式会使Spring.NET在每次请求对象时都创建新的实例,这也许并非是我们预期的行为。所以,除非绝对需要,否则不要使用原型模式。
注意:
当使用原型模式布署对象时,对象生命周期的改变(按:对Spring.NET来说)就没那么明显了。Spring.NET无法通过对象定义来完全管理非 singleton(原型)对象的生命周期,因为这些对象一经创建就交由客户代码维护,容器不可能再继续跟踪它们。可以将Spring.NET的非 singleton(原型)创建方式看做是new操作符的替代物。通过这种方式创建的任何对象,其生命周期都只能由客户代码管理。关于容器内对象的生命周期,可参见4.5.1,生命周期接口 。
下面的两个对象分别用singleton和prototype模式布署。对象exampleObject是一个原型对象,每次客户代码请求时,容器都会创建一个新的实例;而对象yetAnotherExample是一个singleton对象,只会创建一次,每次请求返回的都是同一个实例。
<object id="exampleObject" type="Examples.ExampleObject, ExamplesLibrary"
singleton="false"/>
<object name="yetAnotherExample" type="Examples.ExampleObjectTwo, ExamplesLibrary"
singleton="true"/>
可使用<object>节点的lazy-init属性控制singleton对象的创建时机。singleton设计模式经常会涉及到对象的惰性创建,也就是说只在某对象第一次使用时去创建它。默认情况下,singleton对象的lazy-init属性值为false,IoC容器在初始化的时候就会创建它们。将该属性设置为true,就可将对象的创建推迟到第一次使用前,这里,“第一次使用”可能是客户代码的请求,也可能是容器在处理对象的依赖注入。
顶部编辑4.3.属性,协作对象,自动装配和依赖检查
顶部
编辑4.3.1.设置对象的属性和协作对象
控制反转也称为依赖注入。其基本理论是对象应该只通过构造器参数、工厂方法参数或者属性来定义自己的依赖项(即和它一起工作的其它对象)。这样,在创建对象时,将这些依赖项注入到对象内部就是容器要做的工作了。这就从根本上反转了对象在(通过new操作符)创建时自行初始化或者(通过类似于服务定位器模式的方法)查找依赖项的行为(所以叫控制反转)。在此我们不打算花太多的篇幅去讲依赖注入的优点,因为这是很明显的。由于对象不需要自己查找依赖项,甚至不需要知道依赖项的位置或具体类型,只需要由容器将依赖项注入给它,这就很容易让代码变得更加简洁,并且做到高度解耦。
前文提到过,控制反转(依赖注入)主要有两种方式:
- 属性注入(按:即设值方法注入,setter-based dependency injection,但对于.NET来说,恐怕称为属性注入更为合适):在创建对象以后,通过(调用)属性(的设值方法)将依赖项注入。 Spring.NET建议使用属性注入,因为构造器的参数如果太多的话,会使类的代码和对象定义变得很臃肿,特别是在某些属性为可选的时候(即不一定非要注入)。
- 构造器注入(constructor-based dependency injection):调用含参构造器,在对象创建时将依赖项通过构造器参数注入。虽然Spring.NET建议尽量使用属性注入,但也完全支持构造器注入,因为有时候我们只能使用具有含参构造器且没有定义属性的旧类型。另外,对比较简单的对象来说,某些人可能更喜欢使用构造器注入,以确保对象在创建以后马上处于有效的状态。
IObjectFactory接口支持这两种注入方式。在容器中,我们通过XML对象定义来配置依赖项,容器会在必要时使用类型转换器进行转型。
一般来说,在处理对象依赖项的时候,Spring.NET会做以下几件事情:
- 根据包含对象定义的配置信息来创建和初始化容器。大多数用户都会使用支持XML配置文件的IObjectFactory或IApplicationContext实现类作为容器。
- 每个对象的依赖项都通过属性或构造器配置在对象定义中。当对象被创建时,容器会将依赖项注入给对象。
- 属性或构造器参数既可以设置为实际的值,也可以设为同一容器中其它对象的引用。如果用IApplicationContext作为容器,也可以引用父容器中的对象。
- 配置给属性或构造器参数的值必须能够转型为属性或参数的实际类型。默认情况下,Spring.NET可以将字符串值转型为任意基元类型,如int, long,bool等。另外,基于XML的容器也可以使用XML节点配置IList、IDictionary和Set等集合类型的值, Spring.NET会使用TypeConverter将字符串值转换为其它任意类型。可参考5.3,类型转换以了解TypeConverter的详细信息,以及使用Spring.NET自动转换自定义类型的方法。(按:Set是Spring.NET提供的集合类型)
- 在容器本身被创建的时候,Spring.NET会验证容器内每个对象的配置信息,并验证该对象的依赖项也是有效的(即:对象引用的对象也要定义在同一 IObjectFacotry中,对于IApplicationContext,也可以定义在父容器中)。但是属性的赋值仅在对象被创建时才会发生。对于一个以singleton模式布署、非惰性创建的对象(比如定义在IApplicationContext中的singleton对象)来说,对象的创建就发生在容器本身被创建的时候;否则(按:惰性创建,或非singleton时)就发生在对象被请求的时候。当一个对象被创建时,可能会导致其它一系列对象同时被创建,因为对象的依赖项,以及依赖项的依赖项此时都需要被创建并赋值。
- 一般情况下Spring.NET可以把这些事情做的很好。在载入容器时,Spring.NET会处理配置中出现的问题,比如引用了一个不存在的对象定义或发生循环依赖等等。而属性的设置和依赖项的解析(即在需要时创建所有依赖项的行为)则会推迟到对象实际被创建时才会执行。也就是说,如果某个对象或其依赖项无法正确创建,那么即便是容器可以正常创建,在请求这个对象时也会抛出异常。例如,如果遗漏了本该赋值的属性,或者所赋予的属性值无效时,会抛出异常而导致该对象无法正确创建。这也是IApplicationContext使用非惰性singleton模式作为默认布署方式的原因。所有对象都在实际需要前被创建,这种时间和空间上的开销可以保证IApplicationContext在创建时及早发现问题。如果愿意,可随时覆盖这一默认行为,将任意对象设置为惰性创建。
下面是一些XML对象定义的例子...
首先看一个使用IoC容器进行属性注入的例子。下面是XML对象定义;随后是相关类型的代码。
<object id="exampleObject" type="Examples.ExampleObject, ExamplesLibrary">
<property name="objectOne" ref="anotherExampleObject"/>
<property name="objectTwo" ref="yetAnotherObject"/>
<property name="IntegerProperty" value="1"/>
</object>
<object id="anotherExampleObject" type="Examples.AnotherObject, ExamplesLibrary"/>
<object id="yetAnotherObject" type="Examples.YetAnotherObject, ExamplesLibrary"/>
(按:原文的许多例子中,XML对象定义所使用的属性名以小写字母开头,这是由于IoC容器这部分文档的很多内容都直接来自Spring.Java。在译文中,按照C#的命名习惯将属性名或方法名的第一个字母改成了大写。)
[C#]
public class ExampleObject
{
private AnotherObject objectOne;
private YetAnotherObject objectTwo;
private int i;
public AnotherObject ObjectOne
{
set { this.objectOne = value; }
}
public YetAnotherObject ObjectTwo
{
set { this.objectTwo = value; }
}
public int IntegerProperty
{
set { this.i = value; }
}
}
下面的例子使用IoC容器进行第三类IoC(即构造器参数注入)。首先是XML对象定义,随后是类型声明:
<object id="exampleObject" type="Examples.ExampleObject, ExamplesLibrary">
<constructor-arg name="objectOne" ref="anotherExampleObject"/>
<constructor-arg name="objectTwo" ref="yetAnotherObject"/>
<constructor-arg name="IntegerProperty" value="1"/>
</object>
<object id="anotherExampleObject" type="Examples.AnotherObject, ExamplesLibrary"/>
<object id="yetAnotherObject" type="Examples.YetAnotherObject, ExamplesLibrary"/>
[Visual Basic.NET]
Public Class ExampleObject
Private myObjectOne As AnotherObject
Private myObjectTwo As YetAnotherObject
Private i As Integer
Public Sub New (
anotherObject as AnotherObject,
yetAnotherObject as YetAnotherObject,
i as Integer)
myObjectOne = anotherObject
myObjectTwo = yetAnotherObject
Me.i = i
End Sub
End Class
在本例中,配置在XML对象定义中的构造器参数会在创建对象时传递给ExampleObject类的构造器。
注意属性注入(类型2)和构造器注入(类型3)并不排斥,完全可以在同一对象定义中同时使用,如下所示:
<object id="exampleObject" type="Examples.MixedIocObject, ExamplesLibrary">
<constructor-arg name="objectOne" ref="anotherExampleObject"/>
<property name="objectTwo" ref="yetAnotherObject"/>
<property name="IntegerProperty" value="1"/>
</object>
<object id="anotherExampleObject" type="Examples.AnotherObject, ExamplesLibrary"/>
<object id="yetAnotherObject" type="Examples.YetAnotherObject, ExamplesLibrary"/>
[C#]
public class MixedIocObject
{
private AnotherObject objectOne;
private YetAnotherObject objectTwo;
private int i;
public MixedIocObject (AnotherObject obj)
{
this.objectOne = obj;
}
public YetAnotherObject ObjectTwo
{
set { this.objectTwo = value; }
}
public int IntegerProperty
{
set { this.i = value; }
}
}
现在,考虑一种构造器的替代方案。下面例子中,Spring.NET使用静态工厂方法来创建对象:
<object id="exampleObject" type="Examples.ExampleFactoryMethodObject, ExamplesLibrary"
factory-method="CreateInstance">
<constructor-arg name="objectOne" ref="anotherExampleObject"/>
<constructor-arg name="objectTwo" ref="yetAnotherObject"/>
<constructor-arg name="intProp" value="1"/>
</object>
<object id="anotherExampleObject" type="Examples.AnotherObject, ExamplesLibrary"/>
<object id="yetAnotherObject" type="Examples.YetAnotherObject, ExamplesLibrary"/>
[C#]
public class ExampleFactoryMethodObject
{
private AnotherObject objectOne;
private YetAnotherObject objectTwo;
private int i;
// a private constructor
private ExampleFactoryMethodObject()
{
}
public static ExampleFactoryMethodObject CreateInstance(AnotherObject objectOne,
YetAnotherObject objectTwo,
int intProp)
{
ExampleFactoryMethodObject fmo = new ExampleFactoryMethodObject();
fmo.AnotherObject = objectOne;
fmo.YetAnotherObject = objectTwo;
fmo.IntegerProperty = intProp;
return fmo;
}
// Property definitions
}
注意在对象定义中,静态工厂方法所需要的参数也通过constructor-arg节点配置,和直接使用构造器是一样的。要注意工厂方法产品对象的类型不需要一定是包含工厂方法的类型。实例工厂方法的配置方法和静态工厂方法基本相同(除了要用factory-object属性代替type属性外),所以不再赘述。
顶部编辑4.3.2.构造器参数解析(按:构造子决议)
(按:构造器参数解析是指将对象定义中的constructor-arg节点与构造器的实际参数相关联)。构造器参数的解析可通过匹配参数类型来完成。当 constructor-arg节点引用其它对象时,由于容器可以获知被引用对象的类型,所以能据此判断该节点对应构造器的哪个参数。但如果构造器参数为简单类型,就只能在constructor-arg节点中直接用文本来配置参数值,例如<value>1</value>,此时 Spring.NET无法确定文本值的类型,也就无法根据类型判断constructor-arg节点和具体参数的关联关系。请先看下面的类定义,后面两节中都会用到这个类:
using System;
namespace SimpleApp
{
public class ExampleObject
{
private int years; //No. of years to the calculate the Ultimate Answer
private string ultimateAnswer; //The Answer to Life, the Universe, and Everything
public ExampleObject(int years, string ultimateAnswer)
{
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
public string UltimateAnswer
{
get { return this.ultimateAnswer; }
}
public int Years
{
get { return this.years; }
}
}
}
顶部
编辑4.3.2.1.根据参数类型匹配构造器参数
在配置上一节类型的对象时,可以在constructor-arg节点中用type属性显式的为参数指定类型。如下:
<object name="exampleObject" type="SimpleApp.ExampleObject, SimpleApp">
<constructor-arg type="int" value="7500000"/>
<constructor-arg type="string" value="42"/>
</object>
在创建对象时,容器会根据constructor-arg节点的type属性值将value属性值传递给类型相同的参数,type属性值可以是 System中的类型名,例如System.Int32,也可以使用别名,见下表..
表4.2.类型别名| 类型 | 别名 | 数组别名 |
|---|
| System.Char | char, Char | char[], Char() |
| System.Int16 | short, Short | short[], Short() |
| System.Int32 | int, Integer | int[], Integer() |
| System.Int64 | long, Long | long[], Long() |
| System.UInt16 | ushort | ushort[] |
| System.UInt32 | uint | uint[] |
| System.UInt64 | ulong | ulong[] |
| System.Float | float, Single | float[], Single() |
| System.Double | double, Double | double[], Double() |
| System.Date | date, Date | date[], Date() |
| System.Decimal | decimal, Decimal | ecimal[], Decimal() |
| System.Bool | bool, Boolean | bool[], Boolean() |
| System.String | string, String | string[], String() |
顶部
编辑4.3.2.2.根据参数索引匹配构造器参数
使用索引匹配构造器参数要比仅使用类型进行匹配精确的多。可以显式的在constructor-arg节点中用index属性来指定参数的索引,如下:
<object name="exampleObject" type="SimpleApp.ExampleObject, SimpleApp">
<constructor-arg index="0" value="7500000"/>
<constructor-arg index="1" value="42"/>
</object>
构造器参数索引匹配适用于构造器具有多个简单类型参数的情况,也可以解决构造器具有多个相同类型参数的问题。注意索引是从0开始的。
顶部
编辑4.3.2.3.根据名称匹配构造器参数
最精确的方式是使用构造器参数的名称来匹配;可以在constructor-arg节点中使用name属性来指定参数的名称:
<object name="exampleObject" type="SimpleApp.ExampleObject, SimpleApp">
<constructor-arg name="years" value="7500000"/>
<constructor-arg name="ultimateAnswer" value="42"/>
</object>
顶部
编辑4.3.3.详细讨论对象属性和构造器参数
前面提到过,属性和构造器参数也可以引用其它对象(协作对象)。Spring.Objects.Factory.Xml命名空间下的 XMLObjectFactory类(按:及其它基于XML的容器类)可以通过property和constructor-arg节点的子元素来引用协作对象。
在对象定义中,用property和constructor-arg的value属性来为属性或构造器的参数设值。前文已经讨论过,Spring.NET使用TypeConverter将字符串值转换为属性或者参数的实际类型。Spring.Objects.TypeConverters命名空间增强了.NET基础类库提供的标准TypeConverter类。
下面的例子用到了System.Data.SqlClient命名空间下的SqlConnection类。该类有一个公共属性用于设置连接字符串。同其它类型一样,很容易在Spring.NET对象工厂中配置它的对象:
<objects xmlns="http://www.springframework.net">
<object id="myConnection" type="System.Data.SqlClient.SqlConnection">
<!-- results in a call to the setter of the ConnectionString property -->
<property
name="ConnectionString"
value="Integrated Security=SSPI;database=northwind;server=mySQLServer"/>
</object>
</objects>
顶部
编辑4.3.3.1.设置空值
<null/>节点可用于设置空值。Spring.NET将对象定义中空的value节点值作为空字符串处理。下面的对象定义可说明这一行为:
<object type="Examples.ExampleObject,
ExamplesLibrary"> <property
name="email"><value></value></property>
<!-- equivalent, using value attribute as opposed to nested -->
<value/> element...
<property name="email" value=""/>
</object>
其结果是将Email属性值设为"",和下面的代码是一样的:
exampleObject.Email = "";
如果需要赋空值,可以用<null/>节点,见下例:
<object type="Examples.ExampleObject, ExamplesLibrary">
<property name="email">
<null/>
</property>
</object>
结果是将Email属性设为null,和下面的代码是一样的:
exampleObject.Email = null;
顶部编辑4.3.3.2.设置集合值
IList,NameValueCollection,Set,IDictionary等类型的属性,可以用list,name-values和set,dictionary节点来设置。
<objects xmlns="http://www.springframework.net">
<object id="moreComplexObject" type="Example.ComplexObject">
<!--
results in a call to the setter of the SomeList (System.Collections.IList) property
-->
<property name="SomeList">
<list>
<value>a list element followed by a reference</value>
<ref object="myConnection"/>
</list>
</property>
<!--
results in a call to the setter of the SomeDictionary (System.Collections.IDictionary) property
-->
<property name="SomeDictionary">
<dictionary>
<entry key="a string => string entry" value="just some string"/>
<entry key-ref="myKeyObject" value-ref="myConnection"/>
</dictionary>
</property>
<!--
results in a call to the setter of the SomeNameValue (System.Collections.NameValueCollection) property
-->
<property name="SomeNameValue">
<name-values>
<add key="HarryPotter" value="The magic property"/>
<add key="JerrySeinfeld" value="The funny (to Americans) property"/>
</name-values>
</property>
<!--
results in a call to the setter of the SomeSet (Spring.Collections.ISet) property
-->
<property name="someSet">
<set>
<value>just some string</value>
<ref object="myConnection"/>
</set>
</property>
</object>
</objects>
.NET基础类库中有很多类都定义了只读的集合类型属性(按:指没有set方法的属性),在“设置”这类属性时,Spring.NET会先用get方法读出集合的引用,然后向集合中添加元素。也就是说,此时“设置属性”实际上是向此类属性后台的集合中添加元素(按:注意不是将另一个集合的引用注入给该属性,因为它没有set方法。)。
注意Dictionary和Set的值可以用下面的任一子节点进行配置:
(object | ref | idref | list | set | dictionary | name-values | value | null)
在设置集合属性时,使用value和ref节点的简短格式可以避免XML配置过于冗长。请参见4.3.3.8,value和ref节点的简短格式 。
请注意,Spring.NET已经计划在
未来版本中支持对NameValueCollection类型的属性进行设置。
顶部编辑4.3.3.3.设置泛型集合的值
Spring.NET也可以为IList<T>和IDictionary<TKey,TValue>类型的属性设值。在对象定义中,用element-type属性指定IList<T>的类型参数;用key-type和value-type分别指定 IDictionary<TKey,TValue>中键和值的类型参数。容器会自动将字符串值转换为相应类型。如果使用自定义类型作为泛型的类型参数,需要注册自定义的类型转换器,可以参考4.4,类型转换一节。在.NET框架中,IList<T>和IDictionary<TKey,TValue>的实现类分别是 System.Collections.Generic.List和System.Collections.Generic.Dictionary。
下面的彩票类用来示范如何设置泛型IList的值。
public class LotteryTicket
{
List<int> list;
DateTime date;
public List<int> Numbers
{
set { list = value; }
get { return list; }
}
public DateTime Date
{
get { return date; }
set { date = value; }
}
}
该类的对象定义如下:
<object
id="MyLotteryTicket"
type="GenericsPlay.Lottery.LotteryTicket, GenericsPlay">
<property name="Numbers">
<list element-type="int">
<value>11</value>
<value>21</value>
<value>23</value>
<value>34</value>
<value>36</value>
<value>38</value>
</list>
</property>
<property name="Date" value="4/16/2006"/>
</object>
下面的类型稍微复杂一点,其中使用Spring.Expressions.IExpression接口作为IList<T>和 IDictionary<TKey,TValue>值的类型参数。Spring.Expressions.IExpression有一个相应的类型转换器,Spring.Objects.TypeConverters.ExpressionConverter,已经在Spring.NET中注册过了。
public class GenericExpressionHolder
{
private System.Collections.Generic.IList<IExpression> expressionsList;
private System.Collections.Generic.IDictionary<string, IExpression>
expressionsDictionary;
public System.Collections.Generic.IList<IExpression> ExpressionsList
{
set { this.expressionsList = value; }
}
public System.Collections.Generic.IDictionary<string,IExpression>
ExpressionsDictionary
{
set { this.expressionsDictionary = value; }
}
public IExpression this[int index]
{
get { return this.expressionsList[index]; }
}
public IExpression this[string key]
{
get { return this.expressionsDictionary[key]; }
}
}
该类的对象定义如下:
<object id="genericExpressionHolder"
type="Spring.Objects.Factory.Xml.GenericExpressionHolder, Spring.Core.Tests">
<property name="ExpressionsList">
<list element-type="Spring.Expressions.IExpression, Spring.Core">
<value>1 + 1</value>
<value>date('1856-7-9').Month</value>
<value>'Nikola Tesla'.ToUpper()</value>
<value>DateTime.Today > date('1856-7-9')</value>
</list>
</property>
<property name="ExpressionsDictionary">
<dictionary key-type="string"
value-type="Spring.Expressions.IExpression, Spring.Core">
<entry key="zero">
<value>1 + 1</value>
</entry>
<entry key="one">
<value>date('1856-7-9').Month</value>
</entry>
<entry key="two">
<value>'Nikola Tesla'.ToUpper()</value>
</entry>
<entry key="three">
<value>DateTime.Today > date('1856-7-9')</value>
</entry>
</dictionary>
</property>
</object>
顶部
编辑4.3.3.4.设置索引器属性
C#的索引器属性允许我们使用方括号[]来访问类型内部的集合字段。Spring.NET可以在对象定义中为索引器属性设值,也支持重载索引器和多参数索引器。Spring.NET使用属性表达式解析器(property expression parser)将对象定义中的文本值转换为与索引器匹配的实际类型,可参见第十章,表达式求值。下面是一个例子:
public class Person
{
private IList favoriteNames = new ArrayList();
private IDictionary properties = new Hashtable();
public Person()
{
favoriteNames.Add("p1");
favoriteNames.Add("p2");
}
public string this[int index]
{
get { return (string)favoriteNames[index]; }
set { favoriteNames[index] = value; }
}
public string this[string keyName]
{
get { return (string) properties[keyName]; }
set { properties.Add(keyName,value); }
}
}
下面的对象定义为索引器属性赋值:
<object id="person" type="Test.Objects.Person, Test.Objects">
<property name="[0]" value="Master Shake"/>
<property name="['one']" value="uno"/>
</object>
注意:在1.02版中,属性表达式解析器的使用方式与旧版不同。请看下面的解释:
老的配置方式使用下面的语法:
<object id="objectWithIndexer" type="Spring.Objects.TestObject, Spring.Core.Tests">
<property name="Item[0]" value="my string value"/>
Item是索引器的默认名称,也可以在索引器声明上应用[IndexerName("MyItemName")]特性来改变索引器的标识名称,然后就可以用MyItemName[0]配置索引器的第一个元素。
这种方法有一些限制。索引器必须是单参数且其参数类型必须可以从字符串成功转型。另外,也不支持多重索引器(按:即重载的索引器)。现在则可以用 IndexerName特性来避开这一局限。
顶部
编辑4.3.3.5.内联对象定义
在property节点内部,可以用内嵌的object节点为属性定义一个内联对象,这就相当于直接引用容器内的其它对象。内联对象不需要定义id或name(即使是定义了,Spring.NET也会将其忽略)。
<object id="outer" type="...">
<!-- Instead of using a reference to target, just use an inner object -->
<property name="Target">
<object type="ExampleApp.Person, ExampleApp">
<property name="Name" value="Tony"/>
<property name="Age" value="51"/>
</object>
</property>
</object>
顶部编辑4.3.3.6.idref节点
如果需要将某个字符串属性设置为容器中其它对象的id或name(按:注意是设置为对象id或name的字符串内容,而非引用该对象),可使用idref节点以避免错误。
<object id="theTargetObject" type="...">
</object>
<object id="theClientObject" type="...">
<property name="TargetName">
<idref object="theTargetObject"/>
</property>
</object>
在运行时,这和下面的配置结果是一样的:
<object id="theTargetObject" type="...">
</object>
<object id="theClientObject" type="...">
<property name="TargetName" value="theTargetObject"/>
</object>
前一种方法的好处在于,idref节点可使Spring.NET在布署时验证以theTargetObject为名的对象定义是否存在。在后一种定义中,对象theClientObject必须自己来进行这一验证,并且只能在容器将其创建以后进行,而此时容器可能已经运行了相当长一段时间了。
另外,如果要引用同一XML文件中的对象定义名称,可以用local属性来代替object属性,这样XML解析器可以在解析时对目标对象进行验证,但此时引用的必须是对象的id(按:即只能是对象定义的id属性值,而不能是name属性中定义的别名)。
<property name="TargetName">
<idref local="theTargetObject"/>
</property>
顶部编辑4.3.3.7.引用协作对象
<ref/>是我们要讲的property节点的最后一个子节点。ref节点用于引用容器内的其它对象(也就是所谓的协作对象)。在前面的例子中,我们曾经将SqlConnection的实例作为协作对象,用<ref object=""/>将其设置给了其它对象的属性。前文也提到过,被引用的对象叫做引用它的对象的依赖项,并且会在需要前被初始化(如果依赖项是 singleton对象,则可能已经被容器初始化了)。在XML对象定义中,共有3种方法可以引用协作对象,不同的引用方法决定了目标对象的查找与验证方式。
通过ref节点的object属性来指定目标对象是最常用的方式,object属性可以引用同一容器或父容器中的对象定义,而不管这些对象定义是否保存在同一个XML文件中。object属性的值可以是目标对象的id属性,也可以是用name属性定义的别名。
<ref object="someObject"/>
使用local属性则可以充分利用XML解析器的功能来对同一文件中的XML ID进行引用验证。local属性的值必须是目标对象的id。如果在同一文件中找不到目标对象定义,XML解析器就会报错。因此,如果目标对象定义在同一 XML文件中,使用local属性进行引用是最好的选择,这样可以及早发现错误。
<ref local="someObject"/>
ref节点的parent属性可以引用当前容器父容器中的对象,parent属性的值可以是目标对象的id,也可以是由name属性定义的别名,并且,目标对象必须位于当前容器的父容器中。比如,如果创建一个代理对象来包装父容器中的某个已知对象(代理对象可能和目标对象的名字相同),就需要从父容器中获得原始对象的引用。
<ref parent="someObject"/>
顶部编辑4.3.3.8.value和ref节点的简化格式
我们可以使用value和ref节点的简化格式来缩短XML对象定义。property、constructor-arg和entry节点都支持用value属性代替完整的value节点,如下:
<property name="MyProperty">
<value>hello</value>
</property>
<constructor-arg>
<value>hello</value>
</constructor-arg>
<entry key="myKey">
<value>hello</value>
</entry>
这等价于:
<property name="MyProperty" value="hello"/>
<constructor-arg value="hello"/>
<entry key="myKey" value="hello"/>
一般来说,在手工键入对象定义时,这些简化格式可以减少键入工作。
类似的,property和constructor-arg节点也支持用ref属性代替完整的ref节点,如下:
<property name="MyProperty">
<ref object="anotherObject"/>
</property>
<constructor-arg index="0">
<ref object="anotherObject"/>
</constructor-arg>
这等价于...
<property name="MyProperty" ref="anotherObject"/>
<constructor-arg index="0" ref="anotherObject"/>
注意
ref简化格式只等价于<ref object="xxx">,<ref local="xxx">和<ref parent="xxx">并没有相应的简化格式,必须使用完整节点。
最后,entry节点也允许用key/key-ref和value/value-ref简化词典键和值的定义。下面的定义:
<entry>
<key><ref object="MyKeyObject"/></key>
<ref object="MyValueObject"/>
</entry>
等同于:
<entry key-ref="MyKeyObject" value-ref="MyValueObject"/>
前面提到过,简化格式只等价于<ref object="xxx">,不能用来表示local或parent形式的对象引用。
顶部编辑4.3.3.9.复合属性名
在设置对象属性时,可以使用复合或嵌套的属性名,但要保证属性名路径中除最后一部分外的每一部分都不为空值,例如:
<object id="foo" type="Spring.Foo, Spring.Foo">
<property name="Bar.Baz.Name" value="Bingo"/>
</object>
顶部编辑4.3.4.方法注入
多数用户都会将容器中的大部分对象布署为singleton模式。当一个singleton对象需要和另一个singleton对象协作,或者一个非 singleton对象需要和另一个非singleson对象协作时,Spring.NET都能很好的处理它们的依赖关系。但是,如果对象的生存周期不同,就可能会产生问题。例如,假设一个singleton对象A要使用一个非singleton(原型)对象B,A中的每个方法都会用到B的新实例。由于 A是singleton对象,容器只有会创建它一次,也就是说只有一次给A的属性赋值的机会,所以不可能在每次A需要的时候都给它注入一个新的B。
有一种解决的办法有点违背控制反转原则:类A可以通过实现IObjectFactoryAware接口来获取容器的引用,并调用GetObject ("B")在每次需要的时候从容器中请求一个(新的)对象B。但这并不是一个很好的解决方案,因为客户代码此时必须要和Spring.NET发生紧耦合。
通过方法注入,我们可以用更优雅的方式解决类似的问题。
顶部编辑4.3.4.1.查询方法注入(Lookup Method Injection)
Spring.NET可以动态覆盖对象的抽象方法或虚方法,并且可以在容器内查找已命名对象,查询方法注入就利用了这些功能。被查询对象一般应该是非 singleton的(但也可以是singleton)。Spring.NET使用System.Reflecttion.Emit命名空间中的类型在运行时动态生成某个类的子类并覆盖其方法,以实现查询方法注入。
在需要进行方法注入的类中,被注入(即被覆盖)的方法必须按以下格式声明:(按:只需是一个抽象无参方法,可见性在private以上即可,方法名称没有限制。要注意的是,虽然包含抽象方法的类必须是抽象类,但是由于Spring.NET的动态子类化,使得抽象类看上去也可以被实例化,稍后请看一个例子。)
protected abstract SingleShotHelper CreateSingleShotHelper();
如果方法不是抽象的(按:但必须是虚方法),Spring.NET也可以将其覆盖。在对象定义中,可以使用object节点的lookup-method 子节点让Spring.NET在运行时覆盖抽象的CreateSingleShotHelper方法,使其返回容器中名为 singleShotHelper的对象,如下:
<!-- a stateful object deployed as a prototype (non-singleton) -->
<object id="singleShotHelper" class="..." singleton="false"/>
<!-- myobject uses singleShotHelper -->
<object id="myObject" type="...">
<lookup-method name="CreateSingleShotHelper" object="singleShotHelper"/>
<property>
...
</property>
</object>
注入后,myObject对象可随时调用自己的CreateSingleShotHelpher方法来获取一个新的SingleShotHelper实例。注意在定义singleShotHelper对象时必须要非常小心,如果确实每次都需要新的实例,一定要将其声明为非singleton模式。否则(不管使用默认设置还是显式指定singleton属性为true),每次返回的仍将是同一个SingleShotHelper对象。
注意,查询方法注入可以和构造器注入(通过向构造器提供可选的参数值)及属性注入(通过设置属性值)一起使用。
(按:请看一个例子,首先,声明需要进行方法注入的类型和被查询对象的类型:
public abstract class MyClass //注意,可以直接在配置中定义这个类的对象
{
public abstract SingleShotHelper CreateSingleShotHelper();
//或者可以是一个虚方法
//public virtual SingleShotHelper CreateSingleShotHelper()
//{
// return null;
//}
public void Process1()
{
//调用 this.CreateSingleShotHelper()
}
public void Process2()
{
//调用 this.CreateSingleShotHelper()
}
}
public class SingleShotHelper
{
}
下面是相关的对象定义:
<object id="singleShotHelper" type="Sample1.SingleShotHelper, Sample1" singleton="false"/>
<object id="myObject" type="Sample1.MyClass, Sample1">
<lookup-method name="CreateSingleShotHelper" object="singleShotHelper"/>
</object>
顶部编辑4.3.4.2.替换任意方法
这是一种较为少见的方法注入形式,可以用指定的方法替换容器内对象的任意方法。该功能略显复杂,用户可以跳过本节,等有实际需要时再来参考。
在Xml对象定义中,可以用object节点的replaced-method子节点将对象的任一方法替换为其它实现。下面的类定义了一个虚方法ComputeValue():
public class MyValueCalculator {
public virtual string ComputeValue(string input) {
// ... some real code
}
// ... some other methods
}
提供(要注入的)新方法的类必须实现Spring.Objects.Factory.Support.IMethodReplacer接口:
/// <summary>
/// Meant to be used to override the existing ComputeValue(string)
/// implementation in MyValueCalculator.
/// </summary>
public class ReplacementComputeValue : IMethodReplacer
{
public object Implement(object target, MethodInfo method, object[] arguments)
{
// get the input value, work with it, and return a computed result...
string value = (string) arguments[0];
// compute...
return result;
}
}
对象定义如下:
<object id="myValueCalculator" type="Examples.MyValueCalculator, ExampleAssembly">
<!-- arbitrary method replacement -->
<replaced-method name="ComputeValue" replacer="replacementComputeValue">
<arg-type match="String"/>
</replaced-method>
</object>
<object id="replacementComputeValue" type="Examples.ReplaceMentComputeValue, ExampleAssembly"/>
replaced-method节点可以用一或多个arg-type子节点来标识被替换方法的签名,但注意只有当目标方法有重载时arg-type才是必需的。为方便起见,arg-type的类型值可用类型全名的部分字符来表示,比如说,下面的字符都能表示System.String类型:
System.String
String
Str
因为通常仅靠参数的个数就足以区分不同的方法重载,所以用最简短的类型字符串来匹配方法参数可以节省不少键入工作。
顶部编辑4.3.5.引用其他对象或类型的成员
本节详细讨论如何使用其它对象或类型的成员来进行依赖注入。这种情况很普遍,特别是在使用某些不能(或不希望)按Spring.NET的约定来修改的现有类型时。比如说,某个类的构造器参数值需要从数据库的记录中计算出来,并且必须由另外一个类型处理后才能得出确切的值。 MethodInvokingFactoryObject类可以处理这些问题,它允许把任意方法的返回值注入给对象的构造器参数或属性。类似的, PropertyRetrievingFactoryObject类和FieldRetrievingFactoryObject类分别允许用其它类型的属性或字段进行注入。这些类都实现了IFactoryObject接口,以表明自己的对象是一个工厂对象,通过工厂对象的id获取的不是工厂对象本身,而是其产品对象。在4.5.3,IFactoryObject接口中将讨论工厂对象。
顶部
编辑4.3.5.1.使用对象或类的属性值进行注入
PropertyRetrievingFactoryObject是IFactoryObject接口的实现类,用于将其它对象或类型的属性值注入给对象的属性或构造器参数。该类可以从对象或类型中(指静态属性)获取任何公有属性的值。
如果要用PropertyRetrievingFactoryObject获取实例属性的值,需要为其指定目标对象及属性名,目标对象可以是任意其它对象,甚至可以是内联对象。如果要获取静态属性,则需要指定目标类型的全名和属性名。
然后,通过PropertyRetrievingFactoryObject对象的id即可获取目标对象或类型的属性值,获取的结果可直接注入给对象的属性或构造器参数。注意,实例属性和静态属性都支持嵌套。
下面使用PropertyRetrievingFactoryObject获取目标对象的属性值。在此目标对象的属性值是个内联的对象定义,所以在 PropertyRetrievingFactoryObject对象定义的TargetProperty属性中,使用了嵌套式的属性路径。
<object name="person" type="Spring.Objects.TestObject, Spring.Core.Tests">
<property name="age" value="20"/>
<property name="spouse">
<object type="Spring.Objects.TestObject, Spring.Core.Tests">
<property name="age" value="21"/>
</object>
</property>
</object>
// will result in 21, which is the value of property 'spouse.age' of object 'person'
<object name="theAge" type="Spring.Objects.Factory.Config.PropertyRetrievingFactoryObject, Spring.Core">
<property name="TargetObject" ref="person"/>
<property name="TargetProperty" value="spouse.age"/>
</object>
下面是使用PropertyRetrievingFactoryObject获取静态属性的方法:
<object id="cultureAware"
type="Spring.Objects.Factory.Xml.XmlObjectFactoryTests+MyTestObject, Spring.Core.Tests">
<property name="culture" ref="cultureFactory"/>
</object>
<object id="cultureFactory"
type="Spring.Objects.Factory.Config.PropertyRetrievingFactoryObject, Spring.Core">
<property name="StaticProperty">
<value>System.Globalization.CultureInfo.CurrentUICulture, Mscorlib</value>
</property>
</object>
类似的,获取实例属性的方法如下:
<object id="instancePropertyCultureAware"
type="Spring.Objects.Factory.Xml.XmlObjectFactoryTests+MyTestObject, Spring.Core.Tests">
<property name="Culture" ref="instancePropertyCultureFactory"/>
</object>
<object id="instancePropertyCultureFactory"
type="Spring.Objects.Factory.Config.PropertyRetrievingFactoryObject, Spring.Core">
<property name="TargetObject" ref="instancePropertyCultureAwareSource"/>
<property name="TargetProperty" value="MyDefaultCulture"/>
</object>
<object id="instancePropertyCultureAwareSource"
type="Spring.Objects.Factory.Xml.XmlObjectFactoryTests+MyTestObject, Spring.Core.Tests"/>
顶部编辑4.3.5.2.使用字段值进行注入
FieldRetrievingFactoryObject类的功能和PropertyRetrievingFactoryObject很相似。如其名称所示,FieldRetrievingFactoryObject可以获取对象或类(指静态字段)的公有字段值。
下面的例子使用FieldRetrievingFactoryObject获取一个类的公有静态字段:
<object id="withTypesField"
type="Spring.Objects.Factory.Xml.XmlObjectFactoryTests+MyTestObject, Spring.Core.Tests">
<property name="Types" ref="emptyTypesFactory"/>
</object>
<object id="emptyTypesFactory"
type="Spring.Objects.Factory.Config.FieldRetrievingFactoryObject, Spring.Core">
<property name="TargetType" value="System.Type, Mscorlib"/>
<property name="TargetField" value="EmPTytypeS"/>
</object>
获取公有实例字段的方法如下:
<object id="instanceCultureAware"
type="Spring.Objects.Factory.Xml.XmlObjectFactoryTests+MyTestObject, Spring.Core.Tests">
<property name="Culture" ref="instanceCultureFactory"/>
</object>
<object id="instanceCultureFactory"
type="Spring.Objects.Factory.Config.FieldRetrievingFactoryObject, Spring.Core">
<property name="TargetObject" ref="instanceCultureAwareSource"/>
<property name="TargetField" value="Default"/>
</object>
<object id="instanceCultureAwareSource"
type="Spring.Objects.Factory.Xml.XmlObjectFactoryTests+MyTestObject, Spring.Core.Tests"/>
顶部
编辑4.3.5.3.使用方法的返回值进行注入
本节要讲的第三个类是MethodInvokingFactoryObject。MethodInvokingFactoryObject允许使用任意方法的返回值进行注入。
MethodInvokingFactoryObject类可以处理实例方法和静态方法。此外,Spring.NET中另有一种处理对象初始化的机制(参考4.5.1.1,IInitializingObject接口和init-method属性)也可以用来进行(初始化)方法的调用,但是用这种机制调用方法的目的只是进行初始化工作,并且不允许向方法中传递任何参数,调用的时机也被限制在对象被容器创建时。MethodInvokingFactoryObject类则允许在任意时刻调用任意对象的任意方法(或者任意类的静态方法)。
在下面的例子中,使用MethodInvokingFactoryObject类强制在myService对象创建之前调用一个静态方法。
<object id="force-init"
type="Spring.Objects.Factory.Config.MethodInvokingFactoryObject, Spring.Core">
<property name="TargetMethod" value="Initialize">
<property name="TargetType" value="ExampleNamespace.ExampleInitializerClass, ExampleAssembly">
</object>
<object id="myService" type="MyService" depends-on="force-init"/>
(按:原文中上面的例子似有错误,此处已做修正;具体可参考英文文档)
注意对象定义myService的depends-on属性引用了一个名为force-init的 MethodInvokingFactoryObject对象,该引用会使对象force-init在myService之前初始化(并且调用force -init的目标类型上的目标方法。注意,若要使这个配置正常工作,myService对象必须以singleton模式操作(按:否则 myService对象只会在请求时创建,而此时force-init已经被容器预先创建了,也就失去了depends-on的意义)——这也是默认的对象布署模式,参见下一章)。
MethodInvokingFactoryObject类也可用于访问工厂方法(按:当在代码中通过名称"force-init"从容器中获取对象时,如果其目标方法有返回值,那么获得的对象就是目标方法的返回值;如果目标方法返回null,那么获得对象的类型则是 System.Reflecton.Missing),一般情况下,工厂方法都是以singleton模式操作的。如果 MethodInvokingFactoryObject对象为singleton模式,那么在它所有属性被设置后(按:应该是设置了足够的属性后,即 TargetType和TargetMethod),目标方法会立即被调用并将返回值存入缓存以备用。随后,如果容器向此工厂请求对象,那么返回的将是缓存中的值。MethodInvokingFactoryObject对象的singleton属性可以设置为false,此时每次请求对象都会去调用目标方法(返回值不会被缓存)。
在MethodInvokingFactoryObject的对象定义中,用TargetMethod和TargetType属性指定目标静态方法名和方法所在的类型全名;而用TargetMethod和TargetObject属性来指定目标实例方法名和方法所在对象的引用。
目标方法的参数可以通过两种方式来设置(这两种方式可混合使用)。第一种是通过Arguments属性来指定参数列表。注意列表中项的顺序是很重要的——必须和方法签名中的参数列表在次序上完全一致,且类型兼容,如下例:
<object id="myObject" type="Spring.Objects.Factory.Config.MethodInvokingFactoryObject, Spring.Core">
<property name="TargetType" value="Whatever.MyClassFactory, MyAssembly"/>
<property name="TargetMethod" value="GetInstance"/>
<!-- the ordering of arguments is significant -->
<property name="Arguments">
<list>
<value>1st</value>
<value>2nd</value>
<value>and 3rd arguments</value>
<!-- automatic Type-conversion will be performed prior to invoking the method -->
</list>
</property>
</object>
第二种方式是通过NamedArguments属性配置一系列键/值对,其中键名对应参数名(文本类型),值对应参数值(可以是任意对象)。参数名是大小写不敏感的,并且顺序(当然)是不重要的(因为词典类型本身就没有顺序)。见下面的例子:
<object id="myObject" type="Spring.Objects.Factory.Config.MethodInvokingFactoryObject, Spring.Core">
<property name="TargetObject">
<object type="Whatever.MyClassFactory, MyAssembly"/>
</property>
<property name="TargetMethod" value="Execute"/>
<!-- the ordering of named arguments is not significant -->
<property name="NamedArguments">
<dictionary>
<entry key="argumentName"><value>1st</value></entry>
<entry key="finalArgumentName"><value>and 3rd arguments</value></entry>
<entry key="anotherArgumentName"><value>2nd</value></entry>
</dictionary>
</property>
</object>
下面使用MethodInvokingFactoryObject来调用一个实例方法:
<object id="myMethodObject" type="Whatever.MyClassFactory, MyAssembly" />
<object id="myObject" type="Spring.Objects.Factory.Config.MethodInvokingFactoryObject, Spring.Core">
<property name="TargetObject" ref="myMethodObject"/>
<property name="TargetMethod" value="Execute"/>
</object>
上例中的目标对象也可以是匿名的内联对象定义...如果对象的方法不会在工厂对象之外调用,那么最好通过内联对象定义将目标方法限制在工厂对象内部。
最后要注意,如果使用MethodInvokingFactoryObject来调用一个有变长参数列表的方法,必须使用list来按顺序定义要传递的参数值。请看下面的例子,其中CreateObject方法的参数arguments是用C#关键字params来定义的;随后是相应的XML配置:
[C#]
public class MyClassFactory
{
public object CreateObject(Type objectType, params string[] arguments)
{
return ... // implementation elided for clarity...
}
}
<object id="myMethodObject" type="Whatever.MyClassFactory, MyAssembly" />
<object id="paramsMethodObject" type="Spring.Objects.Factory.Config.MethodInvokingFactoryObject, Spring.Core">
<property name="TargetObject" ref="myMethodObject"/>
<property name="TargetMethod" value="CreateObject"/>
<property name="Arguments">
<list>
<value>System.String</value>
<!-- here is the 'params string[] arguments' -->
<list>
<value>1st</value>
<value>2nd</value>
</list>
</list>
</object>
顶部
编辑4.3.6.IFactoryObject接口的其它实现类
除了前一节讲过的PropertyRetrievingFactoryObject,MethodInvokingFactoryObject和 FieldRetrievingFactoryObject三个类之外,Spring.NET还提供了另外几个非常有用的IFactoryObject接口实现类,下面我们来逐一讨论它们。
顶部
编辑4.3.6.1. Log4Net
Log4NetFactoryObject类允许在多个对象中共享一个日志对象,而不需为每个对象都创建单独的日志对象。下面这个 Log4NetFactoryObject对象的LogName属性值为“DAOLogger”,该对象由SimpleAccoundDao和 SimpleProductDao对象共用。
<objects xmlns="http://www.springframework.net"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.net
http://www.springframework.net/xsd/spring-objects.xsd" >
<object name="daoLogger" type="Spring.Objects.Factory.Config.Log4NetFactoryObject, Spring.Core">
<property name="logName" value="DAOLogger"/>
</object>
<object name="productDao" type="PropPlayApp.SimpleProductDao, PropPlayApp ">
<property name="maxResults" value="100"/>
<property name="dbConnection" ref="myConnection"/>
<property name="log" ref="daoLogger"/>
</object>
<object name="accountDao" type="PropPlayApp.SimpleAccountDao, PropPlayApp ">
<property name="maxResults" value="100"/>
<property name="dbConnection" ref="myConnection"/>
<property name="log" ref="daoLogger"/>
</object>
<object name="myConnection" type="System.Data.Odbc.OdbcConnection, System.Data">
<property name="connectionstring" value="dsn=MyDSN;uid=sa;pwd=myPassword;"/>
</object>
</objects>
顶部
编辑4.3.7.使用depends-on
Spring.NET使用<ref/>节点来引用对象的依赖项。除非有特殊的初始化需求,一般不需要使用depends-on属性。但是,如果需要使用其它静态(类型或方法)或对象来做一些初始化的工作,就可以借助depends-on属性来确保在使用依赖对象之前将其初始化(按:与ref不同,depends-on只影响创建顺序,一个对象可以和它depends-on的对象没有任何依赖关系;另外,如果a和b都未创建,那么a ref b时,创建次序是先a后b,也就是先创建a,在为a进行依赖注入时才考虑b;而a depends-on b的创建次序是先b后a,不论a是否要引用b,都先创建b)。例如:
<object id="objectOne" type="Examples.ExampleObject, ExamplesLibrary" depends-on="manager">
<property name="manager" ref="manager"/>
</object>
<object id="manager" type="ManagerObject"/>
顶部编辑4.3.8.自动装配协作对象
Spring.NET具有自动装配的能力,也就是说,Spring.NET可以通过对象定义自动分辨某个对象的协作对象。自动装配是针对单个对象(按:针对每个协作对象)进行的,所以可对某些对象启用而某些对象关闭(按:即自动装配某些协作对象,而不自动装配其它协作对象)。使用自动装配可以减少甚至完全消除属性或参数值的设置工作。[2]自动装配有五种模式:
表4.3. 自动装配模式| 模式 | 解释 |
|---|
| no | 不进行自动装配。这是默认的设置,一般不建议修改该设置,因为显式指定对象的协作关系可以让开发人员很清楚自己在干什么,并且有助于为系统结构建立文档。 |
| byName | Spring.NET 会检查容器中的对象,查找和被装配属性完全同名的对象定义,并将其作为该属性的值。例如,如果将一个对象定义设置为按名称装配,且该对象有一个名为 Master的属性,Spring.NET就会查找名为Master的对象定义,并将其作为Master属性的值。 |
| byType | Spring.NET 会根据指定的类型来查找协作对象。假设某对象定义有一个类型为SqlConnection的协作对象,Spring.NET会在整个对象工厂中查找类型为 SqlConnection的对象定义来为其装配。如果找不到该类型的对象,或者找到了不止一个此类型的对象,Spring.NET会抛出异常,也就无法对此对象进行自动装配了。 |
| constructor | 和byType很相似,只是查找的对象要赋值给构造器的参数。如果对象工厂中没有或不只一个与参数类型相同的对象定义,会抛出致命异常。 |
| autodetect | 根据对象自身的配置信息来自动确定是使用constructor还是byType模式。如果发现对象具有默认构造器,则使用byType模式。 |
注意
显式指定的依赖项会覆盖自动装配的设置。自动装配可以和依赖检查结合使用,依赖检查会在所有自动装配完成之后进行。
顶部
编辑4.3.9.检查依赖项
Spring.NET可为容器中的对象检查依赖关系。依赖关系是由未定义实际值(按:就是没有用value子节点或属性指定值的情况,比如使用ref引用其它对象)的属性或自动装配来指定的。
依赖检查可确保对象的所有属性(或所有某种类型的属性)都能被正确注入。当然,多数类都会为自己的属性设置默认值,或者某些属性并非在所有情况下都是必需的,所以依赖检查的用处实际上有限。依赖检查同样可以象自动装配那样,针对单个对象启用或关闭。默认的设置是不进行依赖检查。依赖检查也有几种不同的处理模式,在XML对象定义中,可通过object节点的dependency-check属性来设置,如下表所示:
表4.4. 依赖检查模式| 模式 | 解释 |
|---|
| none | 不进行依赖检查。没有定义值的属性不会被设置。 |
| simple | 只对基元类型和集合属性进行检查(也就是除了协作对象引用外的其它所有情况)。 |
| object | 只对协作对象进行依赖检查。 |
| all | 对包括协作对象和基元类型、集合属性在内的所有依赖项进行检查。 |
顶部
编辑4.4. 类型转换
类型转换器(TypeConverter)用于将对象从一种类型转换为另一种类型。比如在用XML文件配置IoC容器时,就需要将文本值转换成目标属性的实际类型。如果没有为某种类型注册类型转换器,Spring.NET就会使用标准的.NET类型转换方法进行转型。本节将简述如何注册自定义的类型转换器。在标准的.NET代码中,可用TypeConverter特性将类型转换器与目标类型相关联[3],例如FCL中Font类型的定义:
[Serializable, TypeConverter(typeof(FontConverter)), ...]
public sealed class Font : MarshalByRefObject, ICloneable, ISerializable, IDisposable
{
// Methods
... etc ..
}
顶部
编辑4.4.1.枚举类型的转换
枚举类型的默认转换器是System.ComponentModel.EnumConverter类。在对象定义中,要使用枚举的某个值只需使用对应的枚举名称即可。例如,假设TestObject类中有一个FileMode枚举类型的属性,该枚举的一个值为Create。在下面的XML定义中可以看到枚举类型属性值的设置方法:
<object id="rod" type="Spring.Objects.TestObject, Spring.Core.Tests">
<property name="name" value="Rod"/>
<property name="FileMode" value="Create"/>
</object>
顶部编辑4.4.2.内置的类型转换器
Spring.NET预注册了一部分自定义类型转换器(例如将类的文本名称转换为System.Type类型的对象)。这些转换器都定义在 Spring.Core程序集的Spring.Objects.TypeConverters命名空间下,如下表所示:
表4.5. 内置的类型转换器| 类型 | 解释 |
|---|
| RuntimeTypeConverter | 将字符串表示的类型名转换为实际的System.Type对象 |
| FileInfoConverter | 将字符串转换为System.IO.FileInfo对象 |
| StringArrayConverter | 将以逗号分隔的字符串列表转换为字符串数组,或相反 |
| UriConverter | 将字符串表示的URI转换为实际的URI对象 |
| StreamConverter | 将Spring的IResource URI字符串表示形式转换为InputStream对象. |
| ResourceConverter | 将Spring的IResource URI字符串表示形式转换为IResource对象 |
| ResourceManagerConverter | 将一个两段字符串(资源名,程序集名)转换为 System.Resource.ResourceManager对象 |
| RGBColorConverter | 将以逗号分隔的红,绿,蓝值转换为System.Drawing.Color结构 |
| ExpressionConverter | 将字符串转换为IExpression接口实现类的对象 |
Spring.NET使用标准的.NET机制来解析各种类型,包括但不限于检查和应用程序相关的所有配置文件、GAC和程序集探测机制。
顶部
编辑4.4.3.自定义类型转换器
自定义的类型转换器可通过几种方法进行注册。在Spring.NET中,自定义类型转换器由TypeConverterRegistry类管理,在使用基于XML的容器时,最便捷的方法是在配置文件中添加自定义的节点处理器TypeConverterSectionHandler,请参见4.12节, 配置IApplicationContext。
另外一种方法与目前Java版的Spring相兼容,即使用对象工厂后处理器Spring.Objects.Factory.Config.CustomConverterConfigurer。下一节我们会讨论这个类。
如果通过编程方式创建IoC容器,则需要使用IConfigurableObjectFactory接口的 RegisterCustomConverter(Type requiredType, TypeConverter converter)方法注册类型转换器。
顶部
编辑4.4.3.1.使用CustomConverterConfigurer类
本节详细讨论如何在不使用TypeConverter特性的情况下为类型定义转换器。类型转换器也是独立的类,继承自TypeConverter。本节的方法利用了Spring.NET的工厂后处理机制。
请看下面的ExoticType类,以及包含一个ExoticType类型属性的DependsOnExoticType类:
public class ExoticType
{
private string name;
public ExoticType(string name)
{
this.name = name;
}
public string Name
{
get { return this.name; }
}
}
及:
public class DependsOnExoticType
{
public DependsOnExoticType() {}
private ExoticType exoticType;
public ExoticType ExoticType
{
get { return this.exoticType; }
set { this.exoticType = value; }
}
public override string ToString()
{
return exoticType.Name;
}
}
如果设置得当,我们希望能在配置文件中通过字符串来为该类的ExoticType属性设定值,在后台,Spring.NET会使用一个TypeConverter将字符串值转换为ExoticType对象。
<object name="sample" type="SimpleApp.DependsOnExoticType, SimpleApp">
<property name="exoticType" value="aNameForExoticType"/>
</object>
相应的类型转换器为:
public class ExoticTypeConverter : TypeConverter
{
public ExoticTypeConverter()
{
}
public override bool CanConvertFrom (
ITypeDescriptorContext context,
Type sourceType)
{
if (sourceType == typeof (string))
{
return true;
}
return base.CanConvertFrom (context, sourceType);
}
public override object ConvertFrom (
ITypeDescriptorContext context,
CultureInfo culture, object value)
{
if (value is string)
{
string s = value as string;
return new ExoticType(s.ToUpper());
}
return base.ConvertFrom (context, culture, value);
}
}
最后,在容器的配置文件中,用CustomConverterConfigurer对象注册新的TypeConverter,Spring.NET就可以使用它了:
<object id="customConverterConfigurer"
type="Spring.Objects.Factory.Config.CustomConverterConfigurer, Spring.Core">
<property name="CustomConverters">
<dictionary>
<entry key="SimpleApp.ExoticType">
<object type="SimpleApp.ExoticTypeConverter"/>
</entry>
</dictionary>
</property>
</object>
顶部
编辑4.5. 自定义对象的行为
顶部
编辑4.5.1.生命周期接口
Spring.NET通过几个专门的接口来控制容器中对象的行为,这些接口包括Spring.NET定义的IInitializingObject接口和标准的System.IDisposable接口。容器会在实现了这两个接口的对象上调用AfterPropertyiesSet()方法和 Dispose()方法,这样我们就有机会在对象初始化和销毁时做一些额外的工作。
在内部,Spring.NET使用IObjectPostProcessor接口的实现类来处理这两个接口并调用适当的方法。如果需要某些特殊的功能或是 Spring.NET没有提供的生命周期行为,可以自行创建IObjectPostProcessor的实现类。请参考4.8节,使用IObjectPostProcessor接口自定义对象。
下面讨论所有的生命周期接口。
顶部
编辑4.5.1.1.IInitializingObject接口和init-method属性
Spring.Objects.Factory.IInitializingObject接口允许容器在完成对象的属性设置之后执行对象的初始化工作。该接口只有一个方法:
- void AfterPropertiesSet():在对象的所有属性被设置之后由容器调用。在该方法中,我们可以检查必需的属性是否都已被正确设值,或者可以执行进一步的初始化工作。此方法可以抛出任何异常以标识配置错误、初始化失败等情况。
注意
一般来说,尽量不要使用IInitializingObject接口。因为在对象定义中可以通过init-method属性来指定初始化方法。
<object id="exampleInitObject" type="Examples.ExampleObject" init-method="init"/>
[C#]
public class ExampleObject
{
public void Init()
{
// do some initialization work
}
}
和下面的方法是一样的...
<object id="exampleInitObject" type="Examples.AnotherExampleObject"/>
[C#]
public class AnotherExampleObject : IInitializingObject
{
public void AfterPropertiesSet()
{
// do some initialization work
}
}
但在使用init-method时,无需对Spring.NET产生依赖。
注意
在布署prototype模式的对象时,容器无法处理对象的生命周期事件。Spring.NET不可能仅通过对象定义来完全控制非singleton/原型对象的生存期。对象一旦创建就会交付给客户代码,容器不再保存其引用。对于Spring.NET来说,非singleton对象就如同使用new操作符一样,所有与对象生命周期有关的工作都应该由客户代码来处理。
顶部
编辑4.5.1.2.IDisposable接口和destroy-method属性
System.IDisposable接口使我们有机会在容器被销毁时,通过对象的回调方法来处理容器中对象的销毁工作。该接口只有一个方法:
- void Dispose():当容器被销毁时调用。在此可释放对象保留的任何资源(比如数据库连接)。可在此方法中抛出任何异常,但是此处抛出的任何异常都不会阻止容器的销毁,只会被记录到日志中。
注意
由于在对象定义中(XML或数据库中)可以通过destroy-method来指定对象的清理方法,所以在使用Spring.NET时,可以不去实现IDisposable接口。
<object id="exampleInitObject" type="Examples.ExampleObject" destroy-method="cleanup"/>
[C#]
public class ExampleObject
{
public void cleanup()
{
// do some destruction work (such as closing any open connection (s))
}
}
和下面的方式完全相同:
<object id="exampleInitObject" type="Examples.AnotherExampleObject"/>
[C#]
public class AnotherExampleObject : IDisposable
{
public void Dispose()
{
// do some destruction work
}
}
顶部
编辑