当你为你的应用程序配置services 和actions 的时候,定义你的类的依赖关系是核心任务之一。这一章演示了Parsley 提供的各种依赖注入选项。
首选的依赖配置风格是使用AS3 元数据标签,因为一个类的依赖关系是类定义的核心环节,在ActionScript类本身定义依赖它是有道理的。有些情况下你可能仍然倾向于外部化声明依赖,在这一章的最后一节描述 4.5在MXML 或 XML中声明依赖。
一些人认为这是最干净的依赖注入方式的封装,因为它允许你创建不可变的类。因为(不幸的是)Flash播放器目前忽略放在构造函数上的元数据标签,你必须放一个[InjectConstructor]在类的声明上来告诉Parsley执行构造函数注入。
package com.bookstore.actions {
[InjectConstructor]
class LoginAction {
private var service:LoginService;
private var manager:UserManager;
function LoginAction (service:LoginService, manager:UserManager = null) {
this.service = service;
this.manager = manager;
}
}
}
注意,在上面的示例中manager 参数是可选的。Parsley反映了这一信息,并且把他当成定义的依赖是必须或是可选的的一种提示。所以在这种情况下,如果它不包含LoginService类型的对象,容器将抛出一个错误,但如果上下文不包含一个UserManager类型的对象,它只会悄悄忽略第二个参数。
构造函数注入依赖项基于参数类型选择。这意味着,它只适用于你知道上下文将总是含有最多一个对象和一个匹配的类型的依赖。使用接口作为参数类型,它也是一个好的习惯,这样你就可以在配置中切换实现,无需修改类。
如果你想使用构造函数注入,正如3.2 MXML配置 解释的不能简单的使用MXML标签配置,因为在这种情况下,MXML编译器会生成对象创建代码,并且Parsley只有获得被实例化的对象后才会执行额外的配置。
所以这样一个类的定义如下:
<Objects
xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns="http://www.spicefactory.org/parsley"
xmlns:actions="com.bookstore.actions.*">
<fx:Declarations>
<actions:LoginAction/>
</fx:Declarations>
</Objects>
你应该像这样简单的声明它:
<Objects
xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns="http://www.spicefactory.org/parsley">
<fx:Script>
<![CDATA[
import com.bookstore.actions.*;
]]>
</fx:Script>
<fx:Declarations>
<Object type="{LoginAction}"/>
</fx:Declarations>
</Objects>
当使用Parsley的Object 标签的时候,框架负责实例化该对象,以便可以执行构造函数注入。
使用XML没有这个限制。
不幸的是在一些Flash播放器版本有一个肮脏的bug,反射参数类型使用describeType的构造函数时不起作用(Player在这种情况下总会报告‘*’作为类型)(意思是必须的),如果你碰到这个bug,惟一的解决方案(不幸的是),在你初始化Parsley之前创建这些类的实例。
new LoginAction();
new ShoppingCartAction();
你可以简单地扔掉这些实例,就创建一个实例“修复”类的describe type的参数类型。
方法注入类似于构造函数注入。你可以将[Inject]放在任意数量的方法上。
package com.bookstore.actions {
class LoginAction {
private var service:LoginService;
private var manager:UserManager;
[Inject]
public function init (service:LoginService, manager:UserManager = null) : void {
this.service = service;
this.manager = manager;
}
}
}
与构造方法一样Parsley会识别方法的参数是否是可选的,然后确定依赖项是可选或必须的。该对象的注入将被选定由类型注入,所以你应该确保你的配置中包含最多一个对象和一个匹配的类型。MXML配置上没有方法注入的限制,所以与构造函数注入一样,你也可以使用简单MXML标签来添加对象到容器。
这种注入机制类似于方法注入,但是换成了属性表示:
package com.bookstore.actions {
class LoginAction {
private var manager:UserManager;
[Inject]
public var service:LoginService;
[Inject(required="false")]
public function set manager (manager:UserManager) : void {
this.manager = manager;
}
}
}
你可以将 [Inject]标签放在用var 声明的对象或者一个setter函数上,对于属性来说,Parsley不能查出它的依赖是否是可选的,那么你可以显式地设置它的required 属性。如果属性是省略那么默认是true 。
像上面所示的构造函数或方法注入一样,这个模式通过类型选择依赖。同样,你应该确保在你的配置里包含最多一个与类匹配的对象。
通过容器来选择依赖,你可以通过选择显式地设置对象的id注入来代替。
[Inject(id="defaultLoginService")]
public var service:LoginService;
在这种情况下Parsley将通过id选择依赖,所以配置必须包含一个对应的id的对象:
<Objects
xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns="http://www.spicefactory.org/parsley"
xmlns:actions="com.bookstore.services.*">
<fx:Declarations>
<services:LoginServiceImpl id="defaultLoginService"/>
</fx:Declarations>
</Objects>
把ids放到你的类中,通常这不是最好的主意来包含配置工件。Inject元数据标签通常更适用于通过类型注入,就像前一节中演示的。如果你必须明确的设置一个依赖的id,往往是更好的选择外部MXML或XML配置,如在下一节中所示。
最终你可以在MXML或XML声明依赖关系。
MXML例子:
<Objects
xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns="http://www.spicefactory.org/parsley">
<fx:Script>
<![CDATA[
import com.bookstore.actions.*;
import com.bookstore.services.*;
]]>
</fx:Script>
<fx:Declarations>
<Object id="loginService" type="{LoginServiceImpl}">
<Property name="timeout" value="3000"/>
</Object>
<Object id="userManager" type="{UserManager}"/>
<Object type="{LoginAction}">
<ConstructorArgs>
<ObjectRef idRef="userManager"/>
</ConstructorArgs>
<Property name="service" idRef="loginService"/>
</Object>
</fx:Declarations>
</Objects>
XML 例子:
<objects
xmlns="http://www.spicefactory.org/parsley">
<object id="loginService" type="com.bookstore.services.LoginServiceImpl">
<property name="timeout" value="3000"/>
</object>
<object id="userManager" type="com.bookstore.services.UserManager"/>
<object type="com.bookstore.actions.LoginAction">
<constructor-args>
<object-ref id-ref="userManager"/>
</constructor-args>
<property name="service" id-ref="loginService"/>
</object>
</objects>
MXML和XML配置几乎是一样的,除了一些细小的符号差异(大写的“骆峰式”和用破折号的小写名称)。 你可以为构造方法或者属性设置依赖。对于构造函数的参数你甚至可以把它和简单属性的标签混合。
<constructor-args>
<object-ref id-ref="userManager"/>
<string>http://www.bookstore.com/services/</string>
<uint>3000</uint>
</constructor-args>
对于属性你只需要用 id-ref属性替换value 属性来指向另一个对象的定义。
声明内联性依赖
如果一个依赖是只需要一个单独的对象,你可以选择声明它内联:
<objects
xmlns="http://www.spicefactory.org/parsley">
<object type="com.bookstore.actions.LoginAction">
<constructor-args>
<object type="com.bookstore.services.UserManager"/>
</constructor-args>
<property name="service">
<object type="com.bookstore.services.LoginServiceImpl">
<property name="timeout" value="3000"/>
</object>
</property>
</object>
</objects>
请注意,你不能为一个内联对象定义设置一个id,MXML的例子也一样。
当你通过类型注入,并且依赖不是可选的,那么上下文必须包含只有一个与类型匹配的实例。如果依赖项缺失或含糊不清(不止一个匹配类型)上下文将抛出一个错误。
然而,在一个拥有多个上下文的应用程序中,你可以选择在子上下文中"override" 依赖,不会因为依赖的模棱两可而造成这些错误。这允许更大的灵活性。例如,你可以添加一个接口的默认实现到根上下文中,然后仍然让已加载的模块自己来选择是否安装。
当你通过类型来声明依赖的时候,框架会在属于第一个上下文中的依赖对象中查找这个类型。只有当没有发现匹配的实例的时候他才会在上下文继承树的下一个父亲中查找。这意味着覆盖只会影响到子上下文的对象。这并不意味着依赖在父上下文中得到“卸载”。它仍将被用于注入到在父上下文的其他对象。所以覆盖总是被解读为本地覆盖,而不是全局的。
这种机制有助于避免在 [Inject]标签使用字符串标识符,即使你想添加相同的接口的不同的实现到不同的子上下文中。只有当你添加多个实现到相同的上下文中的时候,你必须切换到使用通过id注入以避免错误。
你也可以为id注入用类似的方式添加重写。在一个上下文中不允许有两个或更多相同id的对象,但它是合法的添加一个有id的对象,这个id已经在父上下文有了。