本教程介绍 AOM 2.0中的模型事件,在阅读本文之前,我建议你首先阅读前几篇文章:
“Event/Listener”是我们常用的一种设计模式,其中最常见的莫过于控件事件了。写过 Swing 程序的同学应该都非常了解,譬如,我们可以调用JButton的addActionListener方法,这样的话,当 JButton被点击时,会自动回调你的listener方法。
但正是由于控件事件被用得非常广泛,以致于很多同学把事件当成是控件的一种专有属性,而忽略了“事件” 其实是一种典型的Observer模式,它原本可以用来解决更多的架构设计问题。本文,我们来介绍 AOM 2.0中的模型事件。 请注意,此处所说的事件,并不是指 AOM 中的组件事件,确切的说,这是一种后台的“模型事件”,并且,事件的发送与接收,AOM都为用户做了非常好的封装。
如果我们把事件模型分为两部分,演讲者(即事件起源)与听众(即Listener);那么,从程序的思维来看: 事件模型的显著特点是:一个演讲者可能会有多个听众,而听众可以任意的增加或删除。 因此,事件经常用来进行解耦。譬如说某一个业务数据发生变化,这种变化可能会影响到界面或者其它数据的变化, 但事先,我们并不知道会产生多少变化,于是我们就把“业务数据发生变化”打包成一个事件,谁对此事件感兴趣, 谁就注册并侦听此事件。
但同时,我们发觉,针对常规的事件实现方式,如果你要增加一个 Listener,首先必须要获得“事件发生处”的句柄,然后再通过此句柄显式的增加Listener,写成伪码就是: speaker.addListener(myListener)。无疑,这种实现方式存在一个明显的弊端,那就是必须要事先获得“事件发生地”的句柄, 这就意味着如果你要扩展程序功能,那么,你需要对旧有代码进行更改,而且,这种更改的成本还比较大。有没有更好的实现机制呢?
AOM 2.0中,提出了“Message Bus”的概念,即“消息总线”。发送消息是向“Message Bus”中发送,并且需要注明发送的消息类型;同时,任何人都可以随时从“Message Bus”中接听消息,并且可以通过消息类型来进行筛选,只接收你感兴趣的话题。你可以把“Message Bus”想象成全国的电视网络,电视台就是消息发送者,并且,不同的电视台播放不同类型的节目; 而如果你希望收看这些节目,那么,你只需要买台电视机,选择频道,即可收看到你感兴趣的节目。
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 原理,整个页面即可运行,效果如下:
![](http://www.operamasks.org/articles/magic-4/html_single/images/apply_1.jpg)
现在,我们做一个新的托管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.
程序员申请费用,现在项目经理和部门经理都通过事件机制知道此事了。我们希望在同一个方法中,对事件进行分类: 如果费用小于 等于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.
有时候会有这样一种需求:程序员申请费用,根据费用的大小,要自动导航到不同的页面。但有时候, 要重定向到哪些页面我们事先并不清楚。以上述场景为例:当项目经理审批时,希望导航到 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。
程序运行效果如下:
![](http://www.operamasks.org/articles/magic-4/html_single/images/apply_pm.jpg)
![](http://www.operamasks.org/articles/magic-4/html_single/images/apply_dm.jpg)
我们只是通过上述例子了解了AOM 2.0中模型事件的一些小特性,事实上,AOM 2.0中模型事件是比较完备的,还有许多其它特性我们并没有介绍,譬如,我们可以在 Action 中直接通过return 语句触发事件:
return "#applyFee(this.money)";
在 @RaiseEvent中,我们还可以引用传递给方法的参数:
@RaiseEvent("showResponse($1.name)")
public void loginSuccess(User user) {
...
}
这些特性,还请各位读者参考官方文档。