分类:
2010-04-30 11:29:26
parsley2引入了一个新的通用消息框架,允许你以完全解耦的方式在对象间传递消息。解耦意味着发送者与接收者可以相互不知道,同样重要的是,发送和
接收对象也完全从框架本身脱钩。这个优势被其它FLEX框架所忽略(包括parseley1),因此你不得不使用框架的对象或静态方法去发送程序的事件或 消息。如果你的对象与框架完全脱钩,你可以在不同的上下文中复用他们,那些上下文中可能你想使用不同的框架或根本就不想使用框架。比如你可能想在单元测试
中注入派发对象和接收一个实例,而不是进行额外的初始化。
Parsley消息框架很通用,它没有规定一个特殊的使用情况。这与一些现有的FLEX MVC框架不同,这些框架主张使用一定的结构和使用模式,甚至提供了具体的controller,model和view部分。parsley2可以让你自由的设计你的应用框架。如果你打算使用消息框架去建立一个经典的MVC架构,你可以去读一下 。
对于一个由parsley管理的想派发消息的对象,你可以阅读下列选项:
对于一个由parsley管理的想接收和处理消息的对象,你可以阅读下列选项:
如果你想派发由parsley托管的消息是一个普通的EventDispatcher,只需要下面二步:
Metadata Example
[Event(name="loginSuccess",type="com.bookstore.events.LoginEvent")]
[Event(name="loginFailed",type="com.bookstore.events.LoginEvent")]
[Event(name="stateChange",type="flash.events.Event")]
[ManagedEvents("loginSuccess,loginFailure")]
public class LoginServiceImpl extends EventDispatcher implements LoginService {
[...]
private function handleLoginResult (user:User) : void {
dispatchEvent(new LoginEvent("loginSuccess", user));
}
}
这个例子中,service声明了三个事件。其中的两个(loginSuccess和loginFailure)是应用程序事件,应该由parsley托管,派发这些事件有关的所有对象。第三个事件是一个低级事件,仅关注于与那个service直接交互的对象,这些对象只要注册一个普通的事件监听器。
MXML Example
如果你在MXML中声明了一个托管事件,你可以忽略这里的[ManagedEvents]元标签。注意,你所写的代码中必须包含[Event]元数据标签,因为那是用于表示flash API的。
XML Example
有时候你不想用通过事件调用模式开发。有时一些特殊场景的事件,parsley并不能"bubble",stopPropagation在parsley消息时序处理过程中没有任何作用。你甚至想避开消息接收者通过event.target捕获住消息的派发器。
class LoginMessage {
public var user:String;
public var role:String;
function LoginMessage (user:String
, role:String) {
this.user = user;
this.role = role;
}
}
你可以在service类中使用如下代码:
public class LoginServiceImpl implements LoginService {
[MessageDispatcher]
public var dispatcher:Function;
public function login(user:
String
, role:String
) : void {
dispatcher(new LoginMessage(user));
}
}
注意你的service类不用扩展EventDispatcher。这里只需要声明一个类型属性变量,并用[MessageDispatcher]标识,它将指示parsley在对象创建时,注入一个消息派发函数。你可以传递任何类型的对象给这个派发函数。
MXML Example
XML Example
如果你不想使用元数据标签,你可以通过MXML或XML配置。
消息处理器是接收端最常见的方法。当一个特殊的应用程序消息被捕获,你可以声明方法进行调用。通常,这个方法仅通过参数类型选择:
Metadata Example
[MessageHandler]
public function handleLogin (message:LoginMessage) : void {
无论何时一个匹配的消息类(包括子类)被捕获,将调用这个方法。
MXML Example
XML Example
这里还可以处理一个特殊情况,你分解一个消息的属性作为不同的消息处理器的参数:
[MessageHandler(type="com.bookstore.events.LoginMessage",messageProperties="user,role"]
public function handleLogin (user:User, role:String) : void {
注意:你必须声明消息类型,因为它不能由参数类型检查
最后,你可能遇到消息类的选择不足的问题。如果你在不同的场景和应用程序派发同样的消息类,你可能想更深入的定义消息选择过程。参见详细描述。
消息绑定一般用于以下场景:
直接把一个消息类中的属性赋给目标类的属性,一般过程是先声明一个消息处理函数,通过MessageHandler绑定该函数,然后再函数内将消息类的属性赋给相应属性,过程太麻烦。
如果你要将一个类的属性和一个消息属性绑定,消息绑定就很方便,即当一个匹配的消息类被捕获,框架自动根据消息属性更新类的属性。在下面的例子为例,用户属性与LoginMessage类的属性绑定,如下例:
Metadata Example
[MessageBinding(messageProperty="user",type="com.bookstore.events.LoginMessage")]
public var user:User;
MXML Example
XML Example
如果你想在MessageHandlers中使用
MessageBindings的选择功能,参见
这是接收端的第三种选择。当应用程序状态或用户自己决定消息是否传给处理器和绑定器时, 可通过拦截器实现。拦截器具有下列特征:
例:当你有未登录就可以访问的应用程序,一些行动没有登录则不让操作。在这种情况下可以暂停拦截消息处理,显示一个登录对话框,并成功登录后恢复的消息处理。
另一个例子:在任何一个删除操作之前显示一个简单的警告,通常如下:
public class DeleteItemInterceptor {
[MessageInterceptor(type="com.bookstore.events.ShoppingCartDeleteEvent")]
public function interceptDeleteEvent (processor:MessageProcessor) : void {
var listener:Function = function (event:CloseEvent) : void {
if (event.detail == Alert.OK) {
processor.proceed();
}
};
Alert.show("Do you really want to delete this item?", "Warning",
Alert.OK | Alert.CANCEL, null, listener);
}
}
当用户点击取消按钮时,MessageProcessor
将不会恢复执行,但处理器或绑定器将被执行。
正如MessageBindings,你必须声明消息类型,因为它不能根据方法签名检测。拦截方法总是有一个简单的MessageProcessor类型
参数。你也可以使用MXML或XML代替元数据标签进行声明。
MXML Example
XML Example
拦截作为消息框架的一个特征,框架必须传递MessageProcessor实例给你,你就能反控或处理消息
在2.1版本后,你可以设置一个方法去处理或拦截消息引发的错误:
[MessageError(type="com.bookstore.LoginEvent")]
public function handleError (processor:MessageProcessor, error:IOError) : void;
上例中,当LoginEvent类型的消息抛出一个IOError错误,这个方法将进行处理。你也可以创建一个全局的错误处理方法,接收任何消息引发的错误类型
[MessageError]
public function handleError (processor:MessageProcessor, error:Error) : void;
最后,因为错误处理器通过标签只能监听单一的范围,你可以添加一个错误处理器自动附加到每个应用程序中。你可以通过GlobalFactoryRegistry实现:
var provider:ObjectProvider = Provider.forInstance(this);
var handler:MessageErrorHandler
= new DefaultMessageErrorHandler(provider, "handleError", Object);
GlobalFactoryRegistry.instance.messageRouter.addErrorHandler(handler);
2.2版本的parsley框架也包含了几个由cairngorm3项目提出的设计思想。详见 .
标签集不仅不包括[MessageHandler]标签,还包括
多个不同类型的标签进行消息处理。通过
[Command]标签,你可以让框架去管理异步命令的执行:
[Inject]
public var service:RemoteObject;
[Command(selector="save")]
public function saveUser (event:UserEvent) : AsyncToken {
return service.saveUser(event.user);
}
[Command]标签支持与
[MessageHandler]标签相同的属性集。匹配消息的逻辑也一样。上例中,捕获UserEvent(包括子类)后,命令将执行,选择器save通常是指事件类型。
所不同的是,命令不要求有一个像普通消息处理器需要一个void返回类型。相反,它可以指定一个类型,该框架可以用它来为你管理异步命令执行。在这种情况下,它是这是由远程调用返回AsyncToken。现在,该框架还将为你管理的最终结果或错误。现在,任何对象都可以包含一个方法,用于处理调用的结果:
[CommandResult(selector="save")]
public function handleResult (user:User, event:UserEvent) : void {
上例中,User实例由远程调用返回,与触发动作的原始消息一起传给结果处理器。第二个参数是可选的,如果你忽略它,你必须用标签描述消息类型,以进行精确匹配:
[CommandResult(type="com.foo.events.UserEvent" selector="save")]
public function handleResult (user:User) : void {
这是必要的,就象普通消息处理器一样,parsley独立依赖于目标选择器的字符串标识符(事件类型)。它总是消息类型的混合体和带选项的选择器。
结果参数也是可选的,可以简单地提交:
[CommandResult(type="com.foo.events.UserEvent" selector="save")]
public function afterUserSaved () : void {
或者它可能是ResultEvent而不是实际结果值,如果你需要访问从服务器端返回的头部信息,可写成下面代码:
[CommandResult(selector="save")]
public function handleResult (result:ResultEvent, trigger:UserEvent) : void {
为了接收错误事件,其它标签也可以使用:
[CommandError(selector="save")]
public function handleResult (fault:FaultEvent, trigger:UserEvent) : void {
[CommandError]
参数也是可选的,匹配规则也[CommandResult]标签一样。
最后,你也可以观察执行命令的状态:
[CommandStatus(type="com.foo.events.UserEvent" selector="save")]
public var isSaving:Boolean;
如果一个或多个异步命令匹配描述的类型,并且选择器正在执行,这个Boolean类型总是True,否则为false。这个对于任务处理很方便,比如在命令执行期间将按钮置灰。
可支持的命令类型
上面的例子展示了执行远程调用的例子和返回一个AsyncToken。命令模式也可用于
Cinnamon Remoting,返回ServiceRequest
实例而不是FLEX的RemoteObjects。最后,命令模式与spicelib任务框架集成,一个命令也可将任务作为返回类型。
你可以通过你自己的CommandFactories扩展框架的限制,创建更多的命令标签,接口如下:
public interface CommandFactory {
function createCommand (returnValue:Object,
message:Object, selector:* = undefined) : Command;
}
方法createCommand的返回值是一个方法,该方法用于执行命令(比如AsyncToken),选择器触发命令。它也返回一个Command实例,大多数是
AbstractCommand
的子类,工厂实现类必须进行注册:
GlobalFactoryRegistry.instance.messageRouter
.addCommandFactory(MyReturnType, new MyCommandFactory());
addCommandFactory的声明如下:
function addCommandFactory (type:Class, factory:CommandFactory) : void;
它描述的命令的返回类型和工厂实例本身。
上节命令模式已展示了对象执行命令不会发生任何改变。标签[Command]
, [CommandResult]
和 [CommandError]可以用于任何一个托管对象的方法。它可以是一个单例对象或者一个视图。每个对象可以包含多个命令方法和多个结果处理器。结果处理器可能是相同对象或不同对象。总之,这些标签仅控制单个方法或属性。
框架允许定义一个命令。如果不应用标签给个单个方法,你可以对一个完整的命令对象进行配置。这个对象仅是单个方法,用于执行命令,或者是一个接收结果的方法和一个事件型的错误方法。结果处理器仅被执行同一个实例的命令所调用,这个命令对象可能象这样:
public class SaveUserCommand {
[Inject]
public var service:RemoteObject;
public function execute (event:UserEvent) : AsyncToken {
return service.saveUser(event.user);
}
public function result (user:User) : void {
// do something with the result
}
public function error (fault:Fault) : void {
// do something with the result
}
}
在配置类中定义[DynamicCommand]标签:
这是一个最小的配置,描述了选择器和消息类型。而且我们不一定在命令对象中使用常规的命令元数据标签。上节展示了选择方法中命令处理器、结果处理器和错误处理器的默认名,你也可以用DynamicCommand标签
重写。下例我们重写这三个方法名:
type="{SaveUserCommand}"
selector="save"
execute="saveUser"
result="handleUser"
error="handleFault"/>
标签也支持[MessageHandler]
或 [Command]
标签的所有属性:
type="{SaveUserCommand}"
selector="save"
scope="local"
order="1"/>
上例中,当触发消息被同一上下文的对象捕获,命令将先执行。order用于决定执行的顺序,但不能决定结果处理和错误处理的顺序。
除了特殊语法,
DynamicCommand
标签也允许象正常对象标签一样,拥有相同的子标签集:
当然,命令对象可以由其它对象的结果处理器组成。如果你使用了[CommandResult]标签,这与在一个方法上标识[Command]标签,或者
DynamicCommand标签声明过的命令对象
,其效果是一样的。尽管同一个命令对象拥有private结果处理器,任何一个对象都可以接收到结果。
命令对象生命周期
stateful="true"/>
现在,当第一个匹配消息被捕获,实例才被实例化,陏后,该实例将作为一个容器托管对象,当捕获新的消息后,该实例被恢复,以节省对象创建和初始化时间。对于有状态命令,你不能用私有属性去保存信息,因为多个命令将相互交互,并且覆盖那个值。
本例主要与MessageHandlers相关,MessageBindings和MessageInterceptors匹配方法或属性总是由消息类型决定。有时候,如果你在不同的场景或应用程序states中派发相同的消息,就出出现问题。你必须重新定义选择过程。
如果你使用了Event类的类型属性作为选择器:
[MessageHandler(selector="loginSuccess")]
public function handleLogin (message:LoginEvent) : void {
[...]
}
[MessageHandler(selector="loginFailure")]
public function handleError (message:LoginEvent) : void {
[...]
}
上例中,当
LoginEvent
实例的类型属性值为loginSuccess,handleLogin
方法将被调用。
对于自定义类型,不用扩展flash.events.Event。默认的选择器属性可以在消息类的属性上,用[Selector]元标签声明。
class LoginMessage {
public var user:User;
[Selector]
public var role:String;
[...]
}
现在,你可以根据用户的角色,选择消息处理器:
Metadata例子
[MessageHandler(selector="admin")]
public function handleAdminLogin (message:LoginMessage) : void {
MXML 例子
XML 例子
Parsley 2.0中,每个子上下文可以共享父上下文的消息路由。消息总是全局进行派发。这种做法将简化模块化开发和弹出窗体等。scope的引入将更灵活,允许派发上下文的子集。
全局和局部的范围
对于Parsley 2.1的默认范围是全局的范围,应用程序启动只创建一个根上下文,每个上下文根据全局范围创建,并没有父上下文,但可以被子上下文共享。另外,每个上下文将创建自己的局部上下文,这些可以不被子上下文共享,如下图所示:
因为全局范围唯一性,它是所有的配置标签的默认范围。
[MessageHandler(selector="save")]
public function save (event:ProductEvent) : void {
处理器监听任何一个上下文派发的ProductEvents事件。
[MessageHandler(selector="save", scope="local")]
public function save (event:ProductEvent) : void {
现在,处理器仅监听相同上下文派发的事件。当然,scope属性可用于所有的消息接收类型,比如MessageInterceptor
和MessageErrorHandler。
对于发送端,默认行为是不同的。对于每个[ManagedEvents]标签,scope并没有精确描述消息发送过程中经历的上下文。接收端可以决定它想监听的范围,如果你想限制发送端的范围,你可以在
[ManagedEvents]标签内使用属性scope:
[ManagedEvents("save,delete", scope="local")]
自定义范围
如果你即不想用全局消息,也不想用局部消息,你也能创建你自义的范围。下面假设你开发一个大型的AIR项目。根窗体组件可能以根应用上下文作为基础,创建一个新的上下文,这个窗体的部分组件也可以包含一些子上下文。你可以象下图所示,创建自定义上下文:
窗体范围是自定义的,存在两个并列的默认范围。现在,如何构建框架去创建范围?还是应该根据根上下文创建,可以用MXML描述如下:
或者添加scope到CompositeContextBuilder:
var builder:CompositeContextBuilder = new DefaultCompositeContextBuilder(viewRoot);
builder.addScope("window", true);
FlexContextBuilder.merge(MyWindowConfig, builder);
builder.build();
addScope的第一个参数是scope的名称,该名称必须唯一。这就允许你在消息处理器上定义scope的名称。
[MessageHandler(selector="save", scope="window")]
public function save (event:ProductEvent) : void {
第二个boolean参数表示子上下文是否能共享scope。
通常应用代码应避免直接与parsley API交互。但是特殊情况下或你想扩展框架,建立另一个新框架时,你可能需要注册消息处理器或自动绑定。MessageReceiverRegistry接口包含下列方法:
function addTarget (target:MessageTarget) : void;
function addInterceptor (interceptor:MessageInterceptor) : void;
function addErrorHandler (handler:MessageErrorHandler) : void;
function addCommand (command:CommandTarget) : void;
function addCommandObserver (observer:CommandObserver) : void;
消息接收器分成五类:MessageTarget
是普通接收器,实现类包括MessageHandler
和 MessageBinding。
MessageInterceptor与同名的标签对应,
MessageErrorHandler与标签
[MessageError]对应。
CommandTarget
是一个异步命令的target,CommandObserver监听结果或错误。接口包括五个方法,用于添加这五类接收器。
为了获取MessageReceiverRegistry
实例,你可以注入上下文实例到你的类中。你必须先确定你的接收器的scope,下面的例子是注册一个消息处理器为全局scope:
class SomeExoticClass {
[Inject]
public var context:Context;
[Init]
public function init () : void {
var registry:MessageReceiverRegistry
= context.scopeManager.getScope(ScopeName.GLOBAL).messageReceivers;
var target:MessageTarget
= new MessageHandler(Provider.forInstance(this), "onLogin");
registry.addMessageTarget(target);
}
}
当你在Context上设置了[Inject]标签,parsley将注入上下文实例给这个类。
最后,你可以使用ScopeManager
派发消息:
context.scopeManager.dispatchMessage(new LoginMessage(user, "admin"));
当直接通过ScopeManager
派发,消息将经过该context管理的所有scope。这样接收端可以决定哪个scope去监听。万一你不想让发送端选择,你必须通过个体的scope派发:
var scope:Scope = context.scopeManager.getScope(ScopeName.LOCAL);
scope.dispatchMessage(new LoginMessage(user, "admin"));
chinaunix网友2010-11-09 10:46:07
当用户点击取消按钮时,MessageProcessor将不会恢复执行,但处理器或绑定器将被执行。 我认为此处翻译的不准确,与原来意思正好相反。处理器或绑定器将不会被执行