在这篇文章开始之前我们先看一个日常编程中经常出现的问题:在面向对象编程中,应用程序中所用的接口(Interface)与相应实现(Implementation)经常是耦合在一起的,比如下面一段代码:
InformationSearcher s = new DBInformationSearcher ();
在该应用程序中,为了使用一个信息搜索的功能,需要实例化一个具体的实现,比如DBInformationSearcher这个类实现了从数据库进行信息搜索。但是如果信息还有其它来源呢?比如文件,远程服务等等。如果这个程序要支持或者更换为其它形式的信息搜索,则需要修改应用程序的代码。比如为了支持从文件进行信息搜索,则需要把程序代码改为下面的样子:
InformationSearcher s = new FileInformationSearcher ();
问题是:在应用程序中如何能够让接口与实现解耦呢?
目前大概有两路解法: 依赖注入 (Dependency Injection)与动态服务 (Dynamic Service)
关于什么是依赖注入,Thoughtworks的首席科学家Martin Fowler在其著名的文章Inversion of Control Containers and the Dependency Injection Pattern 中做了清楚的解释。根据Martin的介绍,依赖注入可以分为三种类型,依次为Interface Injection、Setter Injection和Constructor Injection。感兴趣的朋友们可以去看看这篇文章,也欢迎大家在我的博客中和我进行相关讨论。
依赖注入毫无疑问是解决上述问题的一个途径,而且Setter Injection这种依赖注入的方法更是在Spring这样火的框架中大行其道。然而,依赖注入也有其应用限制,即依赖关系必须是提前定义好的。比如在Spring中,依赖关系是在applicationContext.xml文件中提前指定好的。因此,这种静态依赖关系注入的方法缺少了一些动态性和灵活性,虽然扩展和更改依赖关系不需要再去修改源代码了,可是还是需要修改静态依赖关系注入部分的定义,比如修改Spring中的applicationContext.xml文件。因此,动态发布、查找和绑定依赖关系的机制和方法在动态性比较高的系统中就非常有价值了。
通过上面的描述,相信大家对于问题以及动态服务方法的价值都有了一些了解。那么在本篇文章的下面部分,我将会介绍什么是OSGi的服务,如何动态地发布、查找、绑定以及跟踪OSGi的服务。
在OSGi中,Bundle中定义的任何一个对象都可以被发布为其所实现的接口名下的服务。在我之前写的一篇文章OSGi探秘系列(4)- OSGi Bundle之依赖关系中 ,曾经写过一个接口MailBox和其实现FixedMailbox。在这里我们写一个FixedMailboxActivator的类,用来将FixedMailbox发布为服务:
package org.osgi.message.reader.fixedmailbox;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.message.reader.api.MailBox;
import java.util.Properties;
public class WelcomeMailboxActivator implements BundleActivator {
public void start(BundleContext context) throws Exception {
MailBox mailbox = new FixedMailbox();
Properties props = new Properties();
props.put("version", "1.2");
context.registerService(MailBox.class .getName(), mailbox, props);
}
public void stop(BundleContext context) throws Exception {
}
}
从上面的代码可以看到,注册一个服务非常简单,仅需要通过调用BundleContext的registerService方法即可,该方法的第三个参数是一个属性集合,发布的时候可以指定一些具体的属性,比如版本信息。另外,我们看到在stop()方法里面,我们没有unregister服务,这是由于OSGi会在bundle被停止的时候自动unregister该bundle所注册的服务。
当服务发布了以后,其它的bundle就可以进行查找和绑定了,如下面的代码所示:
package org.osgi.message.reader.mailboxclient;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import org.osgi.message.reader.api.MailBox;
import org.osgi.util.tracker.ServiceTracker;
import java.util.Properties;
public class MailboxClientActivator implements BundleActivator {
public void start(BundleContext context) throws Exception {
// 服务查找、绑定方法1
ServiceReference ref = context.getServiceReference(MailBox.class.getName());
if(ref != null) {
MailBox mailbox = (MailBox)context.getService(ref);
mailbox. .....
}
// 服务查找、绑定方法2
ServiceTracker mboxTracker = new ServiceTracker(context, MailBox.class.getName(), null);
MailBox mbox= (MailBox) mboxTracker.getService();
if(mbox!=null)
mbox. ......
}
public void stop(BundleContext context) throws Exception {
}
}
在实践中,我推荐大家使用第二种服务查找、绑定的方法,即用ServiceTracker的方法。ServiceTracker构造器中的第三个参数是ServiceTrackerCustomizer的实例,允许我们对服务的添加、更改和删除进行监听。下面的代码示例了如何在上面的MailboxClientActivator中去实现ServiceTrackerCustomizer的私有类实现。
private class MailboxServiceTrackerCustomizer implementsServiceTrackerCustomizer{
public Object addService(ServiceReference ref) {
FixedMailbox fmBox = (FixedMailbox) context.getService(ref);
ServiceRegistration registration = context.registerService(Mailbox.class.getName(), fmBox, null);
return registration;
}
public void modifyService(ServiceReference ref, Object service) {
}
public void removeService(ServiceReference ref, Object service) {
ServiceRegistration registration = (ServiceRegistration) service;
registration.unregister();
context.ungetService(ref);
}
}
然后修改上面构造ServiceTracker的代码如下,就可以监听service的变化。
// 服务查找、绑定方法2
ServiceTracker mboxTracker = new ServiceTracker(context, MailBox.class.getName(), new MailboxServiceTrackerCustomizer());
通过ServiceTrackerCustomizer监听服务是非常有用的。因为在具体查找和绑定过程中可能出现以下几个问题:1)所要查找的服务不存在,但是后来被添加到OSGi运行时环境了;2)服务被动态删除和修改了。通过使用ServiceTrackerCustomizer可以有效解决这些问题。
另外,查询和绑定服务的时候还存在另外一种情况,就是查询结果里面包含了多个服务实例。OSGi提供了Filter过滤机制以及一些系统默认的排名机制来解决服务实例的选择问题,感兴趣的读者可以参照OSGi规范查看具体细节。