---------------------
原地址:http://www.eclipsezone.com/eclipse/forums/t90796.html
原作者:Neil Bartlett
---------------------
OSGi起步(5):使用服务
上一部分,我们知道了如何注册一个服务。这一次,我们来看看怎么去查找和使用另一个bundle注册的服务。
和以前一样,我们使用Martin Fowler关于“依赖注入”的论文里提到的例子。我们已经创建了一个MovieFinder,并且把它做为服务注册到注册表中。现在,我们来创建一个MovieLister,它将使用MovieFinder来查找某个导演执导的影片。我们假设MovieLister本身也是一个服务,它将被其他bundle使用,比如一个用户界面程序。这就有麻烦了,因为OSGi的服务是动态的,它们存在与否都是可能的。这意味着,有时候我们想去调用MovieFinder服务,却意外地发现它并不可用!
MovieFinder服务不存在的时候,MovieLister该怎么处理呢?很显然,调用MovieFinder是MovieLister很重要的一个工作,所以我们只有很少的几个可选的选项:
1、返回一个错误,比如,返回null或者抛出一个异常;
2、等待;
3、一开始就不出现。
本文中,我们将把注意力集中在前两个选项上,因为它们比较简单。一开始,你可能会觉得第三个选项没有任何意义。不过,在我们实现前两种方法以后,它会变得很让人期待。
我们要做的第一件事是,为MovieLister定义一个接口。复制以下代码到osgitut/movies/MovieLister.java:
import java.util.List;
public interface MovieLister {
List listByDirector(String name);
}
接着,创建文件 osgitut/movies/impl/MovieListerImpl.java:
import java.util. * ;
import osgitut.movies. * ;
import org.osgi.framework. * ;
import org.osgi.util.tracker.ServiceTracker;
public class MovieListerImpl implements MovieLister {
private final ServiceTracker finderTrack;
public MovieListerImpl(ServiceTracker finderTrack) {
this.finderTrack = finderTrack;
}
public List listByDirector(String name) {
MovieFinder finder = (MovieFinder) finderTrack.getService();
if(finder == null) {
return null;
} else {
return doSearch(name, finder);
}
}
private List doSearch(String name, MovieFinder finder) {
Movie[] movies = finder.findAll();
List result = new LinkedList();
for (int i = 0; i < movies.length; i++) {
if(movies[i].getDirector().indexOf(name) > -1) {
result.add(movies[i]);
}
}
return result;
}
}
这大概是到目前为止我们所写的最长的代码了。那么这些代码在做什么呢?首先,我们注意到,为了与OSGi相关的代码保持松散结构,查找电影的实际过程被提取到了doSearch(String ,MovieFinder)方法中。虽然,我们所用的查找方法很差而且没有效率,不过,做为一个教程,这倒是不重要。而且,数据库中只有两部电影:)
listByDirector(String name)方法很有意思,它使用ServerTracker对象从服务注册表中获取MovieFinder。ServiceTracker是一个很有用的类,它掩盖了OSGi API底层许多乏味的细节。但是,我们仍然需要检查一个服务是否确实存在。我们假设ServiceTracker将在构造函数中传入。
在bundler的控制器(activator)中创建ServiceTracker是一个不错的主意。复制以下代码到osgitut/movies/impl/MovieListerActivator.java :
import java.util. * ;
import org.osgi.framework. * ;
import org.osgi.util.tracker.ServiceTracker;
import osgitut.movies. * ;
public class MovieListerActivator implements BundleActivator {
private ServiceTracker finderTracker;
private ServiceRegistration listerReg;
public void start(BundleContext context) throws Exception {
// Create and open the MovieFinder ServiceTracker
finderTracker = new ServiceTracker(context, MovieFinder.class.getName(), null);
finderTracker.open();
// Create the MovieLister and register as a service
MovieLister lister = new MovieListerImpl(finderTracker);
listerReg = context.registerService(MovieLister.class.getName(), lister, null);
// Execute the sample search
doSampleSearch(lister);
}
public void stop(BundleContext context) throws Exception {
// Unregister the MovieLister service
listerReg.unregister();
// Close the MovieFinder ServiceTracker
finderTracker.close();
}
private void doSampleSearch(MovieLister lister) {
List movies = lister.listByDirector("Miyazaki");
if(movies == null) {
System.err.println("Could not retrieve movie list");
} else {
for (Iterator it = movies.iterator(); it.hasNext();) {
Movie movie = (Movie) it.next();
System.out.println("Title: " + movie.getTitle());
}
}
}
}
现在,这个activator开始变得有趣起来了。首先,在start方法中,创建了一个ServiceTracker(服务追踪器)对象,它将被我们刚刚写的MovieLister使用。接着,这个“追踪器”被“释放”,并开始在注册表中查找MovieFinder服务。然后它创建MovieListerImpl对象,并把它以“MovieLister”注册到服务注册表中。最后,为了在启动bundle的时候发生一些有趣的事情,activator简单地搜索了一下MovieLister然后输出结果。
我们需要编译并安装这个bundle。这一次,我不再给出完整的指令。你应该能够根据之前所做的实现这个操作。记住,你还需要创建一个manifest文件,并指明它的Bundle-Activator是osgitut.movies.impl.MovieListerActivator。另外,Import-package一行应该包含三个从其它bundle导入的package,分别是:org.osgi.framework, org.osgi.uitl.tracker和osgitut.movies.
一旦你把MovieLister.jar安装到Equinox的运行环境中,你就可以启动它了。这时,你将看到两条或者三条消息,这取决于BasicMovieFinder bundle自从上一次起是否还在继续运行。如果没有,你将看到:
Could not retrieve movie list
如果在运行,将看到
Title: Spirited Away
停止或者启动不同的bundle,你将看到你希望的每一条消息。这差不多就是本次要讲的所有内容了,除了我刚才说的,当一个服务不可用的时候,你还可以选择等待。由现有的代码,应该是很容易实现的:把MovieListerImpl的第16行的getService()改为调用ServiceTracker的waitForService(5000),然后添加try/catch块来处理异常InterruptedException。
一般来说,我不建议像这样子将一个线程挂起。这种情况是很危险的,因为listByDirector()方法是由bundle的activator的start方法调用的,而它将被框架线程调用。activator通常被要求快速返回,因为当一个bundle处于活动状态的时候,将有一系列的事情发生。事实上,在最坏的情况下,还可能产生死锁,因为我们进入了到框架所拥有的一个对象的synchronized(同步)段中,而这个对象很可能已经被别的东西给阻塞了。一个比较好的建议是,永远不要再一个bundle的activator的start方法中,或者直接被框架调用的代码中,进行长时间的操作和阻塞。
下一次,我们将看看当依赖不存在的时候,如何实现第三种方法:“一开始就不出现”。不要走开。