Parsley 开发指南 6 消息传递

来源:互联网 发布:淘宝客推广工具手机版 编辑:程序博客网 时间:2024/04/27 14:56

6 消息传递

Parsley有一个通用的消息传递框架,该框架允许你用一个完全分离的方式交换对象之间的消息。解耦不仅仅意味着发送方和接收方不需要知道彼此。发送和接收对象也完全对框架本身解耦也是同样重要的。这是一个被大多数Flex框架(包括Parsley的第一个版本)忽略了的优点,你必须使用框架API的对象或静态方法,来分派应用程序事件或消息。这个为什么重要?如果你的对象对框架来说是解耦的,你可以在不同的上下文中重用它们,你可能想使用一个不同的框架或没有框架。例如,你可能想以编程方式分派和接收实例来进行单元测试,没有初始化一个应用程序框架的额外的负担。

Parsley消息传递框架在某种意义上是通用的,它不强制要求特定的使用风格。这也是一个不同的地方,一些现有的Flex MVC框架经常提倡一个特定的结构和使用模式或甚至提供控制器,模型和视图部分具体的基类。用Parsley你完全自由地设计应用程序体系结构。如果你使用消息传递框架来构建一个典型的MVC架构除了这一章你可能还想读 11 构建MVC架构

这一章描述了如何配置对象发送和接收消息。每一个例子都包含AS3元数据,MXML和XML配置的配置选项。

6.1 派发消息

对于一个想派发消息的Parsley托管对象,你有以下设置选项:

6.3 管理的事件 如果派发的对象是一个常规的EventDispatcher .

6.4 注入的 MessageDispatchers 如果你的消息不是Event的子类

6.11 使用消息传递API 在极少数情况下你需要直接使用框架API

6.2 接受消息

对于一个想接受和处理消息的Parsley托管对象,你有以下设置选项:

l 6.5 消息处理(MessageHandlers) 当一个特定的消息派发的时候应该被调用的方法。

l 6.6 消息绑定(MessageBindings) 当一个特定的消息派发的时候应该被设置的属性。

l 6.7 拦截消息(Intercepting Messages ) 关于拦截,在消息被处理或者绑定之前,可选的取消或者推迟它,然后重新派发消息。

6.8 错误处理 处理其他handlers bindings 或者interceptors抛出的错误

7.2 映射消息到命令 映射命令消息,在execute方法中接受消息,并且让命令的生命周期绑定到它的执行时间

6.11 使用消息传递API 在极少数情况下,你需要使用框架API来注册以上列出的特性。

6.3 托管事件

如果你想派发消息的被Parsley托管的类是一个常规的EventDispatcher ,这是最方便的选择。它可用于任何现有的EventDispatcher的实现,即使这并不是用Parsley设计的。它需要两个步骤:

l 用普通的[Event] 标签声明你的类要分派的事件,无论如何这是一种好做法,不管你是不是用的Parsley,因为他提高了你的类的可读性并且ASDoc输出包含了所有这个标签声明的事件。

l 告诉Parsley哪些声明的事件应该被“托管”,这样他们可用于框架的消息接收器配置。像所有消息传递功能一样,这一步你可以使用AS3元数据,XMLM或者XML标签。

元数据样例

[Event(name="loginSuccess"type="com.bookstore.events.LoginEvent")]

[Event(name="loginFailed"type="com.bookstore.events.LoginEvent")]

[Event(name="stateChange"type="flash.events.Event")]

[ManagedEvents("loginSuccessloginFailure")]

public class LoginServiceImpl extends EventDispatcher implements LoginService {

    [...]

    

    private function handleLoginResult (user:User) : void {

        dispatchEvent(new LoginEvent("loginSuccess" user));

    }

    

}

在上面的例子中,服务声明了三个事件,其中两个(loginSuccess和loginFailure)是应用程序事件,需要被Parsley管理并且派发给上下文中所有与该事件关联的对象。第三个是一个低级事件只对对象直接与服务交互。这些对象可能仍然为那个事件注册一个常规事件侦听器。

