星期三, 六月 25, 2008

[JAVA] OSGi入门:Declarative Services和依赖[译]

欢迎回来EclipseZone"OSGi入门"系列讲座。进入到今天这节课之前我希望你能找到所有讲座以前的部分在我的个人博客上。 

上次我们首次接触了Declarative Service。这次我们将看看Declarative Service的消费者那边。记得以前我们注册了一个服务在java.lang.Runnable接口下;现在我们将创建一个组件依赖在这个服务上。 

正如讨论的那样,Declarative Services规范是所有关于让你聚焦于你的代码的应用逻辑上,而不是在以前的课程中写的OSGi"glue"代码。鉴于此,我们只是潜入到代码中,但是因此我们需要创建一个项目。以下是最后课程中同样的步骤,但是使用"SimpleImporter"作为项目名称。 

现在从你的浏览器复制以下代码并粘帖到新建的Eclipse工程中的src文件夹里面: 
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
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的原因是用来提供一个方便我们测试代码互动性的方法。在IBM developerWork上的Chris Aniszczyk文章中有更多命令提供器的详细讨论。 

注意我们在这个类中没有调用任何OSGi API,事实上我们甚至无需从org.osgi.*包导入任何东西。我们依赖的这个服务——在这个情况中,一个java.lang.Runnable的实例——我们通过setRunnable方法提供使用并使用unsetRunnable方法带走它。我们可以认为这是一种依赖注入的形式。 

其他的两个方法,getHelp_run是为命令提供器实现的。没错,"_run"是个有趣的名称,它以下划线开头,但是那仅仅是一个Equinox控制台API的奇怪的特性,并且OSGi或者Declarative Service什么都不做。使用下划线开头模式的方法在Equinox控制台中变成命令,所以通过提供一个叫做_run的方法我们添加了一个命令"run"。另外注意的事情关于这个类我们需要关心确保runnable字段能够以线程安全的方式被更新和访问。线程安全在OSGi 相当重要,因为它本质上就是多线程的。坦率的说,我们始终应该把我们的代码写成线程安全的。 

如以前一样,我们需要提供一个XML文件包含DS声明得到这些。复制以下到OSGI-INF/commandprovider1.xml到你的plug-in项目中: 
1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version="1.0" encoding="UTF-8"?>
<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>


这是我们上次叙述疏忽了的另一个重要的步骤。(感谢Seamus Venasse指出来。)你需要编辑build.properties文件到在你的plug-in项目中,并且检查OSGI-INF文件夹是否选中。当Bundle使用Eclipse的导出向导导出时必须保证文件夹被包含在里面,或者使用PDE Build来建立。 

那么我们需要添加下面一行到Bundlemanifest中: 
1
Service-Component: OSGI-INF/commandprovider1.xml


这个定义有我们之前看过的同样的两个元素,implementationservice节点。implementation节点提供实现组件的类的名称,service节点告诉DS像一个服务那样注册这个组件。这种情况下我们注册到CommandProvider接口下,这个是我们如何让Equinox控制台获知关于我们的命令提供器的存在。 

下个节点叫做reference,是我们以前没有见过的——它对DS声明了我们的组件已经依赖在一个服务上。name属性只是一个依赖的名称的随意的字符串(我们仍然不需要担心这个是用来干什么的)并且interface属性指定我们依赖的接口的名称。bind属性是当一个服务有效时将调用实现的类中的一个方法名称,换句话说,当一个Runnable服务用Service Registry注册了,DS将获取一个此新服务的引用并提供使用这个指定的方法提供给我们的组件。同样的unbind属性是当我们使用的一个服务变成无效的时候通过DS调用它。 

Cardinality属性显示了DS真正的强大。这个属性控制是否这个依赖是可选的或是必需的,并且是否它是单独的或多个的。可能的值有: 
引用原文:

? 0..1:可选和单个,"01" 
? 1..1:有且仅有一个,"只有一个
? 0..n:可选和多个,"0到多
? 1..n:必须或者多个,"0到多""至少一个"



在这个例子中我们选择可选和单个,意味着我们的命令提供器能够应付依赖服务变成无效的情况。回来看看_run方法的代码,你可以看到它必须处理检查到null的情况。 

让我们看看如果我们运行了这个Bundle会发生什么。如果你的SimpleExporterBundle从上次一直存在,当你在命令行提示符osgi>输入"run"你将看到如下响应: 
1
Hello from SampleRunnable


