大家都知道Eventing或者Publish / Subscribe机制对于低耦合系统的重要性。很多时候写一个listener接口,一个list用来记录所有的listener,当有event发生的时候,就遍历list来通知每个listener,这种方法最简单明了,但在模块化开发(比如OSGi)中,如果在模块之间实现Publish Subscribe 模式就没有这么简单了。就好像logging这么简单的东西,到了模块化开发中也颇费周折一样。原因很简单,想要从模块化中获利(“利”指的是热启动,热关闭,模块之间耦合度降低,灵活性大大加强,可扩展性加强,健壮性加强,少了几个模块,其他不强制依赖这几个模块的其他模块照样运行),就必然在一些"简单"功能的实现上要多费思量。
好在OSGi准备了EventAdmin service,来解决这个模块间eventing的问题。如果你熟悉JMS中的messaging机制,或者GWT的EventBus的话,EventAdmin基本上是同一回事。你所需要的就是org.eclipse.osgi.services这个插件。
下面这个例子是基于Eclipse这个IDE(Eclipse本身是基于OSGi的,所以对于任何基于OSGi的项目,笔者认为Eclipse是不二选择)
首先新建一个Plugin Project,在MANIFEST.MF中加两个依赖
新建一个类,先称之为EventBus,它将注册OSGi的Declarative Service,获取一个EventAdmin的实例
import org.osgi.service.event.EventAdmin; /** * <ul> * <li>Title: EventBus</li> * <li>Description: This class instantiates the <code>Event Admin</code> service. Bundles wishing to publish events must obtain the Event Admin service and call one of the event delivery methods.</li> * <li>Created: Oct 15, 2012 by: JQin</li> * </ul> */ public class EventBus { /** * The eventBus. */ private static EventAdmin eventBus; /** * @return */ public static EventAdmin getEventBus() { return eventBus; } /** * Method will be used by DS to set the <code>org.osgi.service.event.EventAdmin</code> service. * @param eventBus */ public synchronized void setEventBus(EventAdmin eventBus) { EventBus.eventBus = eventBus; System.out.println("EventAdmin service is set!"); //$NON-NLS-1$ } /** * Method will be used by DS to unset the <code>org.osgi.service.event.EventAdmin</code> service. * @param eventBus */ public synchronized void unsetEventBus(EventAdmin eventBus) { if (EventBus.eventBus == eventBus) { EventBus.eventBus = null; } System.out.println("EventAdmin service is unset!"); //$NON-NLS-1$ } }
<?xml version="1.0" encoding="UTF-8"?> <scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="com.jqin.common.eventbus"> <implementation class="com.jqin.common.eventbus.EventBus"/> <reference bind="setEventBus" cardinality="1..1" interface="org.osgi.service.event.EventAdmin" name="EventAdmin" policy="static" unbind="unsetEventBus"/> </scr:component>
org.eclipse.equinox.ds
org.eclipse.equinox.event
并且要设置为自动开始(auto-start 为 true)
当点击Run之后,如果Console立刻打印了
EventAdmin service is set!
就表示这个EventAdmin Declarative Service设置成功了!
使用起来非常方便,首先在这个插件的MANIFEST.MF中开放EventBus所在的包给其他插件。
和REST中定位资源的方式类似,EventAdmin通过定义成为topic的URI来区分不同的Event,比如下面这个event的topic就是
org/eclipse/equinox/events/MemoryEvent/CRITICAL
任何subscribe了这个topic的listener都可以接收到event信息
Event event =newEvent("org/eclipse/equinox/events/MemoryEvent/CRITICAL", null); eventAdmin.postEvent(event);
EventAdmin发送Event的方法有两种,一种是同步发送,即sendEvent,另一个是异步发送,即postEvent。二者的区别是sendEvent会block caller,确保所有的subscriber都接收到了event,而postEvent则是把event排到EventAdmin的一个queue里面,然后caller就不管了,也不会被block。
在新建Event的时候,除了需要topic来定义这个Event的URI以外,Event还支持一个Map变量用来存储属性,这个属性Map通常就被用来保存需要在Event中传输的数据。
下面介绍一个如何注册成为某个或者某些event的subscriber,同样通过Declarative Service。
先新建一个subscriber类
import org.osgi.service.event.Event; import org.osgi.service.event.EventHandler; public class Subscriber implements EventHandler { /* * (non-Javadoc) * @see org.osgi.service.event.EventHandler#handleEvent(org.osgi.service.event.Event) */ @Override public void handleEvent(Event event) { // TODO Auto-generated method stub } }
同样在这个文件中,注册一个名为event.topics的属性
完成后的component.xml文件类似于这样
<?xml version="1.0" encoding="UTF-8"?> <scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="subscriber"> <implementation class="Subscriber"/> <property name="event.topics" type="String" value="org/eclipse/equinox/events/MemoryEvent/*"/> <service> <provide interface="org.osgi.service.event.EventHandler"/> </service> </scr:component>
这就表示这个Subscriber注册了org/eclipse/equinox/events/MemoryEvent/* 这个topic,大家都知道 * 标记指的是任何,所以前面发送的Event topic是org/eclipse/equinox/events/MemoryEvent/CRITICAL自然也其中。
推荐基于OSGi模块化开发的程序员使用EventAdmin的原因和推荐使用Publish Subscribe pattern,以及eventing/messaging机制的原因一样,减少模块间的依赖。使用模块化开发的最重要原因就是低耦合,所以这一点很重要。