上面例子中的方法展示了结果处理(这可能是为一个远程服务调用注册的处理程序)将结果写入一个事件和简单的分派它。没有FrontController.getInstance().dispatch...或者任何其他类似的东西。自从loginSuccess被声明为托管事件,他就会被传递到被Parsley管理的所有MessageHandlers中。

MXML Example

<Object type="{LoginServiceImpl}">

<ManagedEvents names="['loginSuccess''loginFailure']"/>

</Object>

如果你在MXML中声明托管事件,相对前面的例子来说你可以省略[ManagedEvents]元数据标签。注意你仍然需要包含[Event]元数据标签,因为那些不是Parsley 的配置工件,是常规Flash API的特性。

XML Example

<object type="com.bookstore.services.LoginServiceImpl">

<managed-events names="loginSuccessloginFailure"/>

</object>

和MXML 配置非常相似除了几个符号的差异。

6.4 注入的 MessageDispatchers

有时候你不想为你的应用程序消息使用事件。不知何故一些事件的语义可能在特定场景中没有多大意义。被Parsley托管的应用程序事件不能‘冒泡’,stopPropagation在Parsley消息处理序列中不会有任何效果,并且对于完全解耦的消息传递来说,你也许甚至希望避免消息接收器可以通过event.target得到message dispatcher。

在这些情况下Parsley可以使用任何类作为应用程序消息,无论它是否继承于flash.events.Event。然后,你可以请求框架注入一个消息分发器函数,你可以使用你的自定义应用程序的信息。假设你创建了以下简单的消息类:

class LoginMessage {

    public var user:User;

    

    public var role:String;

    

    function LoginMessage (user:User role:String) {

        this.user = user;

        this.role = role;

    }

    

}

你可以在一个服务中这样使用:

public class LoginServiceImpl implements LoginService {

    [MessageDispatcher]

    public var dispatcher:Function;

    [...]

    

    private function handleLoginResult (user:User) : void {

        dispatcher(new LoginMessage(user));

    }

    

}

现在你的服务不继承于EventDispatcher。相反,它声明了一个Function 类型的变量,用[MessageDispatcher]标签注解,指示Parsley在对象创建的时候注入一个消息分发器函数。然后你可以简单地通过这个分配器函数传递任何类型的对象。

MXML Example

<Object type="{LoginServiceImpl}">

    <MessageDispatcher property="dispatcher"/>

</Object>

XML Example

<object type="com.bookstore.services.LoginServiceImpl">

<message-dispatcher property="dispatcher"/>

</object>

如果你不想使用元数据标签还可以用MXML或者XML配置请求分派器注入。

6.5 消息处理

消息处理程序是接受端最常见的方法。你可以声明方法,当特定应用程序消息派发的时候会得到调用。在最简单的情况下,方法将简单地通过参数类型选择:

Metadata Example

[MessageHandler]