太好了!这次确定了我们成功的导入了上节课我们写的Runnable服务。现在试着使用"stop"命令关闭SampleExporterBundle。当你再次输入"run"命令,你将看到: 
1
Error, no Runnable available


DS通知的意思是Runnable服务消失了,并且调用了我们的unsetRunnable方法让我们知道。 

再看看cardinality属性,如果我们改变成"1..1"你认为将会发生什么,例如从一个可选到必须的依赖切换过来?试着换下并重启动Equinox。如果SampleExporterBundle是激活的当我们输入"run"之后我们将看到和之前一样的信息"Hello from SampleRunnable"。但是,如果SampleExporter是未激活的之后我们将看到一个与之前非常不同的错误信息。事实上,我们将看到Equinox的控制台帮助信息,那是对于未被识别的命令的标准响应。这表示我们的命令提供其它自己已经通过DS注销了!直到组件有一个必须的依赖前都不会满足,DS只能被迫撤消它提供的任何服务。反过来说Equinox控制台忘掉了"run"命令。 

你可能会对policy属性感到奇怪,知道现在我仍然没有提及。这个值是"static""dynamic"中的一个,它指出是否这个组件能够应付服务的动态切换。如果不是动态的,那么每次目标服务改变了它必须为DS撤消组件并创建一个新的实例。正如你可能期望的,这相当于一个重量级的方法,最好编码你的组件来支持任何时候可能的动态切换。不幸的是默认值是static,所以你一定要记得明确的设置它为dynamic 

那么我们看过了可选单一和仅一个,但是什么是多重依赖呢?可能在Service Registry中存在多余一个的Runnable,并且如果我们只绑定它们中间的一个之后选择的那一个我们得到的是任意的。。或许我们喜欢实现一个"runall"命令来运行所有的当前注册了的Runnable 

如果我们改变cardinality"0..n"的话将会发生什么?在某一方面它将总是运作:代替只调用一次setRunnable方法,DS将为每个Registry中的Runnable实例调用setRunnable一次。问题是我们写的类将会混乱。代替设置一个单独的Runnable字段,我们需要储存Runnable到一个集合。在这里类的版本有细小的改变,即将重新复制并粘贴到你的项目的src文件夹中: 
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
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并复制以下内容: 
1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version="1.0" encoding="UTF-8"?>
<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>


最终添加这个文件到manifestService-Component头去,看起来就像这样: 
1
2
Service-Component: OSGI-INF/commandprovider1.xml,
  OSGI-INF/commandprovider2.xml


这个声明同之前的一样不可缺少,除了我们对bindunbind方法重命名并改变cardinality"0..n"。就像练习一样,试着注册一些附加的Runnable服务并检查"runall"命令执行它们全部。下次,如果我们改变cardinality"1..n"你认为会将会发生什么呢?尝试着验证你的想象。 

That's all for this lesson. Remember that the OSGi Alliance Community Event is happening next week in Munich, Germany, and I believe it's still not too late to register . See you there! 

[JAVA]OSGi入门:介绍Declarative Services[译]

欢迎来到"OSGi入门"系列的下个部分。这个部分十分的有趣,因为我们将开始尝试使用Declarative Services 

Declarative Service(或者"DS")规范是OSGi最新的一个部分,并且它来自于跨越Bundle将服务配置到一起的结果。并非这个任务困难——正如我希望我以前讲课中展示的——它需要相当数量的样板代码。它也要求你对内容的条理很谨慎,也就是说你很容易搬起石头砸自己的脚。 

早期试图解决此问题的是一个叫做Service Binder的工具。由 Humberto Cervantes Richard Hall 开发的工具。Service Binder引入了自动化的服务依赖关系管理,允许开发人员可以集中精力编写他们的服务。服务之间的配置通过声明处理,并且声明都写在XML中。 

这个Declarative Service规范是从Service Binder进化而来,并且是OSGi4.0版本的一个标准部分。那么,让我们看看它能做什么。 

正如我在课程的上一个部分叙述的,我厌倦了在命令行做任何事情:从这点上,我将使用Eclipse SDK。请记住我所介绍的事实上并不依赖Eclipse。虽然Eclipse对我们有或多或少的帮助,不过没什么黑色魔法,所以我们在这里看到的任何东西在NetBeansIntelliJ甚至在老旧的vi上都是完全可行的。 

