AOM 2.0的神奇魔力之四:模型事件

AOM 2.0的神奇魔力之四:模型事件

Kevin

1.前言

本教程介绍 AOM 2.0中的模型事件,在阅读本文之前,我建议你首先阅读前几篇文章:

AOM 2.0的神奇魔力之一:约定优于配置

AOM 2.0的神奇魔力之二:国际化

AOM 2.0的神奇魔力之三:输入校验

2.从更高角度看事件

“Event/Listener”是我们常用的一种设计模式,其中最常见的莫过于控件事件了。写过 Swing 程序的同学应该都非常了解,譬如,我们可以调用JButton的addActionListener方法,这样的话,当 JButton被点击时,会自动回调你的listener方法。

但正是由于控件事件被用得非常广泛,以致于很多同学把事件当成是控件的一种专有属性,而忽略了“事件” 其实是一种典型的Observer模式,它原本可以用来解决更多的架构设计问题。本文,我们来介绍 AOM 2.0中的模型事件。 请注意,此处所说的事件,并不是指 AOM 中的组件事件,确切的说,这是一种后台的“模型事件”,并且,事件的发送与接收,AOM都为用户做了非常好的封装。

3.事件的特性分析

如果我们把事件模型分为两部分,演讲者(即事件起源)与听众(即Listener);那么,从程序的思维来看: 事件模型的显著特点是:一个演讲者可能会有多个听众,而听众可以任意的增加或删除。 因此,事件经常用来进行解耦。譬如说某一个业务数据发生变化,这种变化可能会影响到界面或者其它数据的变化, 但事先,我们并不知道会产生多少变化,于是我们就把“业务数据发生变化”打包成一个事件,谁对此事件感兴趣, 谁就注册并侦听此事件。

但同时,我们发觉,针对常规的事件实现方式,如果你要增加一个 Listener,首先必须要获得“事件发生处”的句柄,然后再通过此句柄显式的增加Listener,写成伪码就是: speaker.addListener(myListener)。无疑,这种实现方式存在一个明显的弊端,那就是必须要事先获得“事件发生地”的句柄, 这就意味着如果你要扩展程序功能,那么,你需要对旧有代码进行更改,而且,这种更改的成本还比较大。有没有更好的实现机制呢?

4.Message Bus:消息总线

AOM 2.0中,提出了“Message Bus”的概念,即“消息总线”。发送消息是向“Message Bus”中发送,并且需要注明发送的消息类型;同时,任何人都可以随时从“Message Bus”中接听消息,并且可以通过消息类型来进行筛选,只接收你感兴趣的话题。你可以把“Message Bus”想象成全国的电视网络,电视台就是消息发送者,并且,不同的电视台播放不同类型的节目; 而如果你希望收看这些节目,那么,你只需要买台电视机,选择频道,即可收看到你感兴趣的节目。

5.场景分析

AOM 2.0的消息总线是如何使用的呢?我们假设这样一种场景:某公司的一位程序员进行费用申请,他申请的费用会经过所属项目组的项目经理的审批。 如果转换成消息模型那就是:“申请费用”是一个事件,而“项目经理“对此感兴趣。

为了让我们的视线关注于事件本身,程序代码我们尽可能的精简:

<f:view xmlns="http://www.w3.org/1999/xhtml" xmlns:f="http://java.sun.com/jsf/core"
xmlns:w="http://www.apusic.com/jsf/widget" xmlns:layout="http://www.apusic.com/jsf/layout"
renderKitId="AJAX">
<w:page title="apply fee">
<w:form>
<layout:panelGrid columns="2">
<w:textField id="money"></w:textField>
<w:button id="apply" />
</layout:panelGrid>
</w:form>
</w:page>
</f:view>

后台的 ApplyBean 代码片断如下:

@ManagedBean(name="ApplyBean", scope=ManagedBeanScope.SESSION)
public class ApplyBean {

@Bind
private int money = 100;

@Action
public void apply(){
System.out.println("I'm a programmer, I apply " + money + " money");
}
}

基于 IoVC 原理,整个页面即可运行,效果如下:

现在,我们做一个新的托管Bean:ProjectManagerBean,代码如下:

@ManagedBean(name="ProjectManagerBean", scope=ManagedBeanScope.SESSION)
public class ProjectManagerBean {

private void applyFeeListener(int money) {
System.out.println("I'm your project manager, you apply " + money + " money.");
}
}

那么,我们如何通过事件机制,让这两个方法关联起来呢?

首先是发送事件,我们将代码修订如下:

@Action
@RaiseEvent("applyFee(this.money)")
public void apply(){
System.out.println("I'm a programmer, I apply " + money + " money");
}

请注意,我们增加了一个@RaiseEvent 的annotation,它的参数中:“applyFee”代表事件类型,“this.money”代表传递的参数。

ProjectManagerBean是怎样注册成为Listener的呢?

@EventListener("applyFee")
private void applyFeeListener(int money) {
System.out.println("I'm your project manager, you apply " + money + " money.");
}

上述代码含义就是:通过@EventListener这个annotation标明:applyFeeListener方法是个事件监听器方法,并且, 它所感兴趣的事件类型是:applyFee。

好了,我们来看一下程序运行的输出结果:

2008-03-01 14:57:30 信息 [con.out] I'm a programmer, I apply 100 money
2008-03-01 14:57:30 信息 [con.out] I'm your project manager, you apply 100 money.

两者之间已经通过事件关联起来了。

现在我们需要扩展一下:除了项目经理感兴趣,部门经理对此事也感兴趣,那么,我们根本无需更改任何代码,只是新增一个新的 DepartManagerBean:

