OSGi教程 – 02 – 使用DS来管理服务
在上一章中你已经学会了如何使用API来注册和获取服务,但是在实际情况中,直接用API来管理Bundle的整个生命周期,尤其是管理Bundle之间的依赖关系是较为繁琐的,而且这导致了Bundle代码需要引入OSGi框架包,这使得OSGi框架包需要侵入业务逻辑代码之中,这无疑是有悖于框架设计的低耦合要求。
OSGi框架提供了声明式服务DS(Declarative Services),通过DS你可以使用XML配置文件来声明服务,管理服务的各个方面,这样解决了业务逻辑代码层面对OSGi框架的耦合问题。
2.1 使用DS需要设置的基本条件
在DS中定义了另外一套模型:Service Component模型,每个提供服务的对象被称为一个服务组件(Service Component),所有的组件均受服务组件运行时SCR(Service Component Runtime)的管理。
所有相关模型的定义,均在org.osgi.service.component包中,在Equinox项目里这个包在名为org.eclipse.osgi.services的Bundle中。另外对其模型的相关操作的实现,不同的OSGi实现框架有一定差别,在Equinox中放在了名为org.eclipse.equinox.ds的Bundle中,另外该Bundle还依赖org.eclipse.equinox.util。所以你如果要使用DS,就需要在运行环境Bundles配置中勾选org.eclipse.osgi.services、org.eclipse.equinox.ds和org.eclipse.equinox.util。
首先仿照第一章的步骤来创建一个OSGi Framework配置。由于我们准备沿用第一章的配置和部分代码,所以选择从“Chapter1”运行环境复制一份作为本章运行环境。我们在配置清单中选择“Chapter1”,然后从右键菜单中选择“Duplicate”,如下图:
图2.1.1 复制配置环境
复制完成后,将会看到在“Chapter1”下面多出一份配置“Chapter1(1)”,在右上角的“Name”文本框处,可以将其名称修改为“Chapter2”,然后点击右下角的“Apply”按钮,如下图:
图2.2.2 重命名配置环境
然后再在“Chapter2”运行环境中查找、勾选org.eclipse.equinox.ds、org.eclipse.equinox.util和org.eclipse.osgi.services三个Bundle。
备注:
如果在Bundle列表中找不到对应的Bundle,可以去Eclipse的官网上下载,放到Eclipse的plugins目录中去,然后重启Eclipse再按上述步骤操作。
完成上述步骤后,你的配置环境窗体看起来大致应是这样的:
图2.2.3 创建使用DS的配置环境
如果你此时点击“Run”按钮运行对应的配置环境,并在控制台中输入ss命令,则你会看到如下图的控制台信息:
图2.2.4 勾选DS相关依赖Bundle后的控制台运行信息
2.2 声明一个引用服务的Component
在上一章节完成的NeedSericeBundle是一个需要使用其它服务端样例Bundle,本节我们对它进行改造,使用DS来将其声明为一个需要使用其它服务的Component,具体步骤如下:
1. 在MANIFEST.MF中添加一行声明:
1 |
Service-Component: OSGI-INF/components.xml |
注意冒号之后有一个空格,如果这是最后一行的话,必须在行末回车换行。
2. 在项目根目录新建一个名为OSGI-INF的文件夹,并创建一个名为components.xml的XML文件(这个文件命名和第1步中的声明吻合即可),其内容如下:
1 2 3 4 5 6 7 8 |
<?xml version="1.0" encoding="UTF-8"?> <component name="SingASong"> <implementation class="needservicebundle.SingASong" /> <reference name="Say" interface="servicebundle.ISayAnyThing" bind="setSayAnyThing" unbind="unSetSayAnyThing" cardinality="1..1" policy="dynamic" /> </component> |
上面的这段XML配置可以翻译如下:
● 我要声明一个名为SingASong的Component,其实现类为SingASong。
● 我需要引用服务ISayAnyThing,并将此引用命名为Say。
● 在引用服务成功后,会调用绑定方法setSayAnyThing将其注入到SingASong 类中。
● 在被引用的服务停止时,会执行unSetSayAnyThing方法。
● cardinality=”1..1″表示该Component对其引用服务的强制依赖,必须至少有一个被引用服务存在时Component才能启动。
● policy=”dynamic”表示当被引用服务变动时,会重新调用set和unset方法,但是Component不会重新生成。reference元素的各个属性的具体意义和选项,这里不作过多讨论。
3. 添加依赖的服务组件包
双击工作空间左边NeedServiceBundle的MANIFEST.MF文件,在打开的“Dependencies”选项卡中的 “Imported Packages”列表中添加org.osgi.service.component包,如下图:
图2.2.1 引入需要的包
4. 创建Component实现类
我们不再像第一章在Activator类的start方法中通过API获取服务,所以需要将start方法中的内容清空。
按照上面的XML文件中所配置的,新建一个类 needservicebundle.SingASong,其主要代码如下:
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 |
package needservicebundle; import org.osgi.service.component.ComponentContext; import servicebundle.ISayAnyThing; public class SingASong { ISayAnyThing sayAnyThing; public void activate(ComponentContext ctxt){ sayAnyThing.sayHello("O...O...O...Only you..."); } public void deactivate(ComponentContext ctxt){ sayAnyThing.sayHello("O...O...O个头..."); } public ISayAnyThing getSayAnyThing() { return sayAnyThing; } public void setSayAnyThing(ISayAnyThing sayAnyThing) { this.sayAnyThing = sayAnyThing; System.out.println("注入服务成功!"); } public void unSetSayAnyThing(ISayAnyThing sayAnyThing) { this.sayAnyThing = null; System.out.println("注销服务成功!"); } } |
5. 启动OSGi框架,运行结果应该如下:
图2.2.2 使用DS的运行结果
你同样可以使用ss,start和stop命令来控制所有bundle,试试在停止、重启被引用服务的Bundle(ServieBundle),看看结果和之前有何不同。
2.3 Service Component Runtime启动Component的步骤
SCR启动一个Component会按如下的步骤:
1. 加载Component的实现类
2. 创建Component的实例和上下文
3. 绑定目标服务
4. 如果有activate方法的话就调用它(在卸载的时候也是,如果有deactivate方法存在,则会在卸载之前先调用它)。
2.4 使用DS注册服务
在2.2部分我们已经将NeedServiceBundle改造成了使用DS来引用服务,现在我们参考2.2部分的内容,将ServiceBundle也改造成使用DS来注册服务。
1. 在MANIFEST.MF中添加一行声明:
1 |
Service-Component: OSGI-INF/components.xml |
注意冒号之后有一个空格,如果这是最后一行的话,必须在行末回车换行。
2. 在项目根目录新建一个名为OSGI-INF的文件夹,并创建一个名为components.xml的XML文件(这个文件命名和第1步中的声明吻合即可),其内容如下:
1 2 3 4 5 6 7 |
<?xml version="1.0" encoding="UTF-8"?> <component name="SayItThreeTimes"> <implementation class="servicebundle.SayItThreeTimes" /> <service> <provide interface="servicebundle.ISayAnyThing" /> </service> </component> |
上面的这段XML配置可以翻译如下:
● 我要声明一个名为SayItThreeTimes的Component,其实现类为SayItThreeTimes。
● 我需要发布一个服务接口ISayAnyThing。
3. 将Activator的start方法中使用API注册服务的代码去掉,重新运行,结果应该跟你在图2.2.2部分看到的一样。
2.5 同时引用并发布服务以及定义多个Component
如果一个component引用了另一个服务,但自身又发布成为一个服务,则需要整合前面讲到的引用服务及注册服务的配置信息,样例配置如下:
1 2 3 4 5 6 7 8 9 10 |
<?xml version="1.0" encoding="UTF-8"?> <component name="SingASong"> <implementation class="needservicebundle.SingASong" /> <reference name="Say" interface="servicebundle.ISayAnyThing" bind="setSayAnyThing" unbind="unSetSayAnyThing" cardinality="1..1" policy="dynamic" /> <service> <provide interface="needservicebundle.SingASong" /> </service> </component> |
如果需要将一个对象声明为多个服务,你可以在上面的配置文件中的service元素下写多个provide子元素,不信你可以试试看喽:-)
有时候你需要通过DS来将一个Bundle内的多个类(或者同一个类)的若干个对象注册为多个服务,这时你需要写多个Component对应的配置文件,并修改MANIFEST.MF文件中Service-Component的值,在多个xml文件之间使用英文逗号(,)分隔,如下图:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: NeedServiceBundle Bundle-SymbolicName: NeedServiceBundle Bundle-Version: 1.0.0.qualifier Bundle-Activator: needservicebundle.Activator Bundle-RequiredExecutionEnvironment: JavaSE-1.8 Import-Package: org.osgi.framework;version="1.3.0", org.osgi.service.component;version="1.2.2", servicebundle Bundle-ActivationPolicy: lazy Service-Component: OSGI-INF/components.xml,OSGI-INF/components2.xml Export-Package: needservicebundle |
备注:
如果发布多个component的name属性相同,则在运行时在控制台上会得到类似如下图的错误提示:
DS服务根据不同的配置文件所生成的Component是独立的。也就是说,即使你使用同一个类,如果你写两个配置文件,那么将生成两个对象和与之相关的Component。
备注:
初学者经常会犯这样的错误:以为不同的配置文件对应同一个类的Component关联的是同一个对象,这样进一步以为在不同的配置文件里注入的依赖服务能在任何一个对象里使用。
例如:在一个Component里引用服务A,在另一个Component里引用服务B,程序员以为是两个Component的对象是同一个,结果在运行时发现A或B的引用是空指针——可想而知,你会在执行时看到控制台上打印出空指针异常的信息。因为在两个不同的对象中分别注入了A或B,无论对于哪一个对象来说,AB两者必有其一为空指针。
我经常看到有些初学者将相关的所依赖服务的引用声明为静态全局变量,以为找到了解决所谓的“空指针”问题的正解——这相当于削足适履。
如果你遇到类似问题,请检查一下你的配置是否正确。


关于添加插件,“2.1 使用DS需要设置的基本条件”中,我没有org.eclipse.equinox.ds、org.eclipse.equinox.util这两个Bundle,从网上下载的这两个jar包放入Plugins文件夹下,在Eclipse中util这个Bundle可以找到,ds这个死活找不到,我下载了.200/.300/.400三个版本都不行,期待回复~