Spring.NET的IoC容器(The IoC container)——简介(Introduction)
简介
这个章节介绍了Spring Framework的控制反转(Inversion of Control ,IoC)的实现原理。
Spring.Core 程序集是Spring.NET的 IoC 容器实现基础。IObjectFactory 接口提供了先进的配置机制,能够管理所有类型的对象。IApplicationContext 是IObjectFactory的子接口,它集成了Spring.NET的 Aspect Oriented Programming (AOP) ,消息资源处理功能(为了多语言),事件传播和应用程序特定层的上下文,例如WebApplicationContext
。
简单来说,IObjectFactory
提供了整个配置框架和基本功能,并且IApplicationContext
添加了更多针对企业级别的功能。IApplicationContext
是IObjectFactory
的超集并且在本章节主要通过它来介绍Spring的IoC容器。
如果你还只是Spring.NET或者IoC的新手,那么你可能需要从Chapter 37, IoC Quickstarts开始看起。这个章节包含了很多入门的例子,但是这些例子包含了很多接下来要细说的东西。如果你没办法一次性吸收所有东西,那也不要太担心,这些例子只是粗略地介绍Spring.NET 的各种功能。你看完这些例子之后,你可以回来这个章节继续查看更加细致的描述。
容器概览
IApplicationContext 接口代表了Spring IoC 容器,主要用来初始化,配置和组合你应用程序中的对象。容器根据配置文件来决定应该初始化,配置和组合哪些对象。配置元数据以XML形式展现。配置元数据告诉容器在你的应用程序中如何组合各个对象以及各个对象之间的关系。
Spring提供了一些可以直接使用的IApplicationContext 接口实现。在独立应用程序中,可以以编程式或者声明式的方式来在App.config文件中生成XmlApplicationContext。在web应用程序中,Spring提供了一个WebApplicationContext 实现,这个实现是通过在Web.config文件中添加一个自定义HTTP模块和HTTP处理句柄来完成配置。查看Web Configuration来获得更多相关信息。
下图从一个高层次展示了Spring是如何工作的。应用程序类通过配置元数据来组合,因此在ApplicationContext 生成和初始化之后,你将会拥有一个完整的配置完成的可以执行系统或者应用程序。
配置元数据
就像前面的图展示的那样,Spring的IoC容器使用配置元数据;这个配置元数据展示了了你做为一个开发者告诉Spring容器如何初始化,配置和组合你的应用程序中的对象。配哦之元数据以一种简单并且通俗的XML方式展现。
Spring的配置包含了至少一个对象的配置信息。基于XML的配置在<object/>标签中配置对象信息。
这些对象都对应你应用程序中的对象。一般来说你会定义的对象包括业务层对象,数据获取层对象(DAO)等等。一般来说你不会在容器中定义细粒度的领域对象,因为它一般是由DAO或者业务逻辑来产生的。
下面的例子展示了基于XML的配置元数据的基本结构:
<objects xmlns="http://www.springframework.net"> <object id="..." type="..."> <!-- collaborators and configuration for this object go here --> </object> <object id="...." type="..."> <!-- collaborators and configuration for this object go here --> </object> <!-- more object definitions go here --> </objects>
id属性是一个字符串,主要用来区分每个对象的定义。type属性定义了这个对象的类型,并且使用完整的类型名称,包含程序集的名称。id的值表示关联的对象。XML中关联对象的具体信息并没有在这个例子中展示,参考Dependencies 以获得更多信息。
Spring.NET通过使用XSD schema来让XML对象定义的验证更加简单。XSD文档记录详尽,所以可以尽情去查看(参见:Appendix D, Spring.NET's spring-objects.xsd)。XSD现在被用来验证XML文档的正确性。XSDschema还有另外一个目的,就是在一些可以实现XSD语法提示的编辑器中(典型的有 Visual Studio),它可以提供实时的语法正确性验证(在Visual Studio中还有智能感知功能)以使在XML定义对象的时候更加简便。你可以参阅Chapter 36, Visual Studio.NET Integration 以获得更多关于这种集成的信息。
初始化容器
Spring IoC容器的初始化非常直观。IApplicationContext 构造器的路径地址都以资源字符串的形式表示,它能让容器从各种外部的资源中,例如本地文件,嵌入的资源等,加载配置元数据。
IApplicationContext context = new XmlApplicationContext("services.xml", "data-access.xml");
下面的例子展示了业务逻辑层对象的配置文件
<objects xmlns="http://www.springframework.net"> <object id="PetStore" type="PetStore.Services.PetStoreService, PetStore"> <property name="AccountDao" ref="AccountDao"/> <property name="ItemDao" ref="ItemDao"/> <!-- additional collaborators and configuration for this object go here --> </object> <!-- more object definitions for services go here --> </objects>
下面的例子展示了DAO的配置文件
<objects xmlns="http://www.springframework.net"> <object id="AccountDao" type="Petstore.Dao.HibernateAccountDao, PetStore"> <!-- additional collaborators and configuration for this object go here --> </object> <object id="ItemDao" type="Petstore.Dao.HibernateItemDao, PetStore"> <!-- additional collaborators and configuration for this object go here --> </object> <!-- more object definitions for data access objects go here --> </objects>
在上面的例子中,业务层包含了PetStoreService类和两个基于NHibernate 的数据获取对象,分别是HibernateAccountDao 和HibernateItemDao 。property中的name属性对应类的属性名称,ref属性对应其他对象定义的名称。id和ref属性的关联表达了两个关联对象的之间依赖关系。获得更多关于配置对象依赖的详细信息,参见Dependencies。
从非默认资源中加载配置元数据
在前面的例子中配置资源都默认存放在bin\Debug 目录下。你可以使用Spring的IResource 来从其他位置加载资源。
下面的例子展示了如何指定外部文件系统或者根目录中的资源作为配置文件来生成IoC容器。
IApplicationContext context = new XmlApplicationContext( "file:///services.xml", "assembly://MyAssembly/MyDataAccess/data-access.xml");
上面的例子使用Spring.NET的IResource。IResource 接口为一系列可以以System.IO.Stream
表示的IO资源提供了一个简单和一致的接口。
这些资源大多以文件或者URL的方式表示,但是也可以是嵌入在.NET程序集中的资源。一个简单的URI可以用来描述资源的位置,例如file:///services.xml
或者其他常见的协议,例如http。
下面一小段代码展示了URI如何指向嵌入.NET程序集中的资源,assembly://<AssemblyName>/<NameSpace>/<ResourceName>
. IResource
在Section 7.1, “Introduction”有更详细的解释。
App.config/Web.config中声明式配置容器
你也可以在标准的.NET应用程序配置文件(App.config
或者 Web.config
)中自定义配置节点来生成容器。一个自定义节点生成和前面例子一样的IApplicationContext
的例子如下:
<spring> <context> <resource uri="file://services.xml"/> <resource uri="assembly://MyAssembly/MyDataAccess/data-access.xml"/> </context> </spring>
context中的type()是可选的。在一个单机应用程序中,context中的type的默认值是Spring.Context.Support.XmlApplicationContext 类,而在一个web应用程序中是WebApplicationContext。一个显式配置context的type属性的例子如下:
<spring> <context type="Spring.Context.Support.XmlApplicationContext, Spring.Core"> <resource uri="file:///services.xml"/> <resource uri="assembly://MyAssembly/MyDataAccess/data-access.xml"/> </context> </spring>
想使用一个自定义的配置节点来获得一个IApplicationContext
引用,只要使用下面代码:
IApplicationContext ctx = ContextRegistry.GetContext();
ContextRegistry 主要用来初始化应用程序上下文和定位service类型并绑定到其他对象上面去(参见Section 5.15, “Service Locator access”以获得更多信息)。实现这个功能的胶水类,Spring.Context.Support.ContextHandler ,是由基础类库提供的IConfigurationSectionHandler接口实现的,这个句柄类需要在配置文件中的configSections 节点中配置,如下:
<configSections> <sectionGroup name="spring"> <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core"/> </sectionGroup> </configSections>
This declaration now enables the use of a custom context section starting at the spring
root element.
In some usage scenarios, user code will not have to explicitly instantiate an appropriate implementation IApplicationContext
interface, since Spring.NET code will do it for you. For example, the ASP.NET web layer provides support code to load a Spring.NET WebApplicationContext
automatically as part of the normal startup process of an ASP.NET web application. As such, once the container has been created for you, it is often the case that you will never need to explicitly interact with it again in your code, for example when configuring ASP.NET pages.
Spring.NET通过使用XSD schema来让XML对象定义的验证更加简单。XSD文档记录详尽,所以可以尽情去查看(参见:Appendix D, Spring.NET's spring-objects.xsd)。XSD现在被用来验证XML文档的正确性。XSDschema还有另外一个目的,就是在一些可以实现XSD语法提示的编辑器中(典型的有 Visual Studio),它可以提供实时的语法正确性验证(在Visual Studio中还有智能感知功能)以使在XML定义对象的时候更加简便。你可以参阅Chapter 36, Visual Studio.NET Integration 以获得更多关于这种集成的信息。
Your XML object definitions can also be defined within the standard .NET application configuration file by registering the Spring.Context.Support.DefaultSectionHandler
class as the configuration section handler for inline object definitions. This allows you to completely configure one or more IApplicationContext
instances within a single standard .NET application configuration file as shown in the following example.
<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>
Other options available to structure the configuration files are described in Section 5.12.1, “Context Hierarchies” and Section 5.2.2.3, “Composing XML-based configuration metadata”.
The IApplicationContext
can be configured to register other resource handlers, custom parsers to integrate user-contributed XML schema into the object definitions section, type converters, and define type aliases. These features are discussed in section Section 5.11, “Configuration of IApplicationContext”
生成基于XML的配置元数据
使用多个XML文件来定义对象是很有用的。通常来说每个独立的XML配置文件都表示你架构中的一个逻辑层或者模块。
你可以使用IApplicationContext 构造器来从多个XML文件中加载对象定义。这个构造器包含了许多IResource 的未知,就像前面展示的那样。或者也可以使用<import/>
节点来从其他文件中加载对象定义,例如:
<objects xmlns="http://www.springframework.net"> <import resource="services.xml"/> <import resource="resources/messageSource.xml"/> <import resource="/resources/themeSource.xml"/> <object id="object1" type="..."/> <object id="object2" type="..."/> </objects>
在前面的例子中,外部对象从三个文件中加载,分别是services.xml, messageSource.xml, 和themeSource.xm。所有的路径都是相对路径,所以services.xml 必须和配置文件在同一个目录下面,而messageSource.xml and themeSource.xml 必须在配置文件同一级的文件夹中。如你所见,可以省略一个斜杠,但是考虑到这些路径都是相对的,还是把斜杠省略掉更好。所有被引入的文件内容,都必须是符合Spring Schema的XML对象定义,
使用容器
IApplicationContext 是一个维护不同对象和依赖的高级工厂接口。使用GetObject(string) 或者indexer [string] 方法可以获得相应的实体。
IApplicationContext 能够读取对象定义并且获得它们,如下:
// create and configure objects IApplicationContext context = new XmlApplicationContext("services.xml", "daos.xml"); // retrieve configured instance PetStoreService service = (PetStoreService) context.GetObject("PetStoreService"); // use configured instance IList userList = service.GetUserNames();
你使用GetObject 方法获得对象的实例。IApplicationContext 接口还包含一些其他可以获得对象的方法,但是你的应用程序代码最好不要用它们。而且你的代码不应该调用GetObject 方法,因此就完全不依赖Spring的API。例如,Spring的web frameworks继承提供了多种web framework类,例如ASP.NET pages 和user controls的依赖注入
对象定义概览
一个Spring IoC容器管理一个或者多个对象。这些对象都由你提供给容器的配置文件元数据生成。
在容器中,这些对象都体现为IObjectDefinition 对象,包含以下元数据:
- 类型名称(type name):被定义对象的实现类名称
- 对象行为配置节点,声明了对象在容器中的行为。(例如,原型还是单例,生命周期的回调等等)
- 对其他对象的引用,也叫做collaborators 或者 dependencies
在新生成的对象中其他配置设置。例如工作者线程池中的线程数量或者线程池的大小限制。
这些元数据转化成了一系列对象的属性。下面的表格展示了一些属性:
除了对象定义之外,IApplicationContext 的实现也允许使用者注册容器外的对象。可以通过访问ApplicationContext的 IObjectFactory 的ObjectFactory来获得一个IObjectFactory 的实现:DefaultListableObjectFactory。DefaultListableObjectFactory 支持通过RegisterSingleton(..) 和RegisterObjectDefinition(..)的方法注册。然而,典型的应用程序只要通过元数据对象定义定义的对象就能工作。
名称对象
每一个对象都有一个活多个标识符。在管理这些对象的容器中,这些标识符都必须是独一无二的。一个对象通常只有一个标识符,但是如果需要多于一个标识符,额外的标识符可以被当作别名。
当使用基于XML的配置文件元数据的时候,你通常使用id或者name特性来指定标识符。id特性允许你指定一个id,由于它是一个真实的XML标签的id特性,当其他标签引用这个id的时候XML解析器能够进行一些额外的验证。这个是一个比较推荐的指定id的方法。然而,XML命名规范会限制XML的ID的字符。这个通常来说不是什么问题,但是如果你需要用到一些特殊的XML字符,或者想要引入其他对象的别名,你就可以把它们指定在‘name’特性中,用逗号(,
),分号(;
)或者空格分开。
在给对象命名的时候一般会使用C#属性的命名惯例,就是Pascal命名规则。例如,'AccountManager', 'AccountService', 'UserDao', 'LoginController'等等。
一致的对象命名方式能够让你的配置文件更加易读和理解,并且这种方式也会在当你使用Spring的AOP来给一系列用名称关联的对象应用通知的时候会更加容易方便。
给对象定义之外的对象起别名
在对象定义中,同时使用一个id和多个name的组合,你可以为这个对象定义多个名字。这些名字都是一个对象的别名,在某些场景下十分有用,例如允许应用程序中的每一个组件通过使用对自身引用的对象名字来推断一个常见的依赖。
然而,仅仅在对象定义的范围中定义别名是不够的。有些时候可能需要为定义在其他地方的对象引入别名。这个在一些大型系统中很常见。在这些系统中,配置文件都被分散在各个子系统中,每个子系统都有它们自己的对象定义集合。在基于XML的配置元数据中,你可以使用<alias/> 来完成这个功能。
<alias name="fromName" alias="toName"/>
在这个情况下,在一个容器中的一个对象的名字是fromName,可能在使用了这个别名定义之后变成了toName。
例如,这个位于子系统A中的配置元数据可能使用'SubsystemA-DbProvider来引用一个DbProvider 。而一个位于子系统B中的配置元数据可能使'SubsystemB-DbProvider来引用一个B系统的DbProvider 。当编译这个使用这两个子系统的主应用程序的时候,应用程序使用'MyApp-DbProvider'来引用它自己的DbProvider。想要让这三个名字都引用同一个对象,就需要往MyApp配置文件中加入下面的别名定义:
<alias name="SubsystemA-DbProvider" alias="SubsystemB-DbProvider"/> <alias name="SubsystemA-DbProvider" alias="MyApp-DbProvider"/>
现在app中的每一个组件都通过独一无二的名称引用了连接,并且不会和其他的定义撞车(实际上是创建了一个名称空间),而且它们引用了同一个对象。
实例化对象
一个对象的定义本质上是一个生成一个或多个对象的配方。容器在接受请求的时候通过查找配方来获取对象的名字,然后使用对象定义包含的配置文件元数据来生成或者获取一个真实的对象。
如果你使用基于XML的配置元数据,你可以通过在<object/>
的‘type’特性中指定要实例化的对象类型。这个‘type’特性()通常是必须的(Instantiation using an instance factory method 和 Object definition inheritance中有提到特殊的例外情况)。你可以用以下两种方式来使用type特性:
通常情况下,指定要被构建对象的type特性。容器通过反射方式调用他的构造函数来直接生成对象实例,有点像C#代码中使用‘new’关键字来构造对象实例。
在一些特殊情况下,指定包含用来创建对象实例的静态工厂方法的实际类。容器会调用类中的静态工厂的方法来生成对象实例,通过调用静态工厂方法返回的对象实例类型可能是相同的类型或者是其他类型。
通过构造函数实例化对象
当你使用构造函数的方式创建对象,所有的普通类都可以使用并且和Spring兼容。也就是说,被开发的类不需要实现任何特定的接口或者用某种特定的编码风格。只要简单地指定对象类型就足够了。然而,根据你想为指定的对象将使用何种IoC,你可能需要创建一个默认构造函数。
你可以定义你的对象类,用基于XML配置文件元数据的方式,如下:
<object id="exampleObject" type="Examples.ExampleObject, ExamplesLibrary"/>
想获得更多关于如何为构造函数提供参数机制,和如何在对象已经构建后之和设置对象实例属性的详细信息,参见Section 5.3.1, “Dependency injection”。
这个XML配置描述了一个可以通过exampleObject 名字来确定的对象定义。它的实例是一个位于ExamplesLibrary
程序集中的Examples.ExampleObject
类。注意一下type特性的值的结构…开头是一个类的名称,对应它的名称空间,然后是一个逗号,逗号后面是包含这个类的程序集名称。在前面的例子中,ExampleObject
类在Examples
名称空间中,然后被编译在ExamplesLibrary
程序集中。
包含该类型的程序集名称必须在type特性中指定。而且,建议指定程序集的全名,这样是为了保证Spring.NET使用来实例化的类就是你指定的类。通常来说这个只在当你使用一些已经被加载到全局程序集缓存(Global Assembly Cache ,GAC)中的程序集里面的类的情况下会出现这个问题。
如果你使用加号+定义了内嵌类来引用相应的内嵌类。例如,如果Examples.ExampleObject
类有一个内嵌类Person
,那么相应的XML声明应该是:
<object id="exampleObject" type="Examples.ExampleObject+Person, ExamplesLibrary"/>
如果你在定义已经被
If you are defining classes that have been compiled into assemblies that are available to your application (such as the bin
directory in the case of ASP.NET applications) via the standard assembly probing mechanisms, then you can specify simply the name of the assembly (e.g. ExamplesLibrary.Data
)... this way, when (or if) the assemblies used by your application are updated, you won't have to change the value of every <object/>
definition's type
attribute to reflect the new version number (if the version number has changed)... Spring.NET will automatically locate and use the newer versions of your assemblies (and their attendant classes) from that point forward.
通过静态工厂实例化对象
当定义使用静态工厂创建的对象的时候,你可以使用type特性来指定包含静态工厂方法的类型,然后用一个factory-method 特性来指定静态工厂方法。你应该能够调用这个名称然后返回一个对象实例,就像使用构造函数产生对象实例一样。这种对象定义的作用就是为了在遗留代码中调用静态工厂方法。
下面的对象定义通过调用工厂方法来定义对象。这个定义不指定返回值对象类型,而知识包含在工厂方法中的类型。在这个例子中,CreateInstance
方法必须是一个静态方法。
When defining an object which is to be created using a static factory method, you use the type attribute to specify the type containing the static factory method and an attribute named factory-method to specify the name of the factory method itself. You should be able to call this method (with an optional list of arguments as described later) and return a live object, which subsequently is treated as if it had been created through a constructor. One use for such an object definition is to call static factories in legacy code.
The following object definition specifies that the object will be created by calling a factory-method. The definition does not specify the type of the returned object, only the type containing the factory method. In this example, CreateInstance
must be a static method.
<object id="exampleObject" type="Examples.ExampleObjectFactory, ExamplesLibrary" factory-method="CreateInstance"/>
想要获取更多关于支持工厂方法的参数,并且在从工厂返回实例之后设置对象实例属性,参见Section 5.3.2, “Dependencies and configuration in detail”。
For details about the mechanism for supplying (optional) arguments to the factory method and setting object instance properties after it has been returned from the factory, see Section 5.3.2, “Dependencies and configuration in detail”
通过实例工厂实例化对象
和使用静态工厂方法一样,使用实例工厂来实例化对象调用一个容器中已经存在的对象中的非静态的方法来产生一个新的对象。要使用这个机制,要将type特性设置为空,然后在factory-object
Similar to instantiation through a static factory method, instantiation with an instance factory method invokes a a non-static method on an existing object from the container to create a new object. To use this mechanism, leave the type
attribute empty, and in the factory-object
attribute specify the name of an object in the current (or parent/ancestor) container that contains the instance method that is to be invoked to create the object. Set the name of the factory method itself with the factory-method
attribute.
<!-- the factory object, which contains an instance method called 'CreateInstance' --> <object id="exampleFactory" type="..."> <!-- inject any dependencies required by this object --> </object> <!-- the object that is to be created by the factory object --> <object id="exampleObject" factory-method="CreateInstance" factory-object="exampleFactory"/>
This approach shows that the factory object itself can be managed and configured through dependency injection (DI). See Dependencies and configuraiton in detail.
泛型对象生成
Generic types can also be created in much the same manner an non-generic types.
通过调用构造器生成泛型对象
The following examples shows the definition of simple generic types and how they can be created in Spring's XML based configuration file.
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>(); } } }
The XML configuration to create and configure this object is shown below
<object id="myFilteredIntList" type="GenericsPlay.FilterableList<int>, GenericsPlay"> <property name="Name" value="My Integer List"/> </object>
There are a few items to note in terms how to specify a generic type. First, the left bracket that specifies the generic type, i.e. <
, is replaced with the string < due to XML escape syntax for the less than symbol. Yes, we all realize this is less than ideal from the readability point of view. Second, the generic type arguments can not be fully assembly qualified as the comma is used to separate generic type arguments. Alternative characters used to overcome the two quirks can be implemented in the future but so far, all proposals don't seem to help clarify the text. The suggested solution to improve readability is to use type aliases as shown below
<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" />
Refer to Section 5.11, “Configuration of IApplicationContext” for additional information on using type aliases.
通过静态工厂生成泛型对象
The following classes are used to demonstrate the ability to create instances of generic types that themselves are created via a static generic factory method.
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; } } }
The accompanying factory class is
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>(); } }
The XML snippet to create an instance of TestGenericObject where V
is a List of integers and W
is an integer is shown below
<object id="myTestGenericObject" type="GenericsPlay.TestGenericObjectFactory, GenericsPlay" factory-method="StaticCreateInstance<System.Collections.Generic.List<int>,int>" />
The StaticCreateInstance method is responsible for instantiating the object that will be associated with the id 'myTestGenericObject'.
通过实例工厂生成泛型对象
Using the class from the previous example the XML snippet to create an instance of a generic type via an instance factory method is shown below
<object id="exampleFactory" type="GenericsPlay.TestGenericObject<int,string>, GenericsPlay"/> <object id="anotherTestGenericObject" factory-object="exampleFactory" factory-method="CreateInstance<System.Collections.Generic.List<int>,int>"/>
This creates an instance of TestGenericObject<List<int>,int>