属性,协作对象,自动装配和依赖检查
设置对象的属性和协作对象
控制反转(依赖注入)主要有两种方式:
-
属性注入(按:即设值方法注入,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在创建时及早发现问题。如果愿意,可随时覆盖这一默认行为,将任意对象设置为惰性创建。
首先看一个使用IoC容器进行属性注入的例子。下面是XML对象定义;随后是相关类型的代码。
[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; } } }
配置在XML对象定义中的构造器参数会在创建对象时传递给ExampleObject类的构造器。
注意属性注入(类型2)和构造器注入(类型3)并不排斥,完全可以在同一对象定义中同时使用,如下所示:
[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使用静态工厂方法来创建对象:
[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属性外),所以不再赘述。