public function handleLogin (message:LoginMessage) : void {

在这种情况下,每当匹配的类型(或子类型)的消息被分发的时候,该方法将被调用。

MXML Example

<Object type="{LoginAction}">

    <MessageHandler method="handleLogin"/> 

</Object>

XML Example

<object type="com.bookstore.actions.LoginAction">

<message-handler method="handleLogin"/> 

</object>

还有一种方式,当你分割消息类的属性到消息处理程序方法的参数:

[MessageHandler(type="com.bookstore.events.LoginMessage"messageProperties="userrole"]

public function handleLogin (user:User role:String) : void {

注意,在这种情况下你还必须声明消息类型,因为它不能从参数类型被探测到。

最后你可能会遇到这样一种情况,通过消息类型选择是不够的。如果你在不同的场景和应用程序状态中发送相同的消息类型,你可能想要进一步改进该消息选择过程。详情见6.9 使用选择器

6.6 消息绑定

消息绑定仅仅是一个快捷方式,绑定类的一个属性到消息的一个属性,每当匹配类型的消息派发的时候,它都会被更新。在以下示例中,示例中的user 属性会被LoginMessage 实例的user属性设值,无论这个消息什么时候派发。

Metadata Example

[MessageBinding(messageProperty="user"type="com.bookstore.events.LoginMessage")]

public var user:User;

MXML Example

<Object type="{LoginServiceImpl}">

    <MessageBinding 

        targetProperty="user" 

        messageProperty="user"

        type="{LoginMessage}"

    />

</Object>

XML Example

<object type="com.bookstore.services.LoginServiceImpl">

    <message-binding 

        target-property="user" 

        message-property="user"

        type="com.bookstore.events.LoginMessage"

/>

</object>

MessageHandlers 你可能希望MessageBindings选择器,详情见 6.9使用选择器

6.7 拦截消息

这是接收端的一个可选的属性,当你想要基于应用程序的状态或用户的决策来决定消息是否应该传递给剩下的处理程序的时候,拦截器可能会派上用场。拦截器有以下特点:

l 所有注册的拦截器在任何处理程序执行或绑定之前执行(除非你显式的设置order属性)

l 拦截器可以选择暂停消息处理和可以在稍后的时间恢复

自2.4版以来任何类型的消息接收器函数都可以作为一个拦截器。对于一个MessageHandler,举例来说唯一的区别就是你在处理器的方法签名中包含了可选的MessageProcessor参数:

[MessageHandler]

public function intercept (msg:LoginMessage processor:MessageProcessor) : void {

这同样可以应用于一个[CommandResult]或者其他类型的消息接受者.第一个参数用于你感兴趣的匹配的消息类型。就像一个正常的消息处理程序。

一个简单的例子,你可能想要使用这样一个拦截器,无论任何类型的交互,用户首先必须确定在对话框的行动。在这种情况下拦截器可以暂停消息的处理,展现对话框,并在用户点击“确定”后恢复消息处理。

实现一个简单的警告示例:

public class DeleteItemInterceptor {

 

    [MessageHandler]

    public function deleteItem (msg:DeleteItemMessage processor:MessageProcessor) : void {

        processor.suspend();

        var listener:Function = function (event:CloseEvent) : void {

            if (event.detail == Alert.OK) {

                processor.resume();

            }  

            else {

                processor.cancel();

            }

        };      

        Alert.show("Do you really want to delete this item?" "Warning" 

            Alert.OK | Alert.CANCEL null listener);

    }

 }

当用户点击取消,MessageProcessor就再也不会恢复,并且没有后续处理程序会被执行。

推荐少使用拦截器,因为它是消息传递框架唯一使用Parsley API的特性,我们必须传递一个MessageProcessor 的实例给你,以便你可以取消或暂停消息处理。

旧的[MessageInterceptor]标签用于2.0版本,2.3版本已经被弃用。为了向后兼容,它将继续在2.4版本中工作。取而代之的是在这一节中解释的新机制,因为它更灵活(任何类型的接收器现在可以充当一个拦截器)和更多的类型安全(旧的拦截器经常在元数据标签用一个字符串来声明他们感兴趣的消息)。

6.8 错误处理

Parsley可以配置一个方法,当对应的消息处理器抛出一个错误的时候会被调用。

[MessageError]

public function handleError (error:IOError message:LoginMessage) : void;

在上述例子中,每当任何LoginMessage消息处理程序抛出一个IOError的时候,错误处理程序将被调用。所以你可以选择匹配的消息类型和匹配的错误类型,当然,像所有的标签一样,指定一个额外的选择器属性。

但是你也可以对任何类型的错误和任何类型的信息创建一个全局处理程序:

[MessageError]

public function handleError (error:Error) : void;

如同所有的消息处理程序一样,一个错误处理程序也可以接受一个MessageProcessor类型的参数,在你想取消或暂停处理或发送消息给发送者上下文的时候:

[MessageError]

public function handleError (error:Error processor:MessageProcessor) : void;

只有第一个参数error是必需的,之前的(LoginMessage)=和 MessageProcessor都是可选的。

最后,因为上面用标签配置的错误处理程序总是监听单个作用域,如果想添加一个错误处理程序自动附加到应用程序的每个作用域。你可以用BootstrapDefaults通过编程的方式来做。

var handler:DefaultMessageErrorHandler = new DefaultMessageErrorHandler();

var method:Method = ClassInfo.forInstance(this).getMethod("handleError");

handler.init(Provider.forInstance(this) method);

BootstrapDefaults.config.messageSettings.addErrorHandler(handler);

6.9 使用选择器

在列举的关于MessageHandlers,MessageBindings和MessageInterceptors的部分中,匹配消息的方法或属性总是完全由类型(类)决定。有时这可能是不够的,如果你在不同的场景或应用程序状态发送相同的消息类型。在这种情况下你可以用自定义选择器改进选择过程。

如果你使用的是Event ,Event 类的type属性可以作为选择器:

[MessageHandler(selector="loginSuccess")]

public function handleLogin (message:LoginEvent) : void {

    [...]

}

[MessageHandler(selector="loginFailure")]

public function handleError (message:LoginEvent) : void {

[...]

}

在上面的例子中handleLogin 方法只会在LoginEvent实例的type 的值是loginSuccess的时候才会被调用。

对于不继承于 flash.events的自定义消息类型。事件没有默认的选择器属性,但是它可以很容易的用[Selector]元数据标签在消息类的属性上声明。

class LoginMessage {

    public var user:User;

    

    [Selector]

    public var role:String;

    

[...]

}

现在你可以基于用户登录的角色选择消息处理程序:

Metadata Example

[MessageHandler(selector="admin")]

public function handleAdminLogin (message:LoginMessage) : void {

MXML Example

<Object type="{AdminLoginAction}">

    <MessageHandler method="handleAdminLogin" selector="admin"/> 

</Object>

XML Example

<object type="com.bookstore.actions.AdminLoginAction">

<message-handler method="handleAdminLogin" selector="admin"/> 

</object>

6.10 使用作用域

作用域特性为定义自定义的交流空间增加了灵活性,允许在应用程序特定区域派发消息(例如一个窗口,弹出或选项卡),而不仅仅是在全局作用域。

Global and Local Scopes

Parsley设置的默认作用域是为每个没有父亲的Context (通常应用程序启动时只有一个根Context 创建)创建的全局作用域,然后共享给那个Context 的孩子(当然也包括孙子)。另外,每个上下文将创建自己的本地作用域,它将不会与其孩子共享。下面的图显示了这个默认设置:

全局作用域是没有显式指定scope的配置标签的默认作用域。

[MessageHandler(selector="save")]

public function save (event:ProductEvent) : void {

上面的处理器监听从任何上下文层次结构派发的ProductEvents 。它监听全局作用域。

[MessageHandler(selector="save" scope="local")]

public function save (event:ProductEvent) : void {

现在,处理程序只监听从相同上下文派发的事件。

当然所有类型的消息接收器的标签都接受scope属性,包括MessageBinding和MessageErrorHandler。

在多上下文应用程序中推荐的默认作用域设置

推荐在一个上下文的应用程序和在多上下文的根上下文应用程序使用全局作用域。在一个多上下文的应用程序中的根上下文中的对象常常表现全局服务,这个服务需要对从子上下文中的对象派发的消息作出反应。因此全局作用域是一种方便的默认行为。子上下文(那些专门为应用程序的特定区域创建的,比如一个弹出,选项卡或模块),建议切换到本地作为默认的设置,以便在这些孩子的对象默认使用一个本地的私人交流的空间。

这是如何为整个上下文切换缺省作用域:

<parsley:ContextBuilder config="...">

<parsley:MessageSettings defaultReceiverScope="local"/>

</parsley:ContextBuilder>

现在当配置标签没有显式地指定作用域的时候,默认作用域是local作用域。

默认的实际上对上下文和它所有的孩子都起作用,除非再为一个子上下文做覆盖。

派发器的默认作用域

发送方的默认行为是不同的。对于任何[MessageDispatcher]或[ManagedEvents]标签,没有显式地指定作用域的消息将通过特定上下文中所有可用的作用域派发。通过这种方式,接收方可以决定它想监听哪些作用域,允许全局和本地接收器处理同一消息的实例。例如你甚至想限制发送端到单个作用域, [ManagedEvents]标签也有一个新的scope 属性。

[ManagedEvents("savedelete" scope="local")]

自定义作用域

最后,你还可以创建自定义作用域,当无论是全局还是本地消息传递都是正确的选择。这种情况可能发生在你创建一个大型AIR window的时候。根窗口组件可能会用根应用程序上下文作为父亲创建一个上下文,但后来在该窗口部件中也有一些子上下文。如果你仍然想为窗口设置一个消息传递的作用域,你只需要跨越多个上下文的作用域,但仍然不是全局性的。自2.1版以来,你可以创建一个作用域设置像下面的示例图:


 window 作用域是自定义的作用域,他有两个并行的默认作用域。现在你怎么指导框架来创建这个作用域?这必须用根上下文的作用域来完成,在本例中,两个根上下文对于两个window作用域。在MXML你可以指定作用域如下:

<parsley:ContextBuilder>

    <parsley:FlexConfig type="{ServiceConfig}"/>

    <parsley:FlexConfig type="{ControllerConfig}"/>

    <parsley:XmlConfig file="logging.xml"/>

<parsley:Scope name="window" inherited="true"/>

</parsley:ContextBuilder>

或者用ContextBuilder  DSL的编程方式添加作用域:

var viewRoot:DisplayObject = ...;

ContextBuilder.newSetup()

    .viewRoot(viewRoot)

    .scope("window")

    .newBuilder()

        .config(FlexConfig.forClass(ServiceConfig))

        .config(FlexConfig.forClass(ControllerConfig))

        .config(XmlConfig.forFile("logging.xml"))

        .build();

作用域的名称不必是惟一的,只要你确保具有相同名称的两个范围不会重叠。这是方便的,因为它允许定义一个window作用域的消息处理程序,无需考虑它属于哪个窗口实例:

[MessageHandler(selector="save" scope="window")]

public function save (event:ProductEvent) : void {

第二个boolean参数指定是否与子上下文共享作用域。所以你也可以创建自定义局部作用域,尽管这可能是一个相当不同的用例。

6.11 使用消息传递API

在普通的应用程序代码中,你应该尽量避免与Parsley API直接交互来保持你的类与框架解耦。但在一些边界情况或者如果你想扩展框架或在上面构建另一个框架,你可能想要以编程方式注册message handlers or bindings。MessageReceiverRegistry接口包含以下方法来注册:

function addTarget (target:MessageTarget) : void;

function addErrorHandler (handler:MessageErrorHandler) : void;

function addCommandObserver (observer:CommandObserver) : void;

有三种消息接收器: MessageTarget 是常规的接收器,实现类包括MessageHandler MessageBinding<MapCommand> 也是一个常规的Target。MessageErrorHandler对应于[MessageError]标签,最后CommandObserver 监听一个命令的结果或错误。这个接口包含三个方法来删除这三个接收器类型。

你可以注入一个Context的实例到你的类中来得到MessageReceiverRegistry 的实例,然后你必须为你的接收器想应用的作用域选择一个注册器。在接下来的例子中我们对全局作用域注册一个消息处理程序。

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);    

}

}

当你把一个[Inject]元数据标签放在Context 类型的属性上时,Parsley总是会注入被管理的Context的实例。

最后你可以使用ScopeManager 来派发消息:

context.scopeManager.dispatchMessage(new LoginMessage(user "admin"));

当直接通过ScopeManager 派发的时候,消息将被派发到所有的被这个上下文管理的作用域(默认只有local和global作用域,但是你可以创建你的自己的 ),通过这种方式接收方可以决定监听哪些作用域。

var scope:Scope = context.scopeManager.getScope(ScopeName.LOCAL);

scope.dispatchMessage(new LoginMessage(user "admin"));

原创粉丝点击