传统的observer(事件-监听)机制一般使用 比较直观的一种是使用一种“注册——通知——撤销注册”的形式。但是这种形式可以通过一个纯被观察对象的纯虚接口类实现依赖倒置实现解耦,但是事实还是有一定的耦合,比如对象的生存周期就通过注册和撤销注册耦合了。
前言
一直以来,都对异步事件很感兴趣,比如一个应用在运行一个耗时的过程时,最好的方式是提交这个耗时的过程给一个专门的工作线程,然后立即返回到主线程上,进行其他的任务,而工作线程完成耗时任务后,异步的通知主线程,这个过程本身是很有意思的。传统的事件-监听器模型可以较好的解决这个问题,不过事件和监听器两者的耦合往往略显紧密,所以需要另一种实现,使得这两者的耦合尽量小,那样模块可以比较通用。
总线模式
前几天跟同事讨论了下Swing中的消息机制,同事给我讲了下总线模式的消息机制,感觉很有意思,于是周末就自己实现了下。具体的思路是这样的:
- 系统中存在一个消息服务(Message Service),即总线
- 监听器对象,通过实现一个可被通知的对象的接口,将自己注册在消息服务上
- 可被通知的对象可以向消息总线上post消息,就这个对象而言,它对其他注册在总线上的对象是一无所知的
- 消息服务进行消息的调度和转发,将消息(事件)发送给指定的对象,从而传递这个异步事件
这个思路最大的好处是,事件被抽象成消息(Message),具有统一的格式,便于传递。挂在总线上的监听器互相不知道对方的存在,监听器可以指定自己感兴趣的消息类型,消息可以是广播的形式,也可以是点对点的。(后来参看了下JMS,其中有pub/sub的模式(即订阅模式),不过,对于异步消息的传递来说,这个可以不必实现)
消息服务
消息服务可以将一大堆分布在不同物理机上的应用整合起来,进行通信,可以将一些小的应用整合为一个大的,可用的应用系统。
用一个例子来说吧:
- public class Test{
- public static void main(String[] args) throws RemoteException{
- /*
- * 创建一个可被通知的对象(监听器), 这个监听器关注这样几个事件
- * TIMEOUT, CLOSE, and READY
- */
- Configuration config = new RMIServerConfiguration(null, 0);
- CommonNotifiableEntry entry1 =
- new CommonNotifiableEntry(config, "client1",
- MessageTypes.MESSAGE_TIMEOUT |
- MessageTypes.MESSAGE_CLOSE |
- MessageTypes.MESSAGE_READY);
- /*
- * 创建另一个监听器, 这个监听器关注这样几个事件
- * OPEN, CLOSE, and TIMEOUT.
- */
- CommonNotifiableEntry entry2 =
- new CommonNotifiableEntry(config, "client2",
- MessageTypes.MESSAGE_OPEN |
- MessageTypes.MESSAGE_CLOSE |
- MessageTypes.MESSAGE_TIMEOUT);
- // 将监听器挂在BUS上
- entry1.register();
- entry2.register();
- // 创建一个新的消息, MESSAGE_OPEN类型.
- Message msg = new CommonMessage(
- entry1.getId(),
- entry2.getId(),
- MessageTypes.MESSAGE_OPEN,
- "busying now");
- // 传递给entry2
- entry1.post(msg);
- // 创建一个MESSAGE_CLICKED类型的消息, entry2
- // 不关注这个类型的消息,所以此消息不会被传递
- Message msgCannotBeReceived = new CommonMessage(
- entry1.getId(),
- entry2.getId(),
- MessageTypes.MESSAGE_CLICKED,
- "cliked evnet");
- entry1.post(msgCannotBeReceived);
- try {
- Thread.sleep(2000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- // re use the message object to send another message entry
- msg.setSource(entry2.getId());
- msg.setTarget(entry1.getId());
- msg.setType(MessageTypes.MESSAGE_READY);
- msg.setBody("okay now");
- entry2.post(msg);
- // 卸载这些监听器,当程序退出,或者
- // 或者监听器不在关注事件发生的时候
- entry1.unregister();
- entry2.unregister();
- }
- }
当前,这个系统可以支持远程的消息传递(通过java的RMI机制),不过对于寻址方面还没有做进一步的处理,有时间再来完善吧。
消息服务的实现
下面我把消息服务的主要实现部分贴出来分析一下:
- /**
- *
- * @author Abruzzi
- *
- */
- public class MessageBus extends UnicastRemoteObject implements Bus{
- private static MessageBus instance;
- private List<NotifiableEntry> listeners;
- private List<Message> messages;
- private Thread daemonThread = null;
- public static MessageBus getInstance() throws RemoteException{
- if(instance == null){
- instance = new MessageBus();
- }
- return instance;
- }
- private MessageBus() throws RemoteException{
- listeners = new LinkedList<NotifiableEntry>();
- messages = new LinkedList<Message>();
- Daemon daemon = new Daemon();
- daemonThread = new Thread(daemon);
- daemonThread.setPriority(Thread.NORM_PRIORITY + 3);
- daemonThread.setDaemon(true);
- daemonThread.start();
- while(!daemonThread.isAlive());
- }
- /**
- * mount notifiable object to listener list
- */
- public void mount(NotifiableEntry entry) throws RemoteException{
- synchronized(listeners){
- listeners.add(entry);
- listeners.notifyAll();
- }
- }
- /**
- * unmount the special notifiable object from listener
- */
- public void unmount(NotifiableEntry entry) throws RemoteException{
- synchronized(listeners){
- listeners.remove(entry);
- listeners.notifyAll();
- }
- }
- /**
- * post a new message into the bus
- * @param message
- */
- public void post(Message message) throws RemoteException{
- synchronized(messages){
- messages.add(message);
- messages.notifyAll();
- }
- }
- /**
- *
- * @author Abruzzi
- * worker thread, dispatch message to appropriate listener
- *
- */
- private class Daemon implements Runnable{
- private boolean loop = true;
- public void run(){
- while(loop){
- if(messages.size() == 0){
- synchronized(messages){
- try {messages.wait();}
- catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- processIncomingMessage();
- }
- }
- }
- /**
- * process the incoming message, remove the first message from
- * queue, and then check all listeners to see whether should
- * deliver the message to or not.
- */
- private void processIncomingMessage(){
- Message msg;
- synchronized(messages){
- msg = messages.remove(0);
- }
- String target = null;
- int type = 0;
- int mask = 0;
- try {
- target = msg.getTarget();
- type = msg.getType();
- if(target == MessageTypes.SENDTOALL){
- for(NotifiableEntry entry : listeners){
- mask = entry.getSense();
- if((mask & type) == type){entry.update(msg);}
- }
- }else{
- for(NotifiableEntry entry : listeners){
- mask = entry.getSense();
- if(entry.getId().equals(target) && (mask & type) == type){
- entry.update(msg);
- }
- }
- }
- } catch (RemoteException e) {
- e.printStackTrace();
- }
- }
- }
消息总线是一个RMI对象,其中mount(), unmout(), post()等方法可以被远程调用。MessageBus维护两个列表,一个消息列表,一个监听器列表。当消息被post到总线上后,post会立即返回,然后工作线程启动,取出消息并将其分发到合适的监听器上。
可能,对同步的处理上考虑不够周全,下来再继续修改。
P.S.我将这个项目托管在google code上了,叫BBMS(Bus Based Message Service),感兴趣的可以去看看:http://code.google.com/p/bbms/。