第一件事,我们需要下载Declarative ServicesEquinox实现。假如你使用当前稳定的Eclipse版本是3.2.2,那就从这里下载。如果你使用不同的版本,你需要从顶层的Equinox下载页找到这个版本,并下载org.eclipse.equinox.ds_x.x.x_xxxxx.jar文件。把下载下来的文件放到你的Eclipse安装目录的plugins下,并重启Eclipse。事实上,如果你已经对Eclipse Plug-in开发有一点点了解,你可以把这个jar文件放入你的目标平台文件夹下。如果你不知道什么是"目标平台",不用担心,只要把这个jar放到plugins目录下就可以了。 

现在,我们将创建一个新的Bundle。做这些事情,我们使用Eclipse中的Plug-in项目向导来创建一个项目:[list=decimal] 
从主菜单,选择File->New->Project
选择Plug-in Project并单击下一步
输入项目名称:SimpleExporter
在底部,"This plug-in is targeted to run with"文字下面,选择"an OSGi framework"之后在下拉框中选择"standard"。这一步不是绝对必须的,它只是防止我们使用盒外无效的其他OSGi框架实现的特性。
单击下一步。在向导接下来的页面中,取消选中的"Generate an activator"复选框。单击结束
[/list]

我们现在有个空的Bundle项目,所以我们需要添加一些代码。为了尽可能的简单,我们将提供一个使用了在所有的Java Runtime中都有效的接口:java.lang.Runnable,使用一个顺手的小的Eclipse快捷方式,仅仅从你的浏览器复制下面的代码,然后选择SampleExporter项目中的src文件夹并点击Edit -> PasteEclipse将为你创建包和源文件。 
1
2
3
4
5
6
7
package org.example.ds;

 

public class SampleRunnable implements Runnable {
public void run() {
System.out.println("Hello from SampleRunnable");
}
}


到目前为止我们没有看到任何新东西,但是,接下来会有趣些:我们将创建一个XML文件来声明SampleRunnable为一个服务。在顶级目录下创建一个名为OSGI-INF的文件夹,并复制下面的例子到文件samplerunnable.xml中: 
1
2
3
4
5
6
7
<?xml version="1.0" encoding="UTF-8"?>
<component name="samplerunnable">
<implementation class="org.example.ds.SampleRunnable"/>
<service>
<provide interface="java.lang.Runnable"/>
</service>
</component>


这是我们将看到的DS中简单声明中的一个。它描述了一个提供一个服务到OSGI Service Registry的叫做"samplerunnable"的在java.lang.Runnable下的组件,并且这个组件通过类org.example.ds.SampleRunnable来实现。

最后一步是告诉Declarative Service运行时关于这个XML文件的存在。我们为Bundle做这个事情是通过添加一个字段到MANIFEST.MF文件。因此,在编辑器中打开manifest(试着右键点击这个项目并选择PDE工具 -> Open Manifest),跳到名为"MANIFEST.MF"的标签,这允许我们直接编辑manifest的文本内容。添加下面的一行: 
1
Service-Component: OSGI-INF/samplerunnable.xml


然后保存这个文件。正如在上个部分中说的,manifest的最后一个空行是非常重要的,但是不像以前,如果你忘记了,你将从Eclipse获得一个错误信息。

在走的更远之前,让我们运行Equniox来检查这些工作。在Run菜单中选择"Run…"。对话框打开后,在左边的树形列表中选择"Equniox OSGi Framework",然后点击New按钮。如果你是一个有经验的Eclipse用户但是没有做过OSGI开发,这个对话框可能看上去有些陌生。第一个标签是Bundles,允许我们选择哪些Bundles将包含在运行配置中,并且是否我们将在启动时便初始化。要获得最小集合,点击Deselect All并仅对以下Bundles做标记
[list=decimal] 
SampleExporter(underneath Workspace)
org.eclipse.equniox.ds(underneath Target Platform)
[/list]

这些是我们感兴趣的Bundles,但是它们有些依赖,所以单击Add Required Bundles来添加那些依赖。所以,更好的主意是选中"Validate bundles automatically prior to launching"。最后,点击Run,并稍等片刻,osgi>提示符将出现在Eclipse控制台中。OSGi框架正在运行,并且你可以使用你以前学过的相同的命令与之交互。

那么是否我们的服务被注册了?我么可以通过键入services命令来检查。在服务列表中的某个地方可能在底部你将看到如下显示: 
1
2
3
{java.lang.Runnable}={component.name=samplerunnable, component.id=1, service.id=22}
  Registered by bundle: initial@reference:file:..../SampleExporter/ [5]
  No bundles using service.


