基于 OSGi 服务模型实现组件之间松耦合通信
简介: OSGi Service Layer 所定义服务模型是以发布 (Publish) 发现 (Find) 和绑定 (Bind)为基础操作的动态协作合作模型。所谓的服务 (Service) 就是标准的 Java 对象向服务注册表 (Service Registry) 注册的一个或者多个接口 (interface)。 Bundle 可以向服务注册表注册服务,查询服务并使用它们。本文所要讨论的是基于 OSGI 动态服务模型的 Event Admin Service,通过使用 Event Admin Service,开发者可以开发类似于 JMS 的,基于消息的松耦合应用。
在开始我们的正式讨论之前 , 让我们花些时间来回顾一下这些预备知识。
简单来讲,OSGi 服务层 (OSGi Service Layer) 是一个构建在 Java 之上的 SOA 平台,它定义了一个动态协作的服务模型,该模型以发布、绑定、发现为基本操作,分离契约 ( 接口 ) 与实现。Bundle 可以向服务注册表注册服务,查询服务并使用它们,如图 1 所示。
图 1. OSGi 服务模型 .
Bundle dw.sample.service.provider 实现服务,并把它注册给服务注册表,Bundle dw.sample.service.consumer 通过向服务注册表查询,获得该服务,并使用它。
OSGi 服务层技术范畴涉及以下的几个核心概念:
- 服务 (Service),即实现契约的 Java 对象 (POJO),该对象可以通过一个或者多个接口向服务注册表注册,别的 Bundle 可以通过服务注册表发现他们。
- 服务注册表 (Service Registry),保存所有服务注册信息。
- 服务引用 (Service Reference),指向服务的引用,它们并不是真正的服务对象,Bundle 使用 Bundle Context 通过该引用获得真正的服务。
- Service Registration,在 Bundle 向服务注册表注册一个服务后,会获得一个 Service Registration,Bundle 可以用 Service Registration 更新服务属性,反注册服务等。
- 服务事件 (Service Event), 当服务对象被注册、修改、反注册时,相应的服务事件会产生。
- 服务监听者 (Service Listener),用于侦听所发生的服务事件。
在本节,我们先来看一个 OSGi 服务的例子。
清单 1 定义了我们的服务契约,清单 2 实现了清单 1 定义的借口。
清单 1. IQuoteService 接口
public interface IQuoteService {
String getQuote();
}
|
清单 2. IQuoteService 接口实现
public class QuoteService implements IQuoteService {
@Override
publicString getQuote() {
return"Hi, you are using quote service.";
}
}
|
接下来,我们需要发布我们所定义的服务,如清单 3 所示:
清单 3. 发布服务
public class Activator implements BundleActivator {
……
public void start(BundleContext context) throws Exception {
IQuoteService service = new QuoteService();
Dictionary<String,String> properties = new Hashtable<String,String>();
context.registerService(IQuoteService.class.getName(), service,properties);
}
……
}
|
如清单 3 所示,我们使用 IQuoteService 接口,通过 Bundle Context 注册了我们服务,properties 是服务附带的属性。
清单 4 给出了另外一个 Bundle 消费该服务的例子。
清单 4. 消费服务
public class Activator implements BundleActivator {
……
public void start(BundleContext context) throws Exception {
ServiceReference reference =
context.getServiceReference(IQuoteService.class.getName());
if(null != reference ) {
IQuoteService service = (IQuoteService) context.getService(reference);
if( null != service) {
System.out.println(service.getQuote());
}
}
}
……
}
|
如清单 4 所示,使用 Bundle Context, 通过 IQuoteService 获得服务引用,进而通过该服务引用获得真实的服务对象,并消费它。
从上面的例子可以看到,实质上,服务的提供者与服务的消费者之间是存在耦合关系的,在清单 4 中,服务的消费者使用了 IQuoteService 接口定义,也就是说,服务的消费者实现依赖于服务提供者的接口定义,虽然这种依赖通过契约与实现相分离得到了弱化。图 2 给出了调用序列图。
通过本节的介绍,相信读者对 OSGi 服务发布、查询、使用有了比较全面的了解。基于这些基本的概念,我们接下来讨论如何使用 OSGi 提供的 Event Admin 服务,实现 Bundle 的松散耦合通信。
OSGi 的 Event Admin 服务规范提供了开发者基于发布 / 订阅模型,通过事件机制实现 Bundle 间协作的标准通讯方式,如图 3 所示:
图 3. Event Admin 服务模型 .
事件发布者使用 Event Admin 服务发送基于主题 (Topic) 的事件,任何对某一主题感兴趣的事件订阅者都会收到该事件,并且做出相应的反应。
我们遵循以下的步骤,通过 Event Admin 发布事件:
- 获得实现了 org.osgi.service.event.EventAdmin 接口的 Event Admin 服务引用。
- 拟定事件的主题。
- 指定事件所携带的 key/value 属性集。
- 使用 Event Admin 提供的 postEvent 或者 sendEvent 方法发布事件,postEvent 使用同步的方式发布事件,即:等到所有的事件订阅者响应了该事件,方法调用返回,而 sendEvent 使用异步的方式发布事件。
清单 5. 发布事件
public class EventPublisher {
……
public void publish() {
if (getEventAdmin() != null) {
//post event, the topic is "com/ibm/dw/test1",
//the event properties: property1 = 1
getEventAdmin().postEvent(new Event("com/ibm/dw/test1",getEventProperties("1")));
getEventAdmin().postEvent(new Event("com/ibm/dw/test1",getEventProperties("2")));
getEventAdmin().postEvent(new Event("com/ibm/dw/test2",getEventProperties("1")));
}
}
protected Dictionary<String,String> getEventProperties(String property1Value) {
Dictionary<String, String> result = new Hashtable<String,String>();
result.put("property1", property1Value);
return result;
}
……
}
public class Activator implements BundleActivator {
……
public void start(BundleContext context) throws Exception {
ServiceTracker serviceTracker =
new ServiceTracker(context,EventAdmin.class.getName(), null);
serviceTracker.open();
//using the service tracker to get the Event Admin service.
EventAdmin eventAdmin = (EventAdmin)serviceTracker.getService();
if (null != eventAdmin) {
getEventPublisher().setEventAdmin(eventAdmin);
// publish events.
getEventPublisher().publish();
}
}
……
}
|
对于事件的主题,原则上应该遵循一定的命名规范,如清单 5 所示,第一个被发布的事件主题是 com/ibm/dw/test1,其规则与命名 Bundle 名字规则相类似,这样的命名规则容易让消费者订阅,不容易产生冲突,混淆。
遵循以下的步骤,来订阅被 Event Admin 发布的事件:
- 确定你想要订阅那些主题事件,支持通配符。
- 如果需要,根据事件的属性确定事件的过滤原则。
- 发布实现了 org.osgi.service.event.EventHandler 接口的你的事件订阅服务,以便当你感兴趣的事件发生后,Event Admin 能通知到你。
事件订阅代码如清单 6 所示。
清单 6. 订阅事件
public class EventSubscriber implements EventHandler {
public void handleEvent(Event event) {
System.out.println(String.format(
"Event Subscriber '%s' handled event on topic '%s':"+
" Value of 'property1' = %s", getHandlerName(),
event.getProperty(EventConstants.EVENT_TOPIC),
event.getProperty("property1")));
}
……
}
public class Activator implements BundleActivator {
……
public void start(BundleContext context) throws Exception {
//Register event subscriber 1 to subscribe all the events
//whose topic is equal to "com/ibm/dw/test1".
context.registerService(EventHandler.class.getName(),
new EventSubscriber("Subscriber 1"),
getHandlerServiceProperties("com/ibm/dw/test1"));
//Register event subscriber 2 to subscribe all the events
//whose topic wilcards matches with "com/ibm/dw/*"
//and the event's property1 property is equal to 2.
context.registerService(EventHandler.class.getName(),
new EventSubscriber("Subscriber 2"),
getHandlerServiceProperties(new String[] { "com/ibm/dw/*" },
"(property1=2)"));
}
protected Dictionary<String, String[]> getHandlerServiceProperties(String... topics) {
Dictionary<String, String[]> result =
new Hashtable<String, String[]>();
result.put(EventConstants.EVENT_TOPIC, topics);
return result;
}
protected Dictionary<String, Object> getHandlerServiceProperties(
String[] topics, String filter) {
Dictionary<String, Object> result =
new Hashtable<String, Object>();
result.put(EventConstants.EVENT_TOPIC, topics);
result.put(EventConstants.EVENT_FILTER, filter);
return result;
}
……
}
|
示例代码的运行结果如清单 7 所示:
清单 7. 运行结果
Event Subscriber 'Subscriber 1' handled event on topic 'com/ibm/dw/test1':
Value of 'property1' = 1
Event Subscriber 'Subscriber 1' handled event on topic 'com/ibm/dw/test1':
Value of 'property1' = 2
Event Subscriber 'Subscriber 2' handled event on topic 'com/ibm/dw/test1':
Value of 'property1' = 2
|
对这个结果,在这里稍微解释一下:对于 Subscriber 1,由于它指明处理所有主题为 com/ibm/dw/test1 的所有事件,所以 EventPublisher 发布的前两个事件它都收到了。而对于 Subscriber 2,由于它指明只接收主题匹配 com/ibm/dw/*,且 property1 属性等于 2 的事件,所以 Subscriber 2 只处理了 EventPublisher 发布的第二个事件。而 EventPublisher 发布的第三个事件,由于不满足条件,没有被任何一个 Subscriber 处理。
关于这部分的源代码,读者可以从本文后面的资源列表中下载。
从这个例子中可以看到,发布事件的 Bundle 与订阅事件的 Bundle 之间没有任何的相互依赖关系,它们之间以 Event Admin 服务为中间人 (Broker),以事件 (Event) 为消息载体,进行 Bundle 间的松散协作。
基于 Event Admin 服务的 Bundle 通讯,我们总结如下几点:
- OSGi Event Admin 的事件模型,与传统的 JMS 相比,OSGi Event Admin 事件不是持久化的,也就是说,通过 Event Admin 发布的事件只在本次应用会话期间存在,当 OSGi 应用重新启动后,所有的未处理的事件将全部丢失。
- 由于 OSGi 服务的动态特性,使用该模型时,必须考虑到以下的情况:由于订阅事件的 Bundle 可以在任意运行时刻注册实现了 org.osgi.service.event.EventHandler 接口的事件订阅服务,所以开发者不能期望订阅者总能够收到在本次会话期间事件发布者发布的所有事件,一个典型的情况是,如果在发布者发布事件的时候,订阅者服务尚未注册,其结果是,该订阅者错过这次事件。
- 在 Equinox 环境下,如果想使用 Event Admin 服务,需要额外从 www.eclipse.org 下载具体的 Event Admin 服务 Equinox 实现 org.eclipse.equinox.event Bundle, 在默认的情况下,org.eclipse.equinox.event Bundle 并没有包含在 Equinox 的 SDK 里。
OSGi 服务层所提供的服务模型,其动态发现、动态绑定、契约与实现相分离等特性,可以帮助开发者实现组件之间依赖保持在接口之间,而与具体实现代码相对松散的应用逻辑。在此基础上 OSGi 提供了基于事件的 Event Admin 服务。Event Admin 服务本质上是基于消息的发布 / 订阅机制,对于 OSGi 的开发者来说,Event Admin 服务可以帮助他们实现组件间更为松散的,不依赖任何接口定义的协作逻辑。
<!-- CMA ID: 458836 --><!-- Site ID: 10 --><!-- XSLT stylesheet used to transform this file: dw-article-6.0-beta.xsl -->