Spring.NET学习笔记(一)
对象、对象工厂和应用程序上下文
Spring.Core程序集是Spring.NET控制反转(IoC)功能的基础。Spring.Core程序集中的IObjectFactory接口为Spring.NET提供了一种高级的配置机制,可用所有可能的存储介质保存任意对象的配置信息。同位于此程序内的IApplicationContext接口则扩展了IObjectFactory,增加了面向切面编程(AOP)和消息资源处理(用于国际化)等功能。
IObjectFactory, IApplicationContext和IObjectDefinition接口
1、IObjectFactory和IApplicationContext
IObjectFactory最常用的实现是Spring.Objects.Factory.Xml.XmlObjectFactory,可以在代码中显示创建这个容器。下面代码创建了XmlObjectFactory类的一个实例,XmlObjectFactory是IObjectFactory的实现类之一,假定在object.xml文件中定义了要装配和发布的服务对象。将该文件的信息传递给XmlObjectFactory的构造器,即可创建一个容器。
【C#】
IResource input = new FileSystemResource ("objects.xml"); IObjectFactory factory = new XmlObjectFactory(input);
代码中使用了Spring.NET的IResource接口,IResource能以简单统一的方式访问许多可用System.IO.Stream表示的IO资源。这些IO资源一般是独立的文件或者URL,但也可以是.NET程序集的内嵌资源。通过IResource接口,可以用简单的URI格式来描述资源的位置,比如可用file://object.xml来表示一个文件。此外,IResource也支持很多其它协议,如http等。
IApplicationContext是IObjectFactory的超集,我们一般都会用IApplicationContext来作为容器。在创建容器时可以像上例一样用IResource实例化IApplicationContext接口的任何一个实现类。另外,IApplicationContext支持用多个配置文件创建容器。
IApplicationContext context = new XmlApplicationContext( "file://objects.xml", "assembly://MyAssembly/MyProject/objects-dal-layer.xml"); //IApplicationContext也是一个IObjectFactory... IObjectFactory factory = (IObjectFactory) context;
更好的创建方式是在标准.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类既可用来初始化应用程序上下文,也可用来以服务定位器风格对容器中的对象进行访问。使这一切成为可能的是Spring.Context.Support.ContextHandler类,该类实现了FCL的IConfigurationSectionHandler接口。必须在.NET配置文件的<configSections>节点中注册这个类,注册了这个节点处理器后,配置文件中的<spring>节点才能起作用。如下所示:
<configSections> <sectionGroup name="spring"> <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core"/> </sectionGroup> </configSections>
在某些情况下,用户不需要以任何方式显式创建容器,Spring.NET可以自行创建。例如,Spring.NET中的Spring.Web模块可以将IApplicationContext作为Web应用程序正常启动进程的一部分进行自动装载。
基本上,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>
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>
对象定义
容器所管理的对象由对象定义来配置,一个对象定义包含以下信息:
-
对象类型,即所定义对象的实际类型。
-
对象行为,用来规定对象在IoC容器中的行为(例如,是否布署为singleton,自动装配的模式,依赖检查的模式,初始化和销毁方法等)。
-
对象创建后要设置的属性值。例如,一个线程池管理对象的可用线程数,或者用来创建线程池的类型信息,都可以通过属性或构造器参数进行设置。
-
对象所需要的其它对象,例如一个对象的协作对象(同样可通过属性或构造器设置)。这些对象也可以叫做依赖项。
上面提到了用属性和构造器参数来设置依赖项。Spring.NET支持两种类型的IoC:类型2和类型3(分别是构造器参数注入和属性注入)。也就是说,当一个对象被IoC容器创建时,既可以使用常规的属性设值方法为属性设值,也可以直接向构造器传递参数来为属性赋值。(对.NET来说,“属性注入”似乎比“设值方法注入”更贴切)
对象定义内容
内容 |
详细信息 |
对象类型 | 对象的创建 |
id和name | 对象标识符(id和name) |
singleton或prototype | Singleton和Prototype |
对象属性 | 设置对象的属性和协作对象 |
构造器参数 | 设置对象的属性和协作对象 |
自动装配模式 | 自动装配协作对象 |
依赖检查模式 | 检查依赖项 |
初始化方法 | 生命周期接口 |
销毁(destruction)方法 | 生命周期接口 |
对象的创建
对象定义会包含对象的类型信息。多数情况下,容器会根据对象定义中的type属性值去直接调用相应类型的某个构造器。另外,容器也可以调用工厂方法来创建对象,这时type属性的值就应该是包含工厂方法的类型。
通过构造器创建对象
使用构造器创建对象时,并不要求对象必须是某种特定的类型,也不需要了解它的实现方式。也就是说,类型不必去实现某个接口或扩展某个基类以求和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"/>
通过静态工厂方法创建对象
在使用静态工厂方法创建对象时,除了要将对象定义的type属性设为包含静态工厂方法的类型外,还要设置一个名为factory-method的属性来指定静态工厂方法的名称。Spring.NET会调用这个方法(稍后会看到,静态工厂方法还可以包含一个可选的参数表)来创建对象,结果和通过构造器创建对象是一样的。在实际项目中,可以用这种方法去调用原有代码中的静态工厂。
下面的对象就是通过静态工厂方法创建的。注意:对象定义中的type并非是要创建的对象的类型,而是包含了工厂方法的类型;同时,CreateInstance必须是静态方法。
<object id="exampleObject" type="Examples.ExampleObjectFactory, ExamplesLibrary" factory-method="CreateInstance"/>
通过实例工厂方法创建对象
通过实例工厂方法创建对象与通过静态工厂方法创建对象的配置方式类似。此时,实例工厂方法所在的对象必须也要配置在同一容器(或父容器)中。
如果要通过实例工厂方法创建对象,对象定义就不能包含type属性,而要用factory-object属性引用工厂方法所在的对象;注意,该属性值必须是包含工厂方法的对象的名称,且该对象必须定义在当前容器或父容器中。工厂方法的方法名则通过factory-method属性指定。
请看下面的例子:
<!-- 工厂对象, 包含方法 'CreateInstance' --> <object id="exampleFactory" type="..."/> <!-- 指定创建对象的工厂对象 --> <object id="exampleObject" factory-method="CreateInstance" factory-object="exampleFactory"/>
泛型类的对象创建
通过构造器创建泛型对象
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" />
通过静态工厂方法创建泛型类
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”的对象。
对象标识符(id和name)
每个对象都有一个或多个id(就是所谓的标识符或名称,这些术语的含义是相同的)。id在容器中应该是唯一的。一个对象通常只有一个标识符,如果指定了一个以上名称,其余的就会被认为是别名。
在XML对象定义中,用id或者name属性来定义对象的标识符。每个对象都需要用id或name属性定义至少一个标识符。id属性允许为对象定义指定一个唯一的id,因为在Spring.NET的shcema文档中,id被标识为XML元素的ID属性,XML解析器可以在其它元素引用它的时候进行验证,在配置对象标识符时,应该优先使用id属性。但是,id属性值不能包含任何XML ID不允许使用的字符。如果一定要使用这些字符,应该使用name属性,在name属性中也可以通过逗号或分号为对象指定一个或多个别名。
Singleton和Prototype
对象可以通过两种模式布署:singleton和非singleton(或者叫做prototype,只是用在这里不是很合适)。当一个对象被定义为singleton时,容器中就只会有一个共享的实例,任何时候通过id或别名请求该对象都会返回这个共享实例的引用(也就是说这个对象只会被创建一次)。
当使用非singleton,或者说原型模式布署时,每次请求对象都会创建新的实例。在某些场合,如果需要为每个用户返回单独的用户对象或其它对象,非singlton布署模式就比较理想。
如果没有显式指定,对象的布署模式默认为singleton。注意非singleton(原型)模式会使Spring.NET在每次请求对象时都创建新的实例,这也许并非是我们预期的行为。所以,除非绝对需要,否则不要使用原型模式。
注意
当使用原型模式布署对象时,对象生命周期的改变(对Spring.NET来说)就没那么明显了。Spring.NET无法通过对象定义来完全管理非singleton(原型)对象的生命周期,因为这些对象一经创建就交由客户代码维护,容器不可能再继续跟踪它们。可以将Spring.NET的非singleton(原型)创建方式看做是new操作符的替代物。通过这种方式创建的任何对象,其生命周期都只能由客户代码管理。
下面的两个对象分别用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,就可将对象的创建推迟到第一次使用前,这里,“第一次使用”可能是客户代码的请求,也可能是容器在处理对象的依赖注入。