是的,它被注册了!并且注意一件重要的事情:它通过我们的Bundle注册的,不是通过Declarative Services Bundle。事实上DS代表了我们的Bundle注册了它。我们将迟些了解它如何做的,但是这节课的目的,足够说明了我们的服务的消费者不需要任何具体的工作,事实上,它们连我们使用Declarative Services也不必知道。如果那些消费者愿意那可以很好的使用Declarative Services,但是它们也能使用简单的OSGi代码。

另外要注意的是在我们的Bundle中,没有具体的OSGiJava代码。换句话说,我们写了个POJO类,这是Declarative Services的一个主要特性。

这些就够了,我很抱歉,这节课程只是一个小小的基础。下次,我们将看看如何用DS以不同的方式消费服务,这将真正的展示出DS提供的的强大和便利。


参考:
本文原文http://www.eclipsezone.com/eclipse/forums/t96740.html 

[JAVA]OSGi入门:动态服务跟踪[译]

欢迎来到EclipseZone OSGi迷你系列 

上回,我们看了如何消费一个服务,使用了一个来自Martin Fowler的启发:一个MovieLister依赖在一个MovieFinder上来搜索通过指定目标的电影。我们还看了使用OSGi服务的动态性质的应对策略,如果它不能找到MovieFinder的一个实例该做什么。 

这是另一个我们上次没有考虑的:如果存在有一个以上的有效MovieFinder怎么办?之后任何Bundle可以注册一个服务在MovieFinder接口下,并且所有的BundleRegistry的眼中都是平等的。 

我们能简单的忽略此问题,并且事实是我们的代码上次做了什么。通过在ServiceTracker上调用getService()方法,我们获得了通过ServiceRegistry选择的一个任意的MovieFinder的单独实例。有各种方式影响判断(例如一个SERVICE_RANKING属性可以在ServiceRegistration中被指定),但是正如一个消费者,我们将在这个判断上从来没有完全的控制。事实上没有太多的控制是一件好事——终究我们真正能使用所有MovieFinder的实例。这是使用在第一个位置点的接口。 

或者,它可能在某些时候觉得使用多个服务实例。例如,如果有多个MovieFinder服务有效,可能意味着有多个电影数据的来源供MovieLister使用。当处理一个电影搜索时通过使用所有的服务,我们可以转换为更宽广的网络并为用户提供更好的搜索结果。 

另一个变化,我们回到上次讨论的问题:当没有可用的MovieFinder服务存在的时候我们应该做什么?我们简单的返回null不论何时调用listByDirector()方法并且MovieFinder是不可用的。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上。在指导的上个部分,我们有个0..1的依赖。 

让我们对MovieListerImpl做些改变。替换成以下代码: 
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.*;

 

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依赖——它现在是个纯粹的POJO。然而它需要某人或某物去跟踪MovieFinder服务并通过bindFinder()方法提供它们,所以我们创建一个新的文件osgitut/movies/impl/MovieFinderTracker.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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
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(); 
} 
} 
}



这个类覆盖了上次我们谈到的ServiceTracker类,并且制定了当服务来去时ServiceTracker的举动。特别的,当一个MovieFinder被添加了的时候调用addingService()方法,并且当一个MovieFinder被移除了的时候调用removedService()方法。还有一个modifiedService()方法我们可以覆盖,但是我们在这里不需要它。 

这里的两个方法值得细看。首先,在addingService()中我们看到传递给我们的参数与其实施记得服务实现对象不如是一个ServiceReferenceServiceReference是一个可以被随意当作参数传递的轻量句柄,并且它能够被用来获取服务的属性,比如那些用ServiceRegistration提供的。关键,获取一个ServiceReference对象不会造成OSGi框架增加目标服务的使用计数。你因此可以认为这类似于Java反射API中的WeakReference类。 

第一件事我们在我们的例子中用ServiceReference来获得真实的MovieFinder服务对象。为了做这些我们再次需要BundleContext——记住,所有同OSGi框架的交互都是通过BundleContext接口完成的。很巧的是我们的父类ServiceTracker保持了BundleContext引用在一个叫做contextprotected成员字段,这样我们可以一直使用。 

下次,我们用上面定义的bindFinder()方法绑定finder到我们的MovieListerImpl,之后我们注册MovieListerImpl成为一个服务,在MovieLister接口的下面。注意,我们要很小心的仅在它没有被注册过的时候注册服务,因为在这个情景中我们想要一个独立的跟踪多个MovieFinder服务的MovieLister 

最终,我们从这个方法返回。这还有另一个点很有趣——addingService的返回类型只是Object,所以我们要返回什么呢?事实上ServiceTracker不关心,我们能返回我们喜欢的任何东西。然而,我们当我们调用modifiedService/removedService时返回给我们从addingService返回的对象。所以你看我们的removedService方法的第一行,你可以看到我们立刻转换ObjectMovieFinder那样我们就能从lister中解除绑定。然后如果跟踪到的finder变为了0,那么我们注销MovieLister服务。 

通常的,我们在addingService中做的任何事都要在removedService方法中销毁。因此我们将从addingService方法返回任何能帮助我们找到它曾做过的任何事。可能是一个HashMap中的键,或者可能是一个ServiceRegistration对象,或者(正如在这个情况中)可能是实际的服务对象。 

removedService中的最后一步,我们需要"unget"我们上次的"got"。这是非常重要的,因为Service Registry因此来减少此服务的使用计数器,那将允许当计数器置为0时释放服务。 

现在我们需要一个激活器,osgitut/movies/impl/TrackingMovieListerActivator.java 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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();
}
}



和一个manifestTrackingMovieLister.mf 
1
2
3
4
5
6
7
8
9
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)"


并且,我将脱离它让你像一个联系那样建立和部署这个BundleEquinox。这些做完,试试按以下步骤的序列来看看每件事在工作: 
引用原文:

1. 启动BasicMovieFinder和启动TrackingMovieLister。检查"MovieLister: added a finder"信息的显示; 
2. 输入services命令并检查MovieLister服务已经存在注册; 
3. 停止BasicMovieFinder并检查"MovieLister: removed a finder"消息的显示; 
4. 再次输入services命令并检查没有MovieLister服务被注册。



到这里我们完成了一项强大技术的播种。我们约束了一个服务的生命周期到另外的服务的生命周期——事实上是多个。进一步发展这个技术,我们能够让其他服务约束到MovieLister,并且仍然是另外的服务依赖在那个上面,等等。我们最终构建了一个相互依赖服务的图表,但是不同于通过一些静态IoC容器构建的Bean的图表,我们图表是可靠的,自我恢复的并能够适应变化的环境。 

另一方面,我们写的代码有些问题。MovieFinderTrackerTrackingMovieListerActivator类事实上只是一个模板形式的加载,并且如果我们要开始扩展这个系统之后我们将一遍又一遍的写着相同的代码来获得完美的约束,只是每次有些略微的修改。因此下个部分我们将看到仅仅通过一对XML标记行来替换所有的那些代码。 

所以在下个部分我们将停止用命令行工具在一个目录中建立任何东西。在这个指导开始的时候我的目标是展示OSGi是一个简单但仍然强大的框架,并且你无需一个像Eclipse那样强大的,重量级的IDE来开发OSGi Bundle。但一些东西显示的太过简单,总是怀疑IDE释放了一些黑魔法代替我们处理。我希望我展示的不存在这种情况,OSGi不需要黑魔法。另一方面,如果你已经像我一样把任何代码都放到这目录的话,你将开始渴望一个正式的开发环境。我也是。对于OSGi这不是问题,当然——任何Java项目如果像这样仅仅使用标准工具,将会很快的失控。 

然而,我恐怕你将要知道EclipseCon之后才能读到下个部分。我将跳上一个飞机在我去往Santa Clara的路上待上不到24小时。希望在那里看到你。 

感谢BJ Hargrave为统一代码所建议的更好的解决方案。 


参考




以下是BJ Hargrave的建议: 
At 11:14 AM on Mar 3, 2007, BJ Hargrave wrote: 
Re: Getting Started with OSGi: Dynamic Service Tracking 
Great example. 

In the spirit of being pedantic , I would like to observe that the MovieFinderTracker class is not thread-safe which is important in a ServiceTracker since ServiceEvents are synchronously delivered from potentially numerous threads. The issue is the registration field which is shared mutable state. Reading and writing of this field MUST be protected. 

Here is a thread-safe version: 

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



You were also missing the ungetService call in removedService (even though the text mentions the importance of this). 

I think your articles are excellent. I just want to make them more accurate!>