@ManagedBean(name="DepartManagerBean", scope=ManagedBeanScope.SESSION)
public class DepartManagerBean {

@EventListener("applyFee")
private void applyFeeListener(int money) {
System.out.println("I'm your department manager, you apply " + money + " money.");
}
}

程序运行的输出结果如下:

2008-03-01 15:01:03 信息 [con.out] I'm a programmer, I apply 100 money
2008-03-01 15:01:03 信息 [con.out] I'm your department manager, you apply 100 money.
2008-03-01 15:01:03 信息 [con.out] I'm your project manager, you apply 100 money.

6.高级用法:EventBroadcaster

程序员申请费用,现在项目经理和部门经理都通过事件机制知道此事了。我们希望在同一个方法中,对事件进行分类: 如果费用小于 等于100,那么,就发送一个 applyLittleFee 的事件,如果费用大于100,则发送一个 applyMuchFee 的事件;项目经理关注 applyLittleFee 事件,而部门经理关注 applyMuchFee 的事件。ApplyBean修订如下:

public class ApplyBean {
@Bind
private int money = 100;

@Inject
private EventBroadcaster event;

@Action
public void apply(){
System.out.println("I'm a programmer, I apply " + money + " money");
if(money <=100) event.broadcast(this, "applyLittleFee", money);
else event.broadcast(this, "applyMuchFee", money);
}
}

此处的 EventBroadcaster (事件发送对象) 被注入,通过此对象,你对“发送什么事件”、“何时发送事件”都能够进行详细的控制。

ProjectManagerBean与DepartManagerBean修订如下:

@ManagedBean(name="ProjectManagerBean", scope=ManagedBeanScope.SESSION)
public class ProjectManagerBean {

@EventListener("applyLittleFee")
private void applyFeeListener(int money) {
System.out.println("I'm your project manager, you apply " + money + " money.");
}
}
@ManagedBean(name="DepartManagerBean", scope=ManagedBeanScope.SESSION)
public class DepartManagerBean {

@EventListener("applyMuchFee")
private void applyFeeListener(int money) {
System.out.println("I'm your department manager, you apply " + money + " money.");
}
}

请注意,ProjectManagerBean及DepartManagerBean所感兴趣的事件也做了相应的修订,程序运行输出如下:

2008-03-01 15:10:10 信息 [con.out] I'm a programmer, I apply 100 money
2008-03-01 15:10:11 信息 [con.out] I'm your project manager, you apply 100 money.
2008-03-01 15:10:14 信息 [con.out] I'm a programmer, I apply 1000 money
2008-03-01 15:10:14 信息 [con.out] I'm your department manager, you apply 1000 money.

7.高级用法:基于事件的导航

有时候会有这样一种需求:程序员申请费用,根据费用的大小,要自动导航到不同的页面。但有时候, 要重定向到哪些页面我们事先并不清楚。以上述场景为例:当项目经理审批时,希望导航到 projectManager.xhtml页面, 当部门经理审批时,希望导航到 departManager.xhtml页面。在 AOM 2.0中,如何解决此需求?

我们首先完成这两个页面:

projectManager.xhtml:

<f:view xmlns="http://www.w3.org/1999/xhtml" xmlns:f="http://java.sun.com/jsf/core"
xmlns:w="http://www.apusic.com/jsf/widget" xmlns:layout="http://www.apusic.com/jsf/layout"
renderKitId="AJAX" xmlns:h="http://java.sun.com/jsf/html">
<w:page title="project manager">
I'm your project manager, you apply <h:outputText id="money"/> money.
</w:page>
</f:view>

departManager.xhtml

<f:view xmlns="http://www.w3.org/1999/xhtml" xmlns:f="http://java.sun.com/jsf/core"
xmlns:w="http://www.apusic.com/jsf/widget" xmlns:layout="http://www.apusic.com/jsf/layout"
renderKitId="AJAX" xmlns:h="http://java.sun.com/jsf/html">
<w:page title="project manager">
I'm your depart manager, you apply <h:outputText id="money"/> money.
</w:page>
</f:view>

再对 ProjectManagerBean及DepartManagerBean修订如下:

@ManagedBean(name="ProjectManagerBean", scope=ManagedBeanScope.SESSION)
public class ProjectManagerBean {

@Bind
private int money;

@EventListener("applyLittleFee")
private String applyFeeListener(int money) {
this.money = money;
return "view:projectManager";
}
}
@ManagedBean(name="DepartManagerBean", scope=ManagedBeanScope.SESSION)
public class DepartManagerBean {

@Bind
private int money;

@EventListener("applyMuchFee")
private String applyFeeListener(int money) {
this.money = money;
return "view:departManager";
}
}

请注意观察:return "view:projectManager"语句中,前面的 view 意即告诉AOM,这是要导航,后面的projectManager代表 view 的ID。

程序运行效果如下:

8.其它高级特性

我们只是通过上述例子了解了AOM 2.0中模型事件的一些小特性,事实上,AOM 2.0中模型事件是比较完备的,还有许多其它特性我们并没有介绍,譬如,我们可以在 Action 中直接通过return 语句触发事件:

return "#applyFee(this.money)";

在 @RaiseEvent中,我们还可以引用传递给方法的参数:

@RaiseEvent("showResponse($1.name)")
public void loginSuccess(User user) {
...
}

这些特性,还请各位读者参考官方文档。

9.总结

本章节,我们简单体验了 AOM 2.0 中“模型事件”的概念。笔者以为:事件是一把双刃剑,运用得好,那么,能够大大增强程序的灵活性及可扩展性; 但如果滥用事件,则只会让你的程序难以跟踪,难以理解。

你可能感兴趣的:(设计模式,Ajax,XHTML,JSF,sun)