上次我们首次接触了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来建立。
那么我们需要添加下面一行到Bundle的manifest中:
1 | Service-Component: OSGI-INF/commandprovider1.xml |
这个定义有我们之前看过的同样的两个元素,implementation和service节点。implementation节点提供实现组件的类的名称,service节点告诉DS像一个服务那样注册这个组件。这种情况下我们注册到CommandProvider接口下,这个是我们如何让Equinox控制台获知关于我们的命令提供器的存在。
下个节点叫做reference,是我们以前没有见过的——它对DS声明了我们的组件已经依赖在一个服务上。name属性只是一个依赖的名称的随意的字符串(我们仍然不需要担心这个是用来干什么的)并且interface属性指定我们依赖的接口的名称。bind属性是当一个服务有效时将调用实现的类中的一个方法名称,换句话说,当一个Runnable服务用Service Registry注册了,DS将获取一个此新服务的引用并提供使用这个指定的方法提供给我们的组件。同样的unbind属性是当我们使用的一个服务变成无效的时候通过DS调用它。
Cardinality属性显示了DS真正的强大。这个属性控制是否这个依赖是可选的或是必需的,并且是否它是单独的或多个的。可能的值有:
引用原文:
? 0..1:可选和单个,"0或1"
? 1..1:有且仅有一个,"只有一个"
? 0..n:可选和多个,"0到多"
? 1..n:必须或者多个,"0到多"或"至少一个"
在这个例子中我们选择可选和单个,意味着我们的命令提供器能够应付依赖服务变成无效的情况。回来看看_run方法的代码,你可以看到它必须处理检查到null的情况。
让我们看看如果我们运行了这个Bundle会发生什么。如果你的SimpleExporterBundle从上次一直存在,当你在命令行提示符osgi>输入"run"你将看到如下响应:
1 | Hello from SampleRunnable |
太好了!这次确定了我们成功的导入了上节课我们写的Runnable服务。现在试着使用"stop"命令关闭SampleExporter的Bundle。当你再次输入"run"命令,你将看到:
1 | Error, no Runnable available |
DS通知的意思是Runnable服务消失了,并且调用了我们的unsetRunnable方法让我们知道。
再看看cardinality属性,如果我们改变成"1..1"你认为将会发生什么,例如从一个可选到必须的依赖切换过来?试着换下并重启动Equinox。如果SampleExporter的Bundle是激活的当我们输入"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> |
最终添加这个文件到manifest的Service-Component头去,看起来就像这样:
1 2 | Service-Component: OSGI-INF/commandprovider1.xml, OSGI-INF/commandprovider2.xml |
这个声明同之前的一样不可缺少,除了我们对bind和unbind方法重命名并改变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!
没有评论:
发表评论