星期三, 六月 25, 2008

[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!> 

没有评论: