OSGi开发起步(Getting Started with OSGi)-1第一个OSGi模块
原文出自: http://neilbartlett.name/blog/osgi-articles/ - Getting Started with OSGi
http://hi.baidu.com/tinawhisper/home
本文系翻译文章,但并未严格按照原文翻译。
1. 第一个OSGi模块
本文期望展示OSGi开发环境的简洁性,因此这里没有使用eclipse开发环境。本文只使用文本编辑器和基本的命令行工具进行OSGi程序开发。
本文的第一个例子相对其他示例有些长,这是因为我们需要建立一个基本的工作环境。在开始之前,我们需要一个可以运行的OSGi框架。现在我们可以在三种开源框架中进行选择:Apache Felix,Knopflerfish和Equinox。本文所有编写的代码对以上三个框架来说都是可用的,只是在使得这些代码运行的指令上有所区别。本文选择eclipse使用的Equinox。首先需要找到eclipse安装目录下的org.eclipse.osgi_3.2.1.R32x_v20060919.jar并复制此文件到一个空目录(文件中的版本号会根据安装eclipse的不同而不同)。为了使命令行参数短一些,把刚才复制过来的jar文件更名为equinox.jar。现在就可以在命令行中定位到刚才那个空目录并执行这个命令:
> java -jar equinox.jar -console
几秒钟后,命令窗口中会出现osgi>提示。如果是这样,就说明OSGi已经运行起来了(意外总会发生,比如你没有安装java!)。
osgi>命令行提示允许我们运行Equinox中的命令来进行整个框架的控制。可以在提示符后输入help获得所有命令的列表。经常使用的一个命令是ss。ss表示简要状态(short status)的意思。ss会列出当前已经安装到框架中所有模块(bundle)和它们状态的列表。在OSGi中bundle表示一个模块,这与eclipse中的插件(plug-ins)具有等同的概念。
输入ss后,Equinox输出为:
Framework is launched.
id State Bundle
0 ACTIVE system.bundle_3.2.1.R32x_v20060919
以上输出告诉我们当前只安装了一个已经启动的模块:系统模块(System bundle)。此模块是OSGi的一个总是安装并处于活动状态的特殊模块,它代表了框架本身。
现在我们开始编写一个模块。在相同的目录下,创建一个名称为HelloActivator.java的文件并复制以下代码到文件中:
import org.osgi.framework.*;
public class HelloActivator implements BundleActivator {
public void start(BundleContext context) { System.out.println("Hello Eclipse!"); }
public void stop(BundleContext context) { System.out.println("Goodbye Eclipse!"); }
}
一个模块还需要一个元文件(manifest file)对它的各种元信息进行声明:如模块的名称,版本等信息。所以现在还需要创建一个名称为HelloWorld.mf的文件并复制下文的文本到此文件中。特别需要注意的是要在此文本文件的最后保留一个空白行,否则打包命令jar在打包时会截短文件(最后一行丢失)。这样就会使得生产的jar包中的元文件缺失一行,导致jar文件读取和运行失败。
Manifest-Version: 1.0
Bundle-Name: HelloWorld
Bundle-Activator: HelloActivator
Bundle-SymbolicName: HelloWorld
Bundle-Version: 1.0.0
Import-Package: org.osgi.framework
现在另外打开一个命令行窗口使用如下命令构建一个jar文件:
> javac -classpath equinox.jar HelloActivator.java
> jar -cfm HelloWorld.jar HelloWorld.mf HelloActivator.class
回到原先的OSGi控制台,输入install file:HelloWorld.jar。控制台会输出"Bundle id is 1" ,接着输入ss命令就可以得到如下输出信息:
Framework is launched.
id State Bundle
0 ACTIVE system.bundle_3.2.1.R32x_v20060919
1 INSTALLED HelloWorld_1.0.0
刚才开发的HelloWorld模块安装到了OSGi框架中,但此模块还没有运行起来。现在输入命令start 1。命令中的1是安装HelloWorld模块完成后,OSGi赋值给此模块的ID号。当运行启动命令后,控制台显示消息"Hello Eclipse!"。接着输入stop 1就会得到消息"Goodbye Eclipse!"。
这些命令都做了些什么事情?上面的代码实现了一个BundleActivator接口,这个接口用于允许框架通知我们一些重要的生命期事件。当模块启动时,框架调用start方法,而如果模块停止时,框架则调用stop方法。还有一个要说明的是元文件中有一行声明"Bundle-Activator: HelloActivator"就是用于通知框架在一个模块中那个类是启动类(activator)。通常我们使用一个完整的类名称,但在此示例中为了简单而只使用的缺省包名。
术语表:
Bundle: 模块
Activator:启动器
Component:组件
Service:服务
Sevice Register:服务注册表
Declarative Service: 服务声明,简称DS
OSGi Framework: OSGi框架或者框架
manifest file: 元文件
=========================================================================================
OSGi开发起步(Getting Started with OSGi)-2与OSGi框架进行交互
原文出自: http://neilbartlett.name/blog/osgi-articles/ - Getting Started with OSGi
本文系翻译文章,但并未严格按照原文翻译。
2. 与OSGi框架进行交互
上一节举了一个简单的示例模块HelloWorld:启动和停止时输出消息的模块。该模块通过实现BundleActivator接口定义的start和stop函数方法完成了这些消息的输出。如果回过头来仔细的研究start和stop方法的定义,我们可以发现函数定义了一个BundleContext类型的参数。在本节描述参数BundleContext以及它的用法。
BundleContext是OSGi框架提供给一个模块的'通行票据'。当模块需要与框架进行交互和通信时,就可以使用BundleContext。实际上,这也是一个模块可以唯一与OSGi框架进行交互的渠道。OSGi框架在每个模块启动时会通过此模块的BundleActivator派送一个票据:BundleContext。
如果上一节的OSGi控制台还在运行状态,下面的工作就不需要重启控制台。如果控制台关闭了就需要使用下面的命令启动它。
> java -jar equinox.jar -console
输入ss后就会发现上次安装过的HelloWorld模块。即便是OSGi框架关闭重启了之后,HelloWorld模块也会保持上节的状态。这主要是因为OSGi框架进行了模块的持久化存储处理。
在这一节我们完成一个查找并卸载HelloWorld模块的模块。在OSGi控制台可以使用uninstall命令简单的完成这项任务。但是这一节希望通过OSGiAPI编码的方式实现此任务。
创建一个名称为HelloWorldKiller.java的文件,输入下面的代码:
import org.osgi.framework.*;
public class HelloWorldKiller implements BundleActivator {
public void start(BundleContext context) {
System.out.println("HelloWorldKiller searching...");
Bundle[] bundles = context.getBundles();
for(int i=0; i<bundles.length; i++) {
if("HelloWorld".equals(bundles[i]
.getSymbolicName())) {
try { System.out.println("Hello World found, " + "destroying!"); bundles[i].uninstall(); return; } catch (BundleException e) { System.err.println("Failed: " + e.getMessage()); }
}
}
System.out.println("Hello World bundle not found");
}
public void stop(BundleContext context) { System.out.println("HelloWorldKiller shutting down"); }
}
接着创建此模块的元文件,注意文件最后要留一个空白行。在元文件中输入以下内容:
Manifest-Version: 1.0
Bundle-Name: HelloWorldKiller
Bundle-Activator: HelloWorldKiller
Bundle-SymbolicName: HelloWorldKiller
Bundle-Version: 1.0.0
Import-Package: org.osgi.framework
编译和构建模块对应的jar文件:
> javac -classpath equinox.jar HelloWorldKiller.java
>jar -cfm HelloWorldKiller.jar HelloWorldKiller.mf HelloWorldKiller.class
在控制台安装构建完成的新模块:install file:HelloWorldKiller.jar。然后输入ss。模块的状态类似以下列表:
id State Bundle
0 ACTIVE system.bundle_3.2.1.R32x_v20060919
1 ACTIVE HelloWorld_1.0.0
2 INSTALLED HelloWorldKiller_1.0.0
接着启动我们的新模块HelloWorldKiller:start 2。其输出信息为:
HelloWorldKiller searching...
Hello World found, destroying!
Goodbye Eclipse!
注意最后一行是先前那个HelloWorld模块的输出信息。当这个模块处于运行状态并且启动HelloWorldKiller时,HelloWorld模块被停止并进行了卸载。这种操作触发了HelloWorld模块启动器的stop方法。
输入命令ss就会发现HelloWorld模块消失了。
id State Bundle
0 ACTIVE system.bundle_3.2.1.R32x_v20060919
2 ACTIVE HelloWorldKiller_1.0.0
这里的问题是:安全性如何保证?好像任何模块都可以卸载其他模块。实际上OSGi定义了一个复杂的安全层次机制。这个安全机制提供了对模块与框架交互的精确控制。这样就可以把卸载模块的权限限制在某些特定的管理模块。重要的是OSGi进行安全控制基本上是一个配置过程,所以在这个讲解系列里我们关注点主要放在编码上。
在开始下节讲解之前,我们可以了解一下BundleContext接口,看看此接口提供了哪些方法可供使用。比如,我们可以使用installBundle方法安装一个模块到OSGi中。或者也可以获取当前所有安装的模块列表并打印这些模块最近一次修改的日期和时间。具体的方法可以参考OSGi R4的API javadoc .
术语表:
Bundle: 模块
Activator:启动器
Component:组件
Service:服务
Sevice Register:服务注册表
Declarative Service: 服务声明,简称DS
OSGi Framework: OSGi框架或者框架
manifest file: 元文件
=========================================================================
OSGi开发起步(Getting Started with OSGi)-3 模块间依赖
3. 模块间依赖
上两节展示了模块的启动和停止以及模块与系统框架的交互方式。但是问题是模块真正的用途是什么呢?
模块是功能集合。模块使我们能够把庞大纷杂的项目划分成可管理的单元。这些单元可以方便的载入到OSGi运行环境。问题是,不管我们喜欢还是不喜欢,模块几乎总是依赖于其他模块。对jar文件来说,从来没有一个可靠的方式来指定不同jar之间的依赖关系(注意,jar元文件中的类路径也不是一个可靠的方法)。因此你永远不能真正的确定: Jar中的代码会正常工作?还是会在运行时失控抛出一个ClassNotFoundException异常?
OSGi以简洁的方式解决了这个问题。仅仅这样说还不如通过功能展示来的更有说服力些。所以让我们马上开始编码来做到这点。
不幸的是直至现在,我们一直使用默认包的方式进行类的开发。这种方式不适应更复杂问题的编码,所以现在需要使用java包的形式进行编码开发和功能划分。首先以一个非常简单的JavaBean类开始,复制以下代码到文件osgitut /movies/ Movie.java :
package osgitut.movies;
public class Movie {
private final String title;
private final String director;
public Movie(String title, String director) { this.title = title; this.director = director; }
public String getTitle() { return title; }
public String getDirector() { return director; }
}
现在接着在此包内创建一个接口。复制以下代码到文件osgitut /movies/ MovieFinder.java :
package osgitut.movies;
public interface MovieFinder { Movie[] findAll(); }
然后把这两个类封装为一个模块。当然,这个模块十分简单而且用途也不大。但是对于本节探讨的内容已经足够了。同前面一样,模块还必要有一个对应的元文件MoviesInterface.mf:
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Movies Interface
Bundle-SymbolicName: MoviesInterface
Bundle-Version: 1.0.0
Export-Package: osgitut.movies;version="1.0.0"
文件中多了一行:Export-Package。这个属性表明包osgitut.movies从这个模块中导出。而问题是java的jar文件中所有的东西都是导出的。但是我们难道不想只导出包中的部分代码而保持其他部分只在包内部可见吗?我们当然可以使用private或者protected限制类的访问范围,而这样一来同一个jar中其他包也不能访问这些类了。所以OSGi有效地引进了一种新的代码保护级别:如果模块中的包名没有在Export-Package头中列出,那么此包就只能在这个模块中才能访问到。
此外,我们还在导出包名称后挂接了软件版本号。在下一个阶段的讲解中我们会发现这十分重要。当然,这也不是绝对有必要提供一个版本。但如果你不指定版本号时,OSGi将自动为导出的包指定版本" 0.0.0 "。始终为导出的包指定明确的版本是一个优秀的实践经验。
现在构建此模块:
> javac osgitut/movies/Movie.java osgitut/movies/MovieFinder.java
> jar -cfm MoviesInterface.jar MoviesInterface.mf osgitut/movies/*.class
完成此项工作后我们不要急于安装此模块。在这之前我们还需要构建此模块的一个依赖模块。即一个实现了MovieFinder接口的具体实现类:osgitut/movies/impl/BasicMovieFinderImpl.java :
package osgitut.movies.impl;
import osgitut.movies.*;
public class BasicMovieFinderImpl implements MovieFinder {
private static final Movie[] MOVIES = new Movie[] { new Movie("The Godfather", "Francis Ford Coppola"), new Movie("Spirited Away", "Hayao Miyazaki") };
public Movie[] findAll() { return MOVIES; }
}
还需要一个元文件:BasicMovieFinder.mf。
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Basic Movie Finder
Bundle-SymbolicName: BasicMovieFinder
Bundle-Version: 1.0.0
Import-Package: osgitut.movies;version="[1.0.0,2.0.0)"
注意,在这里我们导入了一个由其他模块导出的包osgitut.movies。而且还同时为此导入包设置了版本范围。框架在运行时刻使用此版本范围匹配一个适合的导出包。OSGi表达版本范围使用的是通用的数学式语法:方括号表示包含,圆括号表示排除。在上面这个示例元文件中我们使用这个语法有效的指定了一个1.x的版本。
对本节示例来说,在导入部分加入版本限制并不是必需的。但这是一个通用的实践准则。
现在按照以下命令构建第二个模块:
> javac -classpath MoviesInterface.jar osgitut/movies/impl/BasicMovieFinderImpl.java
> jar -cfm BasicMovieFinder.jar BasicMovieFinder.mf osgitut/movies/impl/*.class
最后我们可以准备在OSGi环境中试验这两个模块。第一步就是安装BasicMovieFinder模块并运行ss命令。这时此模块的状态显示为INSTALLED。
id State Bundle
0 ACTIVE org.eclipse.osgi_3.3.0.v20070208
4 INSTALLED BasicMovieFinder_1.0.0
(说明,你的模块列表可能与上面这个示例列表存在一定的差异。实际上,模块id号会根据你上次安装和卸载HelloWorld模块的次数而确定。因此下面示例中的id号要根据你运行环境中相应的id进行替换。)
INSTALLED表示框架载入了这个模块,但还没有完成此模块依赖关系的解析。一个让框架执行模块依赖解析的命令是refresh。这里输入refresh 4然后是ss命令就可以看到:
id State Bundle
0 ACTIVE org.eclipse.osgi_3.3.0.v20070208
4 INSTALLED BasicMovieFinder_1.0.0
这个模块没有解析成功并仍然处于INSTALLED状态。这个问题出在我们并没有安装其依赖的那个包含Movie类和MovieFinder接口的模块。如果想确认是不是这个问题,在框架控制台输入diag 4以获取此模块的调试信息:
file:BasicMovieFinder.jar [4]
Missing imported package osgitut.movies_[1.0.0,2.0.0).
看来确实是因为框架运行环境没有任何可供导出的osgitut.movies包。那么现在就安装MoviesInterface.jar模块并运行ss。大致的输出结果为:
id State Bundle
0 ACTIVE org.eclipse.osgi_3.3.0.v20070208
4 INSTALLED BasicMovieFinder_1.0.0
5 INSTALLED MoviesInterface_1.0.0
最后一步就是通知框架重新执行BasicMovieFinder模块的依赖解析。依旧使用refresh 4命令。输出的结果为:
id State Bundle
0 ACTIVE org.eclipse.osgi_3.3.0.v20070208
4 RESOLVED BasicMovieFinder_1.0.0
5 RESOLVED MoviesInterface_1.0.0
BasicMovieFinder模块现在处于已解析状态。这是一个基本步骤。因为只有处于已解析的模块才能启动。
需要注意的是通常情况下并不需要使用本节展示的手工方式进行模块依赖解析。一般来说模块会在它们需要的时候自动进行解析。例如,我们可以注意到MoviesInterface模块也处理已解析状态,而我们并没有针对此模块显示的执行refresh命令。
下一节我们进行OSGi服务开发。
=======================================================================================
OSGi开发起步(Getting Started with OSGi)-4注册一个服务
4 注册一个服务
我们终于可以开始OSGi服务的讲解了。在我看来,服务层是OSGi中最具特点的部分。在接下来的几节你就会充分体会到这一点。
上节讲解的是MovieFinder接口的例子。这个接口被MovieLister用来查找电影。这个例子是一个Ioc(控制反转)的例子。
MovieLister并不太关心原始电影数据的来源,所以我们使用了MovieFinder接口隐藏了这些细节。关键在于我们可以使用另外一种实现替代现有的MovieFinder:比如从数据库或者亚马逊web服务进行电影查询。而MovieLister只依赖与接口而不是具体的实现。
虽然不错,但是有些时候我们实际上还是要把一个MovieFinder的具体实现明确指定给MovieLister。我们是用一个外部容器把这个具体实现"推"给了MovieLister,而不是让MovieLister自己调用一个查找方法发现这个具体实现对象。
这就是IoC术语的由来。现在存在大量的这类外部容器,如PicoContainer,HiveMind,Spring以及EJB3.0。但是直至现在这些容器都有一个基本限制:它们都是静态的。一旦一个MovieFinder指定给了MovieLister,两者在整个JVM虚拟机生命周期内都一直关联。
OSGi支持具有动态特性的IoC模式。OSGi支持动态的提供给MovieLister一个MovieFinder的实现然后移除它们。这样我们就能在一个支持文本电影数据查询的应用程序和一个依赖亚马逊web服务电影数据查询的应用程序之间进行热切换。
OSGi服务层用于支持以上特性。我们要做的只是在服务注册表中注册MovieFinder为其中的一个服务就能支持热切换特性。这可是相当简单啊。同样的MovieLister的功能也可以由一个MovieLister服务提供。一个服务与一个普通的java对象(POJO)没有什么不同,它只是使用java接口名进行了服务注册而已。
在本节我们就看看如何在注册表中注册服务。接下来就试试怎样编码从注册表获取服务并提供给一个MovieLister。
在上节代码基础上增加一个BasicMovieFinder。不需要更改任何现有的类,只是在增加一个启动器类。下面就是这个启动类osgitut/movies/impl/BasicMovieFinderActivator.java:
package osgitut.movies.impl;
import org.osgi.framework.*;
import osgitut.movies.*;
import java.util.Properties;
import java.util.Dictionary;
public class BasicMovieFinderActivator implements BundleActivator {
private ServiceRegistration registration;
public void start(BundleContext context) { MovieFinder finder = new BasicMovieFinderImpl(); Dictionary props = new Properties(); props.put("category", "misc"); registration = context.registerService( MovieFinder.class.getName(), finder, props); }
public void stop(BundleContext context) { registration.unregister(); }
}
接下来就是修改BasicMovieFinder.mf文件:
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Basic Movie Finder
Bundle-SymbolicName: BasicMovieFinder
Bundle-Version: 1.0.0
Bundle-Activator: osgitut.movies.impl.BasicMovieFinderActivator
Import-Package: org.osgi.framework,
osgitut.movies;version="[1.0.0,2.0.0)"
在这个元文件中相对上节变化了两行。第一个增加了Bundle-Activator行。此行描述当前模块使用的启动模块(以前我们并没有使用)。此外还在导入包行中增加了org.osgi.framework。本节以前版本的模块并没有与框架进行交互,因此也就不需要导入OSGiAPI相关的包。
现在重新构建BasicMovieFinder.jar。
> javac -classpath equinox.jar:MoviesInterface.jar osgitut/movies/impl/*.java
> jar cfm BasicMovieFinder.jar BasicMovieFinder.mf osgitut/movies/impl/*.class
回到OSGi控制台,上节安装的BasicMovieFinder.jar还在框架中运行。因此要更新这个模块,运行update N命令,N是这个模块的ID(使用ss可以得到此ID)。接着使用start N命令启动此模块。接着我们看到什么-基本上也没看到些什么。
实际上,模块已经在OSGi服务注册表中注册了,但是并没有其他人在另外一端要使用此服务。因此这个服务的注册过程并没有产生任何可视效果。如果我们想向自己保证我们的代码确实运行了,也大可不必掘地三尺。执行下面这个命令就可以了:
services (objectClass=*MovieFinder)
我们会看到下面的输出:
{osgitut.movies.MovieFinder}={category=misc, service.id=22}
Registered by bundle: file:BasicMovieFinder.jar [4]
No bundles using service.
看到了,服务的确注册了。在下节我们就讲解如何查找这个服务并在另外模块中使用此服务。
=============================================================================
OSGi开发起步(Getting Started with OSGi)-5使用一个服务
5. 使用一个服务:Consuming a Service
上节讲解了如何注册一个服务。现在就是在一个模块中查找和使用这些服务的时候了。
与上节一样,我们还是使用电影搜索的例子。我们已经构建了一个MovieFinder服务并在服务注册表中进行了服务注册。现状我们需要构建一个MovieLister来使用这个MovieFinder服务搜索某个导演执导的影片。这里假设MovieLister也是一个服务,也可以被其他诸如GUI程序使用。问题是,OSGi服务是动态的:它们可能有效也可能处理不可使用状态。这也就是说有时存在需要使用MovieFinder服务而它却正好不能使用的情况。
那么当MovieFinder不可用的时候MovieLister怎么办?显然,调用MovieFinder是MovieLister能有效运作的核心部分。实际上我们也只有以下几种选择:
- 产生错误。如返回null或者抛出一个异常。
- 等待。
- 不让这种情况出现。
在本文我们选择比较简单的前两种选择。The third option may not even make any sense to you yet, but hopefully it will after we look at some of the implications of the first two.
首先我们需要定义MovieLister服务的接口。复制以下内容到文件osgitut/movies/MovieLister.java:
package osgitut.movies;
import java.util.List;
public interface MovieLister { List listByDirector(String name); }
然后是文件osgitut/movies/impl/MovieListerImpl.java :
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)Unknown macro: { 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++)Unknown macro: { if(movies[i].getDirector().indexOf(name) > -1) { result.add(movies[i]); } }
return result;
}
}
上面的代码可能是至今我们最长的一段示例代码了。这里面有些什么呢?第一,实际的影片搜索逻辑分离到了doSearch(string, MovieFinder)方法中。此方法帮助我们隔离了搜索代码与OSGi代码。虽然,这里制定的搜索办法并不是很高效,但对于一个以示例为目的的程序已经足够了:实际上只是实现了一个只有两部影片的数据库。
第二部分是listByDirector(String name)方法。这个方法使用了一个ServiceTracker对象从服务注册表获取一个MovieFinder。ServiceTracker是一个十分有用的类。它封装和抽象了一些难于使用的OSGi底层API。无论如何我们还是要检查需要的服务是否实际可用。此服务假定在MovieLister构造函数中把ServiceTracker传递过啦。
注意,你可能在其他部分看到一些没有使用ServiceTracker从服务注册表查询服务的代码。例如,使用BundleContext的getServiceReference和GetSevice方法。但这样一来就需要编写一些更复杂的代码并需要仔细进行一些清理工作。在我看来,使用底层API并不能得到太多的好处,反倒是会引起许多问题。最好的选择还是使用ServiceTracker。
在一个模块的启动器中是创建ServiceTracker的最佳地点。复制以下代码到文件osgitut/movies/impl/MovieListerActivator.java:
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"); } elseUnknown macro: { for (Iterator it = movies.iterator(); it.hasNext();) { Movie movie = (Movie) it.next(); System.out.println("Title: " + movie.getTitle()); } }
}
}
现在这个启动器可以启动服务查找感兴趣的影片了。首先,在start方法中创建了一个ServiceTracker对象。这个对象由之前写的MovieLister使用。接着就打开ServiceTracker并设置此对象跟踪注册表中的MovieFinder服务实例。然后创建MovieListerImpl对象并使用MovieLister为名称在注册表中注册一个服务。最后,为了证明服务确实运行,在启动模块时启动器使用MovieLister执行了一个简单的搜索并输出了搜索结果。
到此,我们可以使用前几节的方法构建和安装我们设计的这些模块。要记得创建元文件,并在元文件中正确的设置Bundle-Activator为osgitut.movies.impl.MovieListerActivator。还有就是在导入包中包含以下三个包:org.osgi.framework , org.osgi.util.tracker and osgitut.movies。
一旦把MovieLister.jar安装到了OSGi框架运行环境,就可以启动这个模块。根据BasicMovieFinder模块是否处于运行状态,此时OSGi控制台会输出不同的消息。
如果没有运行,会输出:
osgi> start 2
Could not retrieve movie list
如果处于运行状态则会输出:
osgi> start 2
Title: Spirited Away
在停止和启动模块的时候,以上消息都会在控制台出现。还记得我们可以在服务处于不可用状态是选择等待吗?使用上面的代码,只需要做简单的修改:在MovieListerImpl的第16行中使用ServiceTracker的waitForService(5000)代替getService(),同时增加一个处理InterruptedException的try/catch块。
这会导致listByDirector()方法阻塞5000毫秒等待MovieFinder服务可用。如果这时刚好MovieFinder服务安装并且可以使用,这个方法就可以立即获取到这个服务。
通常并不建议以这样的方式挂起一个线程。在实践中,这样做是相对危险的。因为listByDirector方法实际是由模块启动器中的start方法调用的。而模块启动器start方法又是由一个框架线程调用。启动器必须快速返回,因为当前一个模块启动是还需要执行许多其他的工作。实际上在最糟糕的情况下这种等待的做法可能导致一个死锁。因为我们进入了一个框架对象的同步区,而这个同步区可能已经被其他现场锁定。通常的建议是不要在模块启动器的start方法或者其他任何框架直接调用的代码中执行任何长时间或者阻断性的操作。
=================================================================
OSGi开发起步(Getting Started with OSGi)-6(1)动态服务跟踪
6 动态服务跟踪:Dynamic Service Tracking
上节讲解了如何使用一个服务:MovieLister使用MovieFinder查找由某个导演指导的影片。同时还涉及了处理OSGi服务动态特性的各种策略,特别介绍了MovieLister找不到一个有效的MovieFinder服务时的处理办法。
还有一种上节没有涉及的情况是:如何处理同时有多个MovieFinder服务可用的情况?因为任何模块都可以使用MovieFinder接口注册一个服务,并且对于服务注册机制来说任何模块都是同等对待的。
我们可以简单的忽略这个问题,这也是上节代码的实际处理逻辑。通过调用ServiceTracker上的getService()方法,我们选择由服务注册表任意提供一个MovieFinder服务。有一些参数可以调整注册表的选择策略(比如在服务注册时设置的SERVICE_RANKKING属性),但是作为一个服务的使用者我们并没有参与这个服务选择过程。并且实际上尽量少的控制并不是一件坏事,因为我们应该可以使用任何一个MovieFinder服务。这也是使用接口的原因。
从另外一方面来说,在一些情况下注册和使用多个服务也大有用途,例如,如果有多个有效的MovieFinder服务,这就意味这存在多个影片数据来源可供MovieLister使用。如果可以通过使用所有的这些服务进行影片查找,我们就可以获得更大的搜索网络空间并提供给用户更好的搜索结果。
另一个问题是上节遗留下来的:在没有任何一个MovieFinder服务可用时,MovieLister应该如何正确处理?上节的处理逻辑是在服务无效时简单的在listByDirector调用时返回null。But what if we made it impossible for methods on MovieLister to be called when the MovieFinder isn't present?
MovieLister与MovieFinder一样是一个服务。如果MovieFinder服务消失了,MovieLister服务是不是也应该消失?也就是说,我们希望MovieLister与MovieFinder之间有一个一对多的依赖关系。本文的最后一节,我们介绍零对一依赖关系。
首先使用下面的代码修改MovieListerImpl类:
package osgitut.movies.impl;
import java.util.*;
import osgitut.movies.*;
public class MovieListerImpl implements MovieLister {
private Collection finders =
Collections.synchronizedCollection(new ArrayList());
protected void bindFinder(MovieFinder finder) { finders.add(finder); System.out.println("MovieLister: added a finder"); }
protected void unbindFinder(MovieFinder finder) { finders.remove(finder); System.out.println("MovieLister: removed a finder"); }
public List listByDirector(String director) {
MovieFinder[] finderArray = (MovieFinder[])
finders.toArray(new MovieFinder[finders.size()]);
List result = new LinkedList();
for(int j=0; j<finderArray.length; j++) {
Movie[] all = finderArray[j].findAll();
for(int i=0; i<all.length; i++) {
if(director.equals(all[i].getDirector())) { result.add(all[i]); }
}
}
return result;
}
}
上面的代码移除了MovieListerImpl上所有的OSGi依赖。MovieListerImpl现在是一个纯POJO了。当然,它还需要其他对象协助跟踪MovieFinder服务并通过bindFinder方法提供此服务。因此我们还需要创建一个新的java文件:osgitut/movies/impl/MovieFinderTracker.java。
package osgitut.movies.impl;
import org.osgi.framework.*;
import org.osgi.util.tracker.*;
import osgitut.movies.*;
public class MovieFinderTracker extends ServiceTracker {
private final MovieListerImpl lister = new MovieListerImpl();
private int finderCount = 0;
private ServiceRegistration registration = null;
public MovieFinderTracker(BundleContext context) { super(context, MovieFinder.class.getName(), null); }
private boolean registering = false;
public Object addingService(ServiceReference reference) {
MovieFinder finder = (MovieFinder) context.getService(reference);
lister.bindFinder(finder);
synchronized(this) { finderCount ++; if (registering) return finder; registering = (finderCount == 1); if (!registering) return finder; }
ServiceRegistration reg = context.registerService(
MovieLister.class.getName(), lister, null);
synchronized(this) { registering = false; registration = reg; }
return finder;
}
public void removedService(ServiceReference reference, Object service) {
MovieFinder finder = (MovieFinder) service;
lister.unbindFinder(finder);
context.ungetService(reference);
ServiceRegistration needsUnregistration = null;
synchronized(this) {
finderCount --;
if (finderCount == 0) { needsUnregistration = registration; registration = null; }
}
if(needsUnregistration != null) { needsUnregistration.unregister(); }
}
}
==========================================================================================
OSGi开发起步(Getting Started with OSGi)-6(2)动态服务跟踪
上面这个类覆盖了上节讨论的ServiceTracker类,并且定制了服务有效和失效时ServiceTracker的行为。具体来说就是,当一个增加MovieFinder服务时调用addingSevice方法,当删除一个MovieFinder服务时调用removedService方法。此外还有一个modifiedService方法,只不过在本节处理逻辑中不需要而没有覆盖。
有必要仔细看看这两个方法中的处理逻辑。首先,在addingSevice方法中传入的参数是ServiceReference而不是一个实际服务实现对象。ServiceReference是一个轻量级的句柄对象。它可以作为一个参数任意的进行传输,并可以用来获取实际服务的属性。比如,服务在进行服务注册时传入的属性集。实际上,使用一个ServiceReference对象并不会导致OSGi框架增加实际服务的引用计数。你可以认为ServiceReference承担了类似java反射API中WeakReference类的角色。
在addingSevice方法中首先是通过ServiceReference获取一个实际的MovieFinder服务对象。这又要使用BundleContext:任何与OSGi框架的交互都是通过BundleContext接口进行的。幸运的是,ServiceTracker定义了一个我们可以直接使用的类型为BundleContext的保护成员context。
接下来我们使用bindFinder方法把获取到的MovieFinder服务绑定到MovieListerImpl。然后就把MovieListerImpl也注册为一个使用MovieLister接口的服务。注意,我们只在MovieListerImpl没有注册为服务的情况下才进行服务注册。这是因为,我们现在希望一个MovieLister使用多个MovieFinder服务。
最后,addingSevice方法返回一个Object。问题是,我们到底要返回一个什么对象呢?实际上,ServiceTracker并不关心addingSevice返回什么对象。addingSevice可以返回任何我们需要的对象。重点是,addingSevice返回的对象会在调用modifiedService或者removedService的时候重新传回。这也就是在removedService方法第一行见到的直接对object进行的MovieFinder类型转换。接着就使用此对象进行它与MovieLister的解绑,同时我们根据可以使用的MovieFinder服务是否为零进行MovieLister服务的反注册。
一般说来,任何在addingSevice中的行为都应该在removedService中进行状态清理。因此,我们应该在addingSevice中返回任何有助于我们需要在removedService中进行清理工作的对象。返回的可以是一个HashMap中的键值,也可以是一个ServiceRegistration对象,或者是一个实际的服务对象。
在removedService的最后一步,我们解绑了在addingSevice中绑定的服务。这一点十分重要,这个操作使得服务注册表减少服务使用计数值,从而可以使计数值为零进行服务释放。
现状我们还需要一个模块启动器osgitut/movies/impl/TrackingMovieListerActivator.java:
package osgitut.movies.impl;
import org.osgi.framework.*;
public class TrackingMovieListerActivator implements BundleActivator {
private MovieFinderTracker tracker;
public void start(BundleContext context) { tracker = new MovieFinderTracker(context); tracker.open(); }
public void stop(BundleContext context) { tracker.close(); }
}
不要忘记TrackingMovieLister.mf:
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Tracking Movie Lister
Bundle-SymbolicName: TrackingMovieLister
Bundle-Version: 1.0.0
Bundle-Activator: osgitut.movies.impl.TrackingMovieListerActivator
Import-Package: org.osgi.framework,
org.osgi.util.tracker,
osgitut.movies;version="[1.0.0,2.0.0)"
按照前几节的步骤进行模块的构建和部署。完成后,执行以下的步骤验证运行逻辑是否正确:
- 启动 BasicMovieFinder和TrackingMovieLister。检查是否出现"MovieLister:added a finder"消息。
- 停止 BasicMovieFinder。检查是否出现"MovieLister: removed a finder"消息。
- 执行service命令,检查是否MovieLister服务已经反注册。
本节内容展示的是一个十分强大的技术。我们把一个服务的生命周期与另一个服务(实际上是多个)的生命周期绑定。进一步,我们可以绑定MovieLister和第三个服务并可以依此类推。这样我们就构建了一个服务依赖图,这种依赖图与其他一些静态IOC容器构建的beans图不同。OSGi的服务依赖图更健壮,self-healing并具有自动调整能力。
另一方面,这一节的代码还存在一些问题。MovieFinderTracker和TrackingMovieListerActivator类会形成整个系统负载的瓶颈。当我们打算进行这个系统的扩展时,我们还不得不重复进行这些基本雷同的编码工作。因此在下一节我们讲解如何使用几行XML语句完成这些重复和繁琐的工作。
在下一节不再在一个目录下使用命令行构建我们的系统。本文的目标是展示OSGi是一个简单而强大的框架,但并不妨碍我们使用更高效的方式比如eclipse的IDE完成这些工作。
==================================================================================================
OSGi开发起步(Getting Started with OSGi)-7服务声明介绍
7 服务声明介绍:Introducing Declarative Services
服务声明(简称DS)规范是OSGi最新的内容之一。增加此服务规范是为了在多个模块之间有效协调服务。这种服务协调工作本身并不复杂(如上节我们展示的那样),但是这项工作总是面临那些恼人的代码。而且具体实现时还必须注意处理各种线程问题。总的来说,模块服务协作这件工作是一件极易出错的工作。
最早试图解决服务协作问题的是一个称为Service Binder的工具。这个工具由Humberto Cervantes 和 Richard Hall开发。Service Binder设计了自动服务依赖管理特性。这个特性允许开发者专注于服务。服务之间的协作和依赖通过声明实现,而所有的声明都使用XML完成。
服务声明规范是由Service Binder发展而来,并成为了OSGi4.0标准的一部分。
前面几节的示例都是使用的命令行完成所有的编码、配置、构建和运行工作。从这节开始则使用EclipseSDK。需要明确的是,本文中介绍的任何技术和方法并不依赖于Eclipse。尽管Eclipse可以很好的帮助我们完成很多工作,但同样的我们也可以使用NetBeans,Intellij或者vi来完成这些工作。
首先我们需要下载Equinox的服务声明实现包。可以在Equinox的下载页面下载最新的org.eclipse.equniox.ds_x.x.x_xxxx.jar。下载完成后,把下载的jar复制到Eclipse的plugins目录,并重新启动Eclipse。
现在重新创建一个模块。在Eclipse中使用插件工程向导:
l 在主菜单中选择File->New->Project。
l 选择Plug-in Project并点击Next。
l 输入工程的名称:SampleExporter。
l 在最下面的"This plug-in is targeted to run with"文字下选择"OSGi framework"以及下拉框中的"standard"。这个是个基本步骤:这一步可以防止我们使用OSGi框架实现之外的特性。
l 选择"Next"。在向导显示的下一个对话框中选择"Generate an activator..."。如果选择,点击"Finish"完成创建工作。
我们现在生成了一个空的模块工程。接下来的工作就是在这个工程中增加代码。为了使得示例尽量简单,我们使用在每个java运行环境中都存在的java.lang.Runnable接口提供服务。现在我们可以使用Eclipse的复制代码特性创建包和源文件。复制下面的代码,在Eclipse中选择SampleExporter工程的src目录,并执行Edit->Paste。
package org.example.ds;
public class SampleRunnable implements Runnable {
public void run() { System.out.println("Hello from SampleRunnable"); }
}
接下来我们需要创建一个XML文件。使用这个文件声明SampleRunnable是一个服务。在工程的顶级目录下创建一个名称为OSGI-INF的目录,新建一个名称为samplerrunnable.xml的文件然后复制下面的文本到文件中。
<?xml version="1.0"?>
<component name="samplerunnable">
<implementation class="org.example.ds.SampleRunnable"/>
<service>
<provide interface="java.lang.Runnable"/>
</service>
</component>
这是一个最简单的DS声明。这个声明表示有一个名称为"samplerunnable"的组件,这个组件使用接口java.lang.Runnable向服务注册器提供一个服务。同时还说明了这个模块是由org.example.da.SampleRunnable类实现的。
最后就是要告知服务声明runtime这个XML文件的位置。通知是通过在组件的MANIFEST.MF文件中增加一项说明实现的。在Eclipse中打开元文件页面,定位到"MANIFEST.MF"文件窗格。在这个文件内容的最后增加一行:
Sevice-Component: OSGI-INF/samplerunnable.xml
然后保存文件。在进行下一步之前,我们可以运行Equinox检查一下我们的模块是否可以正常运行。在Run菜单中选择"Run...",当运行对话框打开后选择左边树中的"Equniox OSGi Framework"并点击New按钮。如果你是一个熟练的Eclipse用户但又没有做过OSGi开发,这个对话框相对来说有些复杂(或者奇怪)。第一个标识为Bundles的窗格允许我们选择哪些Bundles是运行环境需要包含的,是否需要启动等。为了使用一个简化的运行环境,取消当前所有选中的Bundles,并重新选择以下的Bundles:
SampleExporter (underneath Workspace)
org.eclipse.equinox.ds (underneath Target Platform)
除此之外我们还有一些依赖的Bundles,这可以使用Add Required Bundles功能把这些依赖的Bundles添加进来。最好把"Validate bundles automatically prior to launching"也选中。最后,点击Run运行这个工程。一段时间后osgi>提示符就出现在Eclipse控制台。OSGi框架运行起来了,我们可以使用前几节的命令与框架进行交互。
我们开发的服务注册了没有?使用services命令检查一下。在命令的输出结果中,我们会发现类似的信息:
{java.lang.Runnable}={component.name=samplerunnable, component.id=1, service.id=22}
Registered by bundle: initial@reference:file:..../SampleExporter/ [5]
No bundles using service.
看来服务的确注册了。需要指出的是:服务是由我们的模块注册的,而不是由声明服务模块注册的,实际上,In fact DS has registered it on behalf of our bundle。还需要注意的是,服务的使用者并不需要为了使用这个服务而做什么特别的处理,它甚至不必知道我们使用了服务声明。这些服务使用者也可以使用DS或者直接使用OSGi代码来访问服务。
还有一点就是在上面的模块代码中并没有出现与OSGi标准相关的java代码。也就是说,本节我们编写的是一个POJO类。这也是DS的一个主要特点。
=================================================================================================================
OSGi开发起步(Getting Started with OSGi)-8(1)服务声明和依赖
8服务声明和依赖:Declarative Services and Dependencies
上节对DS做了初步的介绍。本节介绍如何使用DS声明的服务。上节我们使用java.lang.Runnable接口使用DS注册了一个服务,本节介绍如何创建一个依赖于这个服务的组件。
正如介绍的那样,DS规范使得开发者只需关注应用自身的逻辑,避免如前几节需要编写的那些OSGi服务"粘合"代码。使用DS,我们只要简单的编写代码就可以了。但在之间编码之前,我们还得创建一个Eclipse工程。使用上节的工程创建步骤创建一个名称为SampleImporter的工程。
复制下面的代码并粘贴到新建工程的src目录:
package org.example.ds;
import org.eclipse.osgi.framework.console.CommandInterpreter;
import org.eclipse.osgi.framework.console.CommandProvider;
public class SampleCommandProvider1 implements CommandProvider {
private Runnable runnable;
public synchronized void setRunnable(Runnable r) { runnable = r; }
public synchronized void unsetRunnable(Runnable r) { runnable = null; }
public synchronized void _run(CommandInterpreter ci) {
if(runnable != null) { runnable.run(); } else { ci.println("Error, no Runnable available"); }
}
public String getHelp() { return "\trun - execute a Runnable service"; }
}
这个类实现了CommandProvider接口。启动Equinox后在osgi>提示符可以执行一系列的命令。CommandProvider接口用于扩充这些命令。编写CommandProvider的原因是为了提供一种方便的交互式测试代码方式。在IBM developerWorks上Chirs Aniszczyk的一篇文章详细讨论了CommandProvider。
在上面的代码中我们注意到没有任何OSGi方法调用,实际上我们并不需要从org.osgi.*导入任何类。我们依赖的那个服务(在本示例中是一个java.lang.Runnable实例)是通过setRunnable方法提供的,并且通过unsetRunnable移除。我们可以认为这是某种形式的依赖注射。
另外两个方法getHelp和_run是CommandProvider接口定义的实现方法。_run方法名的下划线有些滑稽,但这也只是Equinox控制台API的一项odd特性,与OSGi的DS无关。在Equinox控制台中规定具有下划线的方法名称是一个控制台命令。因此定义一个_run方法就相当于在Equinox控制台中增加了一个run命令。另外一个需要注意的情况是,在上面这个类的实现代码中仔细的处理了runnable属性的线程安全特性。由于OSGi本质上是多线程的,因此在OSGi中线程安全是十分重要的。Frankly我们应该总是编写线程安全的代码。
和以前一样,我们还是需要一个包含DS声明的XML文件。复制下面的文本到这个插件工程的OSGI-INF/commandprovider1.xml文件中:
<?xml version="1.0"?>
<component name="commandprovider1">
<implementation class="org.example.ds.SampleCommandProvider1"/>
<service>
<provide interface="org.eclipse.osgi.framework.console.CommandProvider"/>
</service>
<reference name="RUNNABLE"
interface="java.lang.Runnable"
bind="setRunnable"
unbind="unsetRunnable"
cardinality="0..1"
policy="dynamic"/>
</component>
编写完成了XML文件后还要把下面这行加入到bundle元文件的最后:
Service-Component: OSGI-INF/commandprovider1.xml
===========================================================================================================
OSGi开发起步(Getting Started with OSGi)-8(2)服务声明和依赖
在XML文件中有两个我们已经介绍过的元素项:implementation和service。Implementation提供了组件的实现类,而service则告知DS把这个组件注册为一个服务。这个示例中的服务使用CommandProvider接口注册。这个接口使得Equinox控制台知晓这个CommandProvider的存在。
文件中还有一个以前没有介绍的reference元素项。reference用于向DS声明组件与一个服务之间的依赖关系。其中的name属性为任意字符串。名称属性用于对依赖进行命名(我们并不需要关心它的用途-实际上只是一个可读性的标识)。本示例中使用了组件依赖的接口名称来命名。bind属性指定一个实现类的方法名。当一个服务可用时,也就是一个Runnable的服务在服务注册表中注册时,DS就会调用此方法。DS使用这个方法传入这个新服务对象的引用提供给我们的组件使用。同样的,unbind属性是当一个服务不可用时,DS回调的方法。
Cardinality是一个能显示DS真正能力的属性。这个属性控制服务的依赖是可选的还是强制的、单依赖还是多依赖。这个属性可能的值为:
0..1: optional and singular, "zero or one"
1..1: mandatory and singular, "exactly one"
0..n: optional and multiple, "zero to many"
1..n: mandatory and multiple, "one to many" or "at least one"
在这个示例中我们使用的是0.1。也就是能cope那些不可用的服务。回头看以下_run方法中的代码,我们就会发现为了处理这种情况而进行的null检查代码。
现在还是来看看这个模块运行的情况。如果上节安装的SampleExporter还有效,在osgi控制台输入run命令会得到如下输出:
Hello from SampleRunnable
这证实了我们已经成功将上节编写的Runnable服务成功导入。接着运行stop命令停止SampleExporter模块。再运行run命令得到的输出是:
Error,no Runnable available
这个输出表明DS发现了Runnable服务已经停止并调用了unsetRunnable方法。
再看一下cardinality属性,如果把它的值改为1.1(改可选为强制)会怎样?更改这个属性后重新运行Equinox。在SampleExporter启动后运行run命令会得到与前面相同的结果。但是当停止SampleExporter后运行run,OSGi输出的是一个与前面不同的错误消息。实际上,Equinox控制台输出的是一个帮助消息。这个消息是对一个不可识别命令的标准错误输出消息。这也就是说我们这个命令执行服务已经被DS反注册了。当一个组件的强制依赖不能满足是,DS强制停止这个组件并反注册组件提供的任何服务。这就是Equinox不能识别run命令的原因。
如此方便的服务装配是选择DS的最佳原因。还记得使用ServiceTracker时,我们处理这种情况时不得不编写的那些代码吗?
还有一个值得关注的属性是ploicy。这个属性的值定义为static和dynamic之一。这两个状态值用于表明组件是否支持服务的动态切换。如果不支持,DS就没有必要每次在目标服务变化后停止原组件并创建一个新组件实例。停止后新建这个过程可是一个重负载性的过程,所以我们的建议最好还是尽可能的编码一个支持动态切换的组件。不过DS的这个属性缺省是静态的,因此在组件开发时还得手动的调整此属性为动态。
上面我们试了0.1和1.1,但是没有讲解多对多(0.n)的依赖。在服务注册表中不会仅仅只有一个Runnable服务注册。如果我们只是需要绑定其中的一个服务,那么我们所绑定的将是其中的任意一个服务。看来我们还需要一个runall命令用来运行服务注册表中已经注册的所有Runnable服务。
但就对上面那个组件来说,我们把cardinality属性更改为"0.n"时会出现什么情况?其实,更改后这个服务还是可以运行:只不过setRunnable不会只调用一次,DS会在每个Runnable服务注册是调用此方法一次。问题是多次调用会导致上面组件处理逻辑的混乱。因此为应对更通常的0.n的情况,就不能只是使用单个的Runnable成员,而是需要一个Runnable集合成员。下面是根据以上分析修改的类,把这个类的代码复制到工程的src目录下:
package org.example.ds;
import java.util.*;
import org.eclipse.osgi.framework.console.CommandInterpreter;
import org.eclipse.osgi.framework.console.CommandProvider;
public class SampleCommandProvider2 implements CommandProvider {
private List<Runnable> runnables =
Collections.synchronizedList(new ArrayList<Runnable>());
public void addRunnable(Runnable r) { runnables.add(r); }
public void removeRunnable(Runnable r) { runnables.remove(r); }
public void _runall(CommandInterpreter ci) {
synchronized(runnables) {
for(Runnable r : runnables) { r.run(); }
}
}
public String getHelp() { return "\trunall - Run all registered Runnables"; }
}
再创建OSGI-INF/commandprovider2.xml:
<?xml version="1.0"?>
<component name="commandprovider2">
<implementation class="org.example.ds.SampleCommandProvider2"/>
<service>
<provide interface="org.eclipse.osgi.framework.console.CommandProvider"/>
</service>
<reference name="RUNNABLE"
interface="java.lang.Runnable"
bind="addRunnable"
unbind="removeRunnable"
cardinality="0..n"
policy="dynamic"/>
</component>
最后是把这个文件按照如下方式加到元文件中:
Service-Component: OSGI-INF/commandprovider1.xml,
OSGI-INF/commandprovider2.xml
上面的XML文件中的DS声明与以前的基本一样,只是修改了bind和unbind方法的名称,以及cardinality的值(0.n)。现在就可以试着运行这个新的runall命令看看结果如何啦。欣赏完输出结果后,还可以试着更改cardinality为1.n看看有什么不一样的事情发生!
本节是这个讲解的最后一节。通过这几节的讲解我们发现OSGi并不神秘并且相当易用。接下来的事情就是不断的开发和使用它。不过,我还是强烈的建议在开始和进行过程中一定认真的学习一下OSGi R4 core和service规范。
=======================================================================================