OSGi教程 – 03 – OSGi 的事件机制
3.1 事件的存在
在前两章,你已经了解了如何将服务声明者、实现者和使用者联系起来。显而易见,它们对服务声明部分是有较强的依赖性的,实现者和使用者双方的代码里,都出现了接口ISayAnyThing,尽管这看起来已经足够好了,但是正如我在 1.3 节中所说,这种依赖可能会带来Bundle的森林——因为使用相同的接口,实际上就是要求服务的提供者和使用者满足一种显式的一致性要求,当服务的提供者和使用者数目很多的时候,处理这种一致性要求的代价可能会很大,甚至会引入很多非技术性的问题——例如管理问题、文档问题。事件机制可以部分地解决这个问题——让Bundle产生一个事件,对该事件有兴趣的处理者,可以进行相关的处理——当然,这样也可能会带来反面的问题:对事件的处理过程可能会被隐藏起来,一旦事件机制被滥用,这会带来问题调试者的噩梦。
最常用的例子是有时候我们需要得到某些信息,那么就可以产生一个事件,由OSGi事件管理服务来自动分发事件给所有感兴趣的处理者,处理者在收到指定格式的事件之后即可进行处理——例如读取相关信息或向事件所关联的对象中填入合适的值——这样当所有的处理者都处理完之后,发送者就有可能根据对应对象值来判断事件被处理的情况。
图3.1.1 事件的发布与处理
对于事件的发布者来说,可能并不清楚,或者是不必清楚事件由谁来处理,如何处理。而对于事件处理者来说,也无需清楚事件由谁来发布,如何发布(甚至连接口这种形式上的约束也不需要)。OSGi框架自动帮你保证消息的分发的有效性,帮你确定收发顺序、安全等。如果处理方式合适,也会很高效(因为在OSGi框架本身就有数目繁多的事件激发和处理,我们做过压力测试,每秒钟处理数以万计甚至更多消息是可能的)。事实上,事件的发布者和处理者使用了一种经典设计模式——观察者模式(或者叫发布-订阅模式)。在OSGi的事件机制中,实际上提供了一种约定(即统一的事件发布和处理接口,而不是各种自定义的服务接口),来使得事件的发布者和处理者的工作方式统一,也就进一步降低了事件处理双方的耦合性(当然,由API级别的约束转为文档级的约束,不见得都是好事,看你怎么把握了)。关于观察者模式的细节,这里不作过多描述,你若有兴趣,可以去参考相关书籍和资料。
当然事件机制的作用不仅仅是解决耦合性过多和过高的问题,几乎OSGi框架里所有的核心Bundle都会与事件打交道,要么发布事件,要么处理事件,对他们专业的称呼是Event Publisher和Event Handler。在所有Bundle生命周期的各个阶段之间变换时也均会产生事件,整个OSGi框架的变动也会产生事件……在这里我们不作过多描述(注意,不作过多描述不代表不重要,也不代表其作用范围不广泛,恰恰相反,这里面包含了太多太多的细节,值得好好研究,只是本章的核心在于演示自定义的事件而已)——关键在于我们自己的Bundle也可以利用事件机制来做很多事情。
我们对之前的例子进行进一步改造,NeedServiceBundle作为Event Publisher发布事件,ServiceBundle作为Event Handler处理事件,NeedServiceBundle在启动之后会告诉ServiceBundle自己已经启动了——要使用事件机制,需要用到OSGi框架的Event Admin服务,相关的定义在org.osgi.service.event包中。
3.2 事件的构成
Event对象有两个属性:Topic和Properties。
Topic是必须的,用来定义事件的类别,比如Bundle启动的时候会发布一个启动事件,其标题为:org/osgi/framework/BundleEvent/STARTED。我们可以采用类似的格式来对自己的事件命名,一般格式为:包名/类名/事件类别。
Properties可以有一个或多个,每个属性都是一对属性名和属性值。属性名是一个对大小写敏感的字符串;属性值可以是任何Java对象,不过建议尽量只使用String和其他基本类型及其包装类,这样可以减少因为多个Handler捕获事件后对事件属性内容修改而带来的潜在问题(比如到底谁改了,改得对不对)。
3.3 发布事件
本章的运行环境我们仍基于之前的OSGi Framework,在开始之前,首先按照之章节描述的方法,在名为“Chapter2”的OSGi Framework基础上复制一份,并命名为“Chapter3”。然后将OSGI-INF/components2.xml删除(这是在之前的演示中我们故意重复声明的服务),并删除MANIFEST.MF文件中Service-Component条目下对应的值。
要发布事件,首先要记得在open Debug Dialog或open Run Dialog的Bundles选项卡中勾选org.eclipse.equinox.event,将其加入运行环境。
图3.3.1配置运行环境
然后在工作空间的NeedServiceBundle工程下双击MANIFEST.MF,进入属性编辑器后在Dependencies选项卡的Imported Packages中新增org.osgi.service.event包。
图 3.3.2 导入 event 包
下面我们将对NeedServiceBundle工程进行改造,使其在SingASong类中发送事件,将SingASong类的activeate方法改造成如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public void activate(ComponentContext ctxt) { sayAnyThing.sayHello("O...O...O...Only you..."); BundleContext bundleContext = ctxt.getBundleContext();// 获取上下文 // 获取消息管理服务引用 ServiceReference sr = bundleContext.getServiceReference(EventAdmin.class.getName()); if (sr != null) { EventAdmin eventAdmin = (EventAdmin) bundleContext.getService(sr);// 获取消息管理服务 Event eventSended = new Event("needservicebundle/WhoCare", new HashMap<String, String>());// 生成消息 Event eventPosted = new Event("needservicebundle/AnyBody", new HashMap<String, String>());// 生成消息 eventAdmin.sendEvent(eventSended);// 以同步方式发送消息 eventAdmin.postEvent(eventPosted);// 以异步方式发送消息 } } |
为了保证代码可运行,注意导入如下包:
1 2 3 4 5 6 7 8 9 |
import java.util.HashMap; import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceReference; import org.osgi.service.component.ComponentContext; import org.osgi.service.event.Event; import org.osgi.service.event.EventAdmin; import servicebundle.ISayAnyThing; |
根据上面代码中的注释可以看到事件消息的发送有两个方法:sendEvent和postEvent。前者是同步调用,当所有的EventHandler处理完毕后才会返回;后者是异步调用,不必等待EventHandler的返回——如果不是必须使用前者来同步调用,建议使用后者,其代价远小于前者。
3.4 处理事件
下面我们来改造ServiceBundle,使其可以处理事件(也就是接收消息),与改造NeedServiceBundle类似,首先需要导入org.osgi.service.event包,如下图:
图3.4.1 为ServiceBundle导入event包
然后让Activator 类实现EventHandler接口并编写handleEvent方法,最后在start方法中注册监听服务即可。
备注:
我们采用 Activator 类来监听事件只是因为在本例中操作比较简单,事实上你可以使用其它类来做同样的事。并且要注意在implements关键字后加上EventHandler接口,否则会在运行时获得关于类型不符的错误提示。
经过改造后的Activator类主要部分是这样的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
package servicebundle; import java.util.Dictionary; import java.util.Hashtable; import org.osgi.framework.BundleActivator; import org.osgi.framework.BundleContext; import org.osgi.service.event.Event; import org.osgi.service.event.EventConstants; import org.osgi.service.event.EventHandler; public class Activator implements BundleActivator, EventHandler { private static BundleContext context; static BundleContext getContext() { return context; } public void start(BundleContext bundleContext) throws Exception { Activator.context = bundleContext; // 要监听的消息标题 String[] topics = new String[] { "needservicebundle/WhoCare", "needservicebundle/AnyBody" }; // 填充EventHandler的属性 Dictionary<String, Object> properties = new Hashtable<String, Object>(); properties.put(EventConstants.EVENT_TOPIC, topics); // 注册EventHandler服务 context.registerService(EventHandler.class.getName(), this, properties); } public void stop(BundleContext bundleContext) throws Exception { Activator.context = null; System.out.println("再见,OSGi的世界"); } // 监听到之后的处理 public void handleEvent(Event event) { System.out.println("监听到事件:" + event.getTopic()); } } |
正常运行起来后,你应该看到类似这样的界面:
图3.4.2消息机制样例运行界面


分享图片