星期三, 六月 25, 2008

[JAVA]OSGi入门:消费一个服务[译]

在我们的上个部分,我们看了如何注册一个服务。现在哦我们需要从另外的Bundle查找并使用服务。 

我们将问题放到我们的需求的上下文中,那个通过Martin Fowler的依赖反转的页面获得的灵感。我们建立了一个MovieFinder的服务并通过Service Registry注册了它。现在我们想要建立使用了MovieFinder的一个MovieLister来搜索定向的电影。我们假定MovieLister它自己将是被其它Bundle消费的一个服务,例如一个GUI应用程序。问题是,OSGi服务是动态的……它们来去都是动态的。意思就是有时我们想要调用MovieFinder服务但是它恰巧无效。 

所以,如果MovieFinder没有呈现,那么MovieLister将做什么?显然,通过MovieLister完成工作的重要部分是调用MovieFinder,所以这有一些有用的选择给我们: 
[list=decimal] 
生成一个错误,例如返回null或者抛出一个异常
等待
在第一次里不要调用
[/list]

在这篇文章中我们将首先看前两项,它们十分简单。第三个选项可能甚至仍然没有什么感觉,但是看过前两个后你将会对它抱有希望。

第一件事我们需要为MovieLister服务定义接口。复制以下代码到osgitut/movies/MovieLister.java文件中: 
1
2
3
4
5
6
7
package osgitut.movies;

 

import java.util.List;

 

public interface MovieLister {
    List listByDirector(String name);
}



现在创建文件osgitut/movies/impl/MovieListerImpl.java 
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
package osgitut.movies.impl;

 

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;
    }
}



这可能是我们的目前最长的代码样本!那么这里将要做什么?首先你注意搜索电影的实际的逻辑被分隔在doSearch(String, MovieFinder)方法中,来帮助我们隔离OSGi具体代码。偶然的,我们执行搜索的方式是相当的愚笨和低效率,但是这不是这节指导的重点。我们无论如何只有两个电影在数据库中!

有趣的部分是在listByDirector(String name)方法中,使用了一个ServiceTracker对象来从Service Registry中获得MovieFinderServiceTracker是将最低级别的OSGi API中的大量令人不愉快的细节抽象出来的非常有用的类。然而我们仍然要检查服务是否确实存在。我们采取在我们的构造函数中传递给我们的ServiceTracker

注意,你可能见过其他地方的代码从Registry获得一个服务而不使用ServiceTracker。例如,它可能调用BundleContext使用getServiceReferencegetService。可是这个代码你不得不写的十分复杂并且它不得不小心的自行清理。以我所见,放到低级别的API中处理的益处很少,并且有很多的问题。总是单独使用ServiceTracker更好。

一个不错的创建ServiceTracker的地方是在Bundler的激活器中。复制这段代码到osgitut/movies/impl/MovieListerActivator.java 
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
44
45
package osgitut.movies.impl;

 

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());
            }
        }
    }
}



现在来看看这个激活器的启动。首先,在start方法中它创建了一个ServiceTracker对象,在我们刚写的MovieLister中使用。然后"打开"ServiceTracker告诉它启动跟踪RegistryMovieFinder服务的实例。之后,创建我们的MovieListerImpl对象并在Registry注册它到名为"MovieLister"的接口下。最后,为了在我们启动Bundle时看到有意思的事情,激活器靠MovieLister运行了一个简单的搜索并打印出结果。

我们需要建立并安装这个Bundle。我这次将不给出完整的介绍——你可以复习一下上个部分并使它工作。记得你需要创建一个manifest文件,并且它指示osgitut.movies.impl.MovieListerActivator类为Bundle-Activator。并且Import-Package行需要包含三个包,名称是org.osgi.frameworkorg.osgi.util.trackerosgitut.movies

一旦你已经安装了MovieLister.jarEquinox运行时,你可以启动它。在那点上你将看到两条信息中的一条,依赖于是否这个BasicMovieFinderBundle在上一次一直在运行。如果它没有运行,你将看到: 
1
2
osgi> start 2
Could not retrieve movie list


但是如果它正在运行你将看到: 
1
2
osgi> start 2
Title: Spirited Away


通过停止和启动各个Bundle,你将能够显示任一信息。并且那些几乎是这部分的全部,除了记得我说过当一个服务无效时等待的事情?代码我们已经有了,这实际上是很琐碎的:简单的改变MovieListerImpl16行来调用waitForService(5000)ServiceTracker上代替getService(),并且添加try/catch块来捕捉InterruptedException

listByDirector()
方法等待5000毫秒的原因是要等MovieFinder服务被显示出来。如果一个MovieFinder服务那时已经安装了,当然,如果它已经存在——我们将立即获得它来使用。

通常,我们建议不要像这样挂起线程。特别在这种情况下,它可能很危险因为listByDirector()方法实际是从我们的Bundle激活器的start方法调用的,这是一个从框架线程调用的方法。
激活器意味着要迅速地返回,因为当一个Bundle启动了的时候会有一定数量的其它事情。事实上最坏的情况下我们可能引起死锁,因为我们会进入一个在属于框架对象的synchronized块,那可能已经被其他的一些给锁定了。通常的做法是从不在Bundle激活器的start方法中处理任何长时间运行或者阻塞操作,或者在框架直接调用的代码不做任何操作。

在下个部分中,我们将看看在使用应付神奇的第三个选项:"Don't exist in the first place"。稍候!


参考:
原始文章http://www.eclipsezone.com/eclipse/forums/t90796.html

没有评论: