8 对象生命周期
本章提供了Parsley托管对象生命周期各个方面的详细信息。
8.1 关于托管对象
有很多方法可以指示框架来管理一个特定的对象。但重要的是要明白,所有这些不同的选项是明确的的并且没有魔法会自动发现让框架自己找到这些对象。用户定期在forum 上尝试提交一些东西,比如在属性上放一个[Inject],然后创建类的一个有新属性的实例,然后期待注入奇迹般的发生。对于这项工作将需要一些自定义编译器或运行时字节码生成,来允许框架将hooks 添加到对象创建上。Parsley没有提供这些选项。意思是要使用 Flex SDK 所提供的正常 mxmlc 和 compc 编译器。不管怎么说,提供这些钩子是毫无意义的,因为Parsley是一个模块化、 多上下文的环境,它不会明确增加的对象应到哪个上下文。
当然,当一个对象在另一个托管对象内部被创建的的时候同样如此。框架没有方法来检测这种调用。所以如果一个托管对象需要一些合作者,他们也往往需要被注入。在极少数情况下它可能创建这些实例本身,然后将它们明确的作为一个动态对象添加到一个上下文。
因此本节的目的是提供概述,将对象转变成托管对象和哪些服务是可用此类对象的选项。
Types of Managed Objects
托管对象的类型一般可以分为两类。
对象在上下文初始化的时候在配置中声明:
对象在使用以下选项初始化后动态的添加到上下文中:
l 9.4 自动组件注入(另一个视图注入选项)
l 8.7 动态对象(编程方式)
l 5.5 发布托管对象(基于解耦绑定功能)
两种类型服务的级别和前面章节提供几乎是相同的。唯一的区别是,动态添加对象在第二个列表不可以注入到其他对象中,因为注入配置是为健壮性设计的并且在上下文创建的时候就验证了。还有其他方法来得到一个添加到其他托管对象中的动态对象比如5 解耦绑定 或者 8.5 对象生命周期方法.
通常框架还负责创建对象。在这些情况下,它能够提供服务,如4.1构造函数注入。有时对象是由应用程序创建的,然后添加到上下文,这显然会阻止一个服务,比如执行构造函数注入。最常见的方法是让容器使用配置标签实例化对象,比如XML或者MXML配置文件中的<Object>,<DynamicObject> 或者 <MapCommand>。
设计应用程序需要慎重考虑的是,托管对象不会被垃圾回收。因此一个大的模块化的应用程序应该精心设计,确保你受益于Parsley对模块化的应用程序的支持,Parsley允许你动态创建和摧毁整个子上下文。
8.2 使用工厂(Using Factories)
有时候,直接配置目标对象可能没有效果。你可能在对象创建时想执行一些复杂的逻辑,以至于你想用工厂模式代替现在模式。AS3中有一个factory类,你可以像配置其它类一样配置它,使用Metadata, MXML 或 XML标签。唯一不同之处在于方法被标识成了factory方法(同样使用 Metadata,MXML或XML):
class CategoryFactory {
public var categoryName:String;
[Inject]
public var bookManager:BookManager;
[Factory]
public function createCategory () : Category {
var books:Array = bookManager.getBooksForCategory(categoryName);
return new Category(categoryName, books);
}
}
然后你也可以在配置文件中使用这个factory ,比如在MXML中:
<Objects
xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns="http://www.spicefactory.org/parsley"
xmlns:model="com.bookstore.model.*">
<fx:Declarations>
<model:BookManager/>
<model:CategoryFactory id="historyCategory" categoryName="history"/>
<model:CategoryFactory id="politicsCategory" categoryName="politics"/>
<model:CategoryFactory id="artsCategory" categoryName="arts"/>
</fx:Declarations>
</Objects>
我们上面定义的每一个factories将获得BookManager 实例注入,并且产生Category实例。
最重要的是,在Parsley引擎下使用工厂实际上为每个工厂创建了2个对象定义。一个用于工厂本身,一个用于工厂方法生产类。意思是如果你希望工厂生产Parsley托管的对象来发送和接受应用程序消息,你可以在目标类型上放一个元数据标签(在这个例子中是Category类)。
这也意味着你可以使用工厂和在注入点创建的对象,虽然大多数情况下你会对工厂产生对象比较感兴趣。
[Inject(id="historyCategory")]
public var historyBooks:Category;
建议不要使用工厂方法返回Object 类型:
[Factory]
public function createInstance () : Object {
这是有效的,Parsley会很乐意处理这种工厂方法。但是你会失去一些Parsley有用的功能,因为它使用了方法的返回类型来为目标类型生产对象定义。如果目标类型只是Object,工厂生产实际不会处理对象上的元数据标签,因为这发生在工厂方法第一次调用之前。此外你不能使用通过类型注入的工厂生产的对象,因为这个类型只能动态的确定。你会被限制为使用ID注入。
当然像大多数Parsley的特性一样,你可以在MXML或XML中声明工厂:
这可能在某些情况下会派上用场,例如一个工厂有多个生产对象的方法。这种情况下在类里面放元数据标签是不可能的(只允许一个[Factory] 标签)。
MXML Example
<Object id="monkey" type="{ZooFactory}">
<Factory method="createMonkey"/>
</Object>
<Object id="polarBear" type="{ZooFactory}">
<Factory method="createPolarBear"/>
</Object>
XML Example
<object id="monkey" type="com.example.ZooFactory">
<factory method="createMonkey"/>
</object>
<object id="polarBear" type="com.example.ZooFactory">
<factory method="createPolarBear"/>
</object>
8.3 异步对象初始化
很多操作在Flash Player中都是异步执行的。所以有可能你想要一个配置在Parsley IOC Container中的对象,首先在剩下的Context初始化之前来加载一些数据或资源,而在此之前初始化对象被异步地注入到其他对象。这种情况下你可以使用[AsyncInit]标签在任何初始化完成(或者失败)后就点燃事件的EventDispatcher上。
[AsyncInit]
public class FileLoader extends EventDispatcher {
public var filename:String;
[Init]
public function init () : void {
var loader:URLLoader = new URLLoader();
loader.addEventListener(Event.COMPLETE, fileLoaded);
loader.addEventListener(IOErrorEvent.IO_ERROR, handleError);
loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, handleError);
loader.load(new URLRequest(filename));
}
private function fileLoaded (event:Event) : void {
// handle loaded file
dispatchEvent(new Event(Event.COMPLETE));
}
private function handleError (event:ErrorEvent) : void {
dispatchEvent(new ErrorEvent(ErrorEvent.ERROR, false, false, event.text));
}
}
在例子中[AsyncInit]标签告诉框架,这是一个异步初始化的对象。用 [Init]注解的方法会在这个对象(FileLoader)的配置和依赖注入都处理完成后被调用(详情查看 8.5 对象生命周期方法)。我们开始这个加载的过程,根据加载是否成功,然后我们派发一个Event.COMPLETE 或者一个 ErrorEvent.ERROR。容器将监听这两个事件,如果是Event.COMPLETE,它会在Context 初始化的时候处理,如果是ErrorEvent.ERROR那么整个Context的初始化会被取消。
切换事件类型
Event.COMPLETE 和ErrorEvent.ERROR是标签初始化是否完成或失败的默认事件类型。他们可以用[AsyncInit]标签来切换:
[AsyncInit(completeEvent="myCustomCompleteEvent",errorEvent="myCustomErrorEvent")]
初始化顺序
AsyncInit对象总是会比普通对象先初始化,除非你给它们定义一个order属性来明确的指出哪一个总是有更高优先级。但是如果你不只一个对象标签了[AsyncInit] ,你可以希望显式地声明初始化顺序,就像下一节解释的。顺序不会因为依赖而改变,因为它们在初始化期间动态处理,并且允许双向或循环依赖,这将很难自动确定顺序。但是这只对AsyncInit对象来说是必要的,并且只在你希望保证当他们注入到其他对象的时候那些已经准备好了。否则一切都将被自动解决。
8.4 对象初始化顺序
如果你想显式地的指定初始化顺序你可以使用order属性:
MXML Example
<Object id="welcomeText" type="{FileLoader}" order="1">
<AsyncInit/>
<Init method="init"/>
<Property name="filename" value="welcome.txt"/>
</Object>
XML Example
<object id="welcomeText" type="com.example.FileLoader" order="1">
<async-init/>
<init method="init"/>
<property name="filename" value="welcome.txt"/>
</object>
如果你忽略这个属性,那么默认值是int.MAX_VALUE,所以所有没有order属性的对象在任意顺序中最后执行。属性可以设置为任意正整数或负整数的值。
Order属性也可以用于对象同步初始化。对于序列中的任何异步初始化对象,容器都会都会等待这个对象完成它的初始化过程然后才开始下一个。
8.5 对象生命周期方法
如果你希望Parsley 容器在你的对象创建或摧毁的时候调用方法,那么你可以添加[Init]或者[Destroy]元数据标签到相应的方法上:
[Init]
public function init () : void {
[...]
}
[Destroy]
public function dispose () : void {
[...]
}
用[Init]标签的方法会在对象已经初始化并且所有的注入已经处理后被调用。
用[Destroy] 标签的方法会在对象所属的Context实例使用Context.destroy()销毁后或者这个对象从上下文中移除后调用。
对于声明在MXML中的Flex组件,并且已经连接到Context 就像9 动态视图装配描述的一样,生命周期是不同的:对于这些对象,每次对象添加或者从舞台移除,方法都会得到调用。当然Destroy]方法还会在对象连接的Context被销毁的时候得到调用。
8.6 生命周期观察方法
在2.2这个功能中增加了一些新的方法方便的来观察或者修改其他对象,而不需要添加一些他们的配置。这可能非常有用,特别是短生命周期( short-lived)的对象,比如视图或者命令在任何时间都可能进入或者离开Context,因此它们不是常规注入有效的来源。
通过观察这些实例你仍然可以得到一个这样一个短生命周期对象的引用。
[Observe]
public function enhance (panel:ShoppingCartPanel) : void;
现在每当ShoppingCartPanel(或者子类)的一个新实例被完全配置好的时候,这个方法会被调用。
默认没有属性,如上面所示相当于:
[Observe(phase="postInit", scope="global")]
所以你也可以使用不同的生命周期的阶段(如preDestroy 当一个对象离开上下文,得到通知),还可以控制作用域只接受监听同一Context下匹配的类型,比如scope="local"。这里的作用域跟消息传递的作用域是一样的 6.10 使用作用域。
这种机制你可以简单地插入一些新的类,该类包含这样的标签并且现在类会突然得到增强而不需要改变它们的配置。这有点类似于现有的[Factory]标签可以用来定制对象实例化。用这个标签可以在实例化后定制对象配置。这两种情况下,你甚至不用以任何方式依赖于Parsley API。
支持的生命周期阶段
preConfigure | Invokes the observer right after the object was instantiated but before any dependency injection was performed. |
preInit | Invokes the observer after dependency injection has been performed but before the init method of the object (if available) gets invoked. |
postInit | The default if the phase attribute is omitted. Invokes the observer after dependency injection has been performed and the init method of the object (if available) has been invoked. |
preDestroy | Invoked when the object is removed from the Context but before the destroy method on the object (if available) gets invoked. |
postDestroy | Invoked when the object is removed from the Context and after the destroy method on the object (if available) was invoked. |
8.7 动态对象
(注:2.3版之前的的框架提供DynamicContext接口。这还可以向后兼容,但弃用了,不再需要,因为核心上下文接口现在包含此功能与几个新的附加选项)。
与通常用<Object>标签指定的根单例对象相比,动态对象可以动态的添加或从上下文移除。动态对象几乎表现得跟普通对象一样。特别地这意味着:
l 你可以把任何普通的对象注入到动态对象中
l 动态对象可以从任何可用的作用域中发送和接受消息
l 动态对象可以生命周期方法比如[Init]和[Destroy]
使用动态对象只有一个重要的限制:
l 你无法将一个动态对象注入到其他对象,除非你使用<DynamicObject>在MXML或者XML中指定
这个限制是自然的,因为依赖项注入是带有验证的,如果这组可用于注入的对象可以在任何时候改变这是不可能的。
对于一个更动态的表达依赖,有更类似于Flex绑定的工作的方式,你可以使用在 2.3 版本引入的[Subscribe]标签.
有多种方式可以创建动态对象:
添加一个现有对象到Context中
var instance:Object = ...;
var dynamicObject:DynamicObject = context.addDynamicObject(instance);
本例中ObjectDefinition会被创建为已有的实例并且元数据会处理这个类的实例,执行任何所需的依赖注入,消息接收器注册或者生命周期方法调用。
对象可以在任何时候从Context移除:
dynamicObject.remove();
在这一点上,该对象(如果存在)标记为[Destroy] 的方法会被调用,任何消息接收器注册将被终止。然后,对象就会完全从上下文删除。
基于在MXML或者XML的对象配置创建一个动态对象
动态对象也可以被配置在MXML或XML中,然后基于这些配置在运行时动态地创建:
<DynamicObject type="{SomeType}" id="dynamic1"/>
上述标签提供了同一组嵌套标签就像<Object>标签一样。然后你可以通过id将动态对象从上下文中取出。
var dynamicObject:DynamicObject = context.createDynamicObject("dynamic1");
或者选择通过类型获取
var dynamicObject:DynamicObject = context.createDynamicObjectByType(SomeType);
通过Context创建的实例可以这样访问:
var instance:Object = dynamicObject.instance;
添加了<DynamicObject>标签后,使用Object标签的singleton属性是弃用的:<Object type="{SomeType}" singleton="false"/>仍然会工作,但是现在会被编译得像<DynamicObject>标签一样,不管怎么样,它不再是必需了。<Object>标签现在应该只被用于声明单例.。
注入的动态对象生命周期
用<DynamicObject>标签声明的动态对象,是唯一可以注入到其他对象的动态对象的类型,如单例一样,因为他们的定义可以在上下文创建时期就是可用的并且有合法性检查。对象生命周期控制的方法在这种情况下,跟通过应用程序代码createDynamicObject来获取这些对象有所不同的。在后一种情况下,应用程序将控制生命周期,可能在任何时间点从上下文删除对象。对于注入则是框架控制了生命周期。只要对象是注入的,那么这个对象就是托管的。它像命令一样允许给动态对象增加注入。
以编程方式基于创建对象定义创建一个动态对象
为构建处理ObjectDefinitionDecorator 或者RootConfigurationElement 实现的对象定义的扩展,有两种相关的addDynamicObject方法的变体。首先可以传递一个额外的ObjectDefinition给方法。
var definition:ObjectDefinition = ...;
var instance:Object = ...;
var dynamicObject:DynamicObject = context.addDynamicObject(instance, definition);
本例中定义(definition )不会被动态上下文创建就像前面的例子,取而代之的是使用指定的定义(definition )。自2.2版以来,这种机制用做内部支持新的选项来在MXML中配置动态连接视图。在别处创建的现有实例必须被配置为ObjectDefinition。
最后,你也可以只传递一个definition到动态上下文,它会基于这个definition创建一个新的实例:
var definition:ObjectDefinition = ...;
var dynamicObject:DynamicObject = context.addDynamicDefinition(definition);
这个实例可以这样访问:
var instance:Object = dynamicObject.instance;
从Context删除一个动态对象
在所有这些不同的用例,删除对象的方法已经演示过了:
dynamicObject.remove();