OSGi教程 – 01 – OSGi基础

本章用于初次接触OSGi的朋友入门,若你对OSGi规范和编程有一定了解,可以跳过。

在本章中将学习如何在OSGi环境中注册和引用服务,我们将会逐步指导你编写出两个OSGi Bundle:ServiceBundle和NeedServiceBundle,前者会向OSGi环境中注册服务,而后者会从OSGi环境中获取对应的服务。下图为对应的示意图。

2016070702

图1.0.1 注册与获取服务的两个Bundle

1.1 注册和导出服务

1.1.1 创建 Bundle

环境准备:JDK1.8 以上,Eclipse4.5 以上;根据版本不同,本文中所述界面细节可能会略有不同,我在样例中使用 JDK1.8.0_73、Eclipse4.5.2。

备注:Eclipse的下载页面位于:http://www.eclipse.org/downloads/。       由于编写此文的时间在2016年上半年,所以选择的是最新的Eclipse 4.5.2,我们使用的是Eclipse for RCP and RAP Developers版本,在下载页面的中部能看到对应的下载链接。请根据你的操作系统和Java版本选择下载32位或64位版。

另外请注意,如果你的编码有相关的规范和要求,请自行根据规范修订本书配套的样例(例如:样例代码使用 GBK 编码,而你或许需要使用 UTF-8 编码)。

从Eclipse的菜单File->New->Other,选择Plug-in Development->Plug-in Project,选择Next,进入如下图界面,输入工程名称,例如本例中的ServiceBundle,在Target Platform中选择“an OSGi frameWork”,点击Next继续。

备注:若开发 Eclipse 插件则在选择Target Platform时使用 “Eclipse version” 选项。

插件开发资料很多,推荐阅读由 Eric ClayBerg 和 Dan Rubel 合著的《Eclipse plugins》,这本国内有中文版;以及由 Lars Vogel著的《Eclipse 4 RCP: The complete guide to Eclipse application development》,这本出版时间较新,不过国内貌似暂时还没有中文版。


2016070703

图 1.1.1.1 命名工程和选择平台

进入如下图1.1.1.2界面后,你可以输入Bundle的ID、版本、名称,提供商信息等,一般保留默认值即可。

备注:下面的 Plug-in Options 区域有提示是否要生成 Activator 类,在该类存在的情况下,可以在 Bundle 的启动和关闭时调用该类的 start 和 stop 方法,与其环境上下文进行互动,但此类并不是必须的。

2016070704

图1.1.1.2 新建项目

点击Next按钮进入下一步,在如下图界面中你可以选择Bundle模板。这里我们不选择任何模板,直接选择Finish按钮。读者可自行试验各个模板有何区别,分析其代码来佐证所学的 OSGi 基础知识。

2016070705

图 1.1.1.3 选择模板

如果你是第一次进行Bundle开发,将会提示你是否进入插件开发视图,当然选择Yes。

此时你的 Eclipse 工作台看起来大致是这样子的:

2016070706

图1.1.1.4 Bundle开发初始界面

至此,一个最简单的Bundle 已经完成了,不过它什么也干不了,我们可以在其Activator 类的start和stop方法中各加一行经典打印语句,让它发出第一声宣言式的呐喊。如下图:

2016070707

图 1.1.1.5 工作台鸟瞰

双击MANIFEST.MF文件,Bundle编辑器将回到初始界面,展开此Bundle的基本信息。如下图:

2016070708

图 1.1.1.6 Bundle 信息鸟瞰

点击右下角的Testing区域里的小三角或者虫子链接,将会替你生成默认的 OSGi 运行环境并运行之,当然,我刚刚所说的的 ServiceBundle 也在其中。片刻之后,你应该会看到如下图的界面:你看到控制台上的话了吧,那就对了。

备注:如果没有看到打招呼的语句,而是出现了各种错误提示,那么细心检查或重新尝试运行一下,可能在启动时已经输出了只是你没有注意到,被后面的一些错误信息给覆盖了(如果有了打招呼语句,后面跟着很多错误信息是正常的,这并不是我们的Bundle有问题而是由于在默认情况下其它Bundle的配置存在缺陷,而这些Bundle并非是目前所必需的)。

如果确定没有输出则请检查上面的步骤没问题,还是不行的话,那就继续本文往下看,或许在后文能得到启发。

2016070709

1.1.1.7 Bundle 运行结果

OK,一个新的世界之门已经向你开启了!

1.1.2 初步解析Bundle运行环境

在开始之前,先谈谈三个Bundle控制台命令 ss、start和stop的作用:

1:ss:用于显示当前运行环境中安装的Bundle及其运行状态

2:start:启动指定的Bundle

3:stop:停止指定的Bundle

在下图的控制台中,会有”osgi>”的提示符,输入 ss 后,你将看到类似如下图的界面:

2016070710

图 1.1.2.1 ss 命令运行效果

显然,这个列表显示了各个Bundle的名称、运行时id和状态。由于到现在为止,这个运行时环境是Eclipse按默认配置自动生成的,所以你看到的这个列表可能很长,Eclipse 把所有它所能启动的bundle全部启动了。

备注:可以使用控制台右上角工具栏中的“Clear Console”按钮先清除控制台上的显示信息,然后再继续后面的步骤。如下图:

2016070710_2

图1.1.2.2 清除控制台显示信息按钮

你输入“stop XXX”,回车(比如我这里输入了“stop 1”),看到了什么?从列表中找到我们的 ServiceBundle,比如在我的机器上它的 id 号为 1,现在它的state 为 ACTIVE,这就是之前你能看到“你好,OSGi 的世界”的问候语的原因。下面我们使用 stop 命令来停止它(如果在你的机器上它不是 ACTIVE 的,而是 RESOLVED 或者其他的什么状态,那就不用再stop了)。

start 命令的使用也是同样的方式,去试试吧。

2016070711

图 1.1.2.3 stop 和 start 命令的运行效果

现在你应该知道 Activator 类的两个方法什么时候运行了。

那么 ss 命令输出的列表内容从何而来呢?谜底在这里——如下图,在工具栏上点击虫子或者绿色箭头按钮,均能看到名为“OSGi Framework”的运行环境列表,点击它就能开始调试或运行对应的 OSGi 环境。

2016070712

图 1.1.2.4 OSGi Framework

如果想要对环境进行配置,点击下面的“Debug Configurations”或“Run Configurations”菜单,如下图:

2016070713

图 1.1.2.5 设置运行平台环境

默认情况下,所有的Bundle都是被勾选上的,我们首先选择“Deselect All”,然后仅勾选 ServiceBundle,然后点击“Add Required Bundles”,会自动将 ServiceBundle 所依赖的其他Bundle自动选上。

备注:“Add Required Bundles”功能并不是总是最好,也并不总是有效,在你有经验之后,可以手工去勾选自己所需要的 Bundle

其中必须勾选的有6个:你可以观察一下到底自动加上了哪些Bundle。

● ServiceBundle本身

● eclipse.osgi_xxxx.jar

● eclipse.equinox.console_xxxx.jar

● apache.felix.gogo.command_xxxx.jar

● apache.felix.gogo.runtime_xxxx.jar

● apache.felix.gogo.shell_xxxx.jar

备注:除了ServiceBundle本身之外,其余几个Bundle的作用在于提供人机交互的控制台,这与之前版本(3.X)的Eclipse已经有所不同,具体说明请参考:Eclipse帮助中心>Platform Plug-in Developer Guide > Programmer’s Guide > Runtime overview> Console Shell,其中对使用SHH协议来telnet 远程连接控制台以及使用JAAS来进行用户授权等进行了说明。目前它的网址位于:

http://help.eclipse.org/mars/index.jsp?topic=%2Forg.eclipse.platform.doc.isv%2Fguide%2Fconsole_shell.htm

在勾选完毕后,点击“Validate Bundles”,将会校验所需依赖Bundle是否都已经勾选了,如果显示“No problems were detected”,那么一般来说,对平台所需Bundle的设置就成功了。

备注:细心的朋友应该可以发现,这个对话框上有Arguments、Settings、Tracing、Environment、Common 几个选项卡,它们用来配置运行时环境的方方面面,如配置文件所在地,虚拟机参数等,这里不作进一步讨论,随着你的经验增多,将逐渐接触到这些参数。其中Settings选项卡中的“Clear the configuration area before launching”建议勾选,以便在每次运行之前都清空配置信息,避免因多次调试导致配置信息混乱,进而在控制台上输出一些莫名其妙的错误提示。如下图:

2016070714

图1.1.2.6 清空配置信息选项

另外,在左边的 OSGi Framework 选项下看到的 OSGi Framework 子项,点击该项即可打开调试或运行环境配置界面,其名称是可以改的,只是默认名称为“OSGi Framework”而已,比如我们可以将其右上部的 Name 处改为“Chapter1”,然后点击“Apply”按钮,你将看到左边的 OSGi Framework 选项下子项的名称也会变成了“Chapter1”,如下图:

2016070715

图 1.1.2.7 重命名后的 OSGi 调试和运行环境

你可能会好奇,这个配置界面真正对应的配置文件存放在哪里呢?答案是在工作空间所在目录下的“.metadata\.plugins\org.eclipse.debug.core\.launches”文件夹下,你会看到对应的扩展名为 launch 的文件,该文件即存储了相关的配置信息。你可以使用文本编辑工具打开它看看其中的内容,关于其中各个参数的解释暂时不展开,以后有机会进行阐述。

备注:可以想象的是,在一个复杂的环境下,你可能需要勾选几十个乃至更多数目的 Bundle,如果将此文件备份出来,那么在再次配置环境时就可以快速恢复备份时的配置情况了。另外在搭建发布环境时,这个文件的内容也能给你若干提示和帮助。

2016070716
图 1.1.2.8 环境配置文件所在的路径

这时在图 1.1.2.C 中的 OSGi Framework 选项就会变成下图中重命名后的选项“Chapter1”:

2016070717

图 1.1.2.9 重命名后的 OSGi 调试和运行环境菜单项

这时我们来再次运行“Chapter1”选项,按照之前所说的输入命令,如下图,是不是清爽了很多?

2016070718

图1.1.2.10配置后的运行界面

1.1.3 声明服务

之前我们创建的Bundle除了能打个招呼,还什么都没有做,现在开始让它进化吧——假设要让这个 ServiceBundle 能够复述我们的所说的话。

在开始之前,有必要帮没有接触过 OSGi 和 Bundle 的朋友建立一个具体的概念:

在通常的 Java 编程中,我们习惯于说某一个类提供一个方法或一个属性,类(Class)和对象(Object)这两个术语在各类Java变成书籍中随处可见,现在我们要换个角度——每个方法或者属性的目的都在于为用户提供一种服务,服务(Service)这个术语,将会贯穿 OSGi 编程的始终。正如《Thinking in Java》里所说“Everything is an Object”,现在让我们说“Every Bundle provids some Service”。

你可以将 OSGi 运行时环境看作一个具有管理模块的大插座,上面按设计者的意愿打上了不同的插孔,而且已经有人在上面按照插孔的规范接入了各种服务,比如日志管理、Eclipse 编程 IDE、宝马汽车的管理系统……有的人甚至会在自己接入之后,开始做黄牛党——自己搞个转接插座让别人接入……你要做的事情就是学着自己弄个服务插头接上去,然后插座管理器会帮你宣传“我这里有人新插上了一个很酷的服务!”

我用插座和插头来打比方是因为:一般来说,提供服务是以接口的方式来进行的,因为同样的服务可能有很多不同的服务提供者、或者同一个服务提供者也可能会提供同一服务的不同版本,相信你在将来会遇到这些问题及其解决方案——这些在Bundle的MANIFEST.MF 里都能有所体现。

ServiceBundle 要可以替人说任何话,首先就要声明一个接口,比如我声明了接口ISayAnyThing,它有一个方法:

在很多情况下,提供服务实现的Bundle和声明服务接口的Bundle不是同一个,换句话讲,很多情况下我们是要做黄牛党,提供服务插头让别人接入真正的服务实现,这个和Java中interface的语义是一致的。不过在这里简单起见,我们写一个实际提供该服务的类,就放在这个Bundle里面。

备注:本例中使用接口声明服务,并不代表不能使用类来声明服务,你可以在学完这一章之后试验一下直接用类来声明服务,这也是可以的,但是我并不推荐你这样做,原因暂时不在这里展开。:P

public ServiceRegistration registerService( String clazz, Object service, Dictionary properties )我们新建一个类 SayItThreeTimes,实现了 ISayAnyThing 接口,它提供的服务很简单,就是把原话念三遍,这个大家都知道怎么用Java去表达了。然后我们要做的就是要把这个普通的Java接口声明为服务,并且告诉管理器(即服务注册表)是由谁来实现的。这里我们要用到BundleContext类的方法:

这两个API都是用来声明服务的,第一个API表示只声明一个服务,后者表示要声明若干个服务(参考第 7 章) ;第一个参数表示要声明的什么服务;第二个参数表示用哪个对象实现来服务;第三个参数表示这个服务需要哪些参数。

BundleContext在Activator类的Start和Stop方法里均能得到,我们在Start中写这样的语句:

这样我们就将SayItThreeTimes声明在服务IsayAnyThing之下了。还有最后一步,接口ISayAnyThing现在是封装在Bundle之内的,我们必须将其暴露出去,不然申请获取服务的人并不知道到底现在插座上插着的服务是怎样的。

打开MANIFEST.MF,Eclipse将会自动为你打开编辑器,选择runtime选项卡,在“Exported Packages”页面点击“add”按钮,将servicebunlde添加进来,这样表示将该包下的所有资源全部暴露出去,使得其他Bundle可以访问。如图

2016070719

图 1.1.3.1 导出包

在本例中我们将服务的声明和实现都放到同一个包之中,而我之前提到过在多数情况下,服务声明和服务实现是在不同的Bundle里的,那么服务实现Bundle就依赖服务声明Bundle(需要实现对应的接口),声明服务的Bundle需要将自己声明的接口导出供其实现者使用。导出以包为单位,凡是在该包之下的所有资源,如xml配置、图片等,都会被暴露出去。

在继续下一节之前,我们回顾一下声明和提供服务的基本方法:首先定义服务接口,然后定义接口的实现,并使用对应的 API 将其注册到OSGi环境中去,最后别忘记导出其它Bundle要用到的资源。

现在你的工作空间看起来大概是这样子的:

2016070720

图 1.1.3.2 提供服务的Bundle结构

点击MANIFEST.MF进入编辑器后,选择不同的选项卡,其中有几个是图形界面,有几个是真正的编码,你可以观察与图形界面对应的真正语句是如何写的,了解其本质是什么,相关的代码可以在随书的源码包中找到。

备注:如果你直接对配置文件的内容进行修改,而不是通过图形编辑器,请留意其格式,因为这些配置文件对空格、回车换行等是敏感的,你可能会不小心造成错误,但是肉眼却很难发现——不过随着 Eclipse版本的升级,对这方面的帮助已经越来越好了。

到此为止,我们已经成功声明了一个名为 ISayAnyThing 的服务,同时提供了一个啰嗦三遍的实现,为了验证其正确性,在下一节中,我们将把他命名为唐僧,让他唱出经典的“Only you”。

1.2 引用服务

引用简明的服务是件愉快的事,导入所需要的包,然后使用API去运行时环境里获取已有的服务就好。不过随着时间的推移,代码越来越多,结构越来越复杂,所引用的Bundle可能越来越多,引用服务也可能会变成一件让人痛苦的事,所以从现在开始,如果你提供某种服务,请为你所期待的使用者提供便利,尽量提供能让你Bundle正确运行起来的信息,不要让他们迷失在Bundle的森林中。

参考 1.1.1 部分所说的方法再创建一个NeedServiceBundle,建好之后,点击MANIFEST.MF 切换到Dependencies选项卡,在“Imported Packages”页面点击“Add”按钮,选择servicebundle添加进来,如果你在这里看不到 servicebundle,请检查在上一节最后所讲的将资源导出你是否做好了。现在你的工作空间看起来大概是这样的:

2016070721

图 1.2.1 创建引用服务的Bundle

要引用服务,我们需要使用 BundleContext 类的如下两个方法:

第一个 API 的参数 clazz 表示要获取的服务的类名, 第二个 API 的参数 reference 和第一个 API 所返回的服务引用是同一种类型。

可以猜到我将在 Activator 的 start 方法里这样写:

确认在选择“Open Debug Dialog”菜单之后的插件选择框中,我们所写的Bundle都是被选中的,然后点击 Debug 按钮吧!

2016070722

图 1.2.2 确认选择Bundle后调试

你将看到这样的运行结果:

2016070723

图 1.2.3 服务提供者和使用者的运行界面

现在你可以试着使用start和stop命令来分别操作我们所写的两个 Bundle,先将两个Bundle都停止,然后看看在ServiceBundle停止的情况下,尝试启动 NeedServiceBundle 会发生什么。

1.3 搭建发布环境

从开始到现在,我们都是在 Eclipse 的开发环境下进行调试和运行,在实际情况下当然是要脱离 Eclipse 开发环境来运行的,下面来简要介绍下发布环境的搭建过程。

在开始之前,告诉大家一个“公开的秘密”——Eclipse 本身也是使用 OSGi 作为其基础架构的,也就是说,Eclipse 本身即是一个完整而复杂的发布环境样例,当然,Eclipse本身之中也包含了运行 OSGi 环境的最小集合。你可以想象,Eclipse 使用的某些参数设置等细节,可以模仿着放到 OSGi 发布环境的搭建中来……

1.3.1 最简单的 OSGi 发布运行环境

首先新建一个目录,比如我这里将其命名为“Chapter1”,然后将你的 Eclipse安装目录下的plugins文件夹下的如下几个jar包复制过来:

● eclipse.osgi_XXXX.jar

● eclipse.equinox.console_XXXX.jar

● apache.felix.gogo.command_XXXX.jar

● apache.felix.gogo.runtime_XXXX.jar

● apache.felix.gogo.shell_XXXX.jar

然后新建一个批处理文件run.bat(在非DOS/Windows操作系统上则使用对应平台的批处理方式,例如在Unix下你可能需要新建一个run.sh),文件内容为

备注:上面的绿字命令行必须放在一行中,不可进行换行,上面的两行是因为文档编辑器受页面宽度所限而进行的自动换行,有读者直接复制粘贴过去使用,在批处理文件中就会变成两行,这样会导致无法正常运行。

osgi.noShutdown=true然后再在Chapter1下建立一个configuration文件夹,在该文件夹下新建一个名为config.ini 的文本文件,其内容为:

建好之后文件夹看起来应该是这样子的(和 Eclipse 安装目录下的部分结构是不是很类似?):

2016070724

 

图1.3.1.1 文件目录

双击run.bat运行,如果你的 java 运行环境是完整(配置好相关的CLASSPATH,JAVA_HOME 等环境变量),则运行界面看起来是大概是这样的(你可以在控制台上输入 OSGi 命令来控制各个Bundle):

2016070725

图 1.3.1.2 最简单的 OSGi 环境运行情况

1.3.2 发布自定义Bundle

在发布之前,请检查你的Bundle的编码方式,本例中使用了默认的 GBK 编码,则一般无需进行特别设定,但如果你设定了其他编码方式,如 UTF-8,则需在 build.properties 文件中,加上如下一行设置:

否则在发布之后,如果你的输出信息为中文将出现乱码。

另外要注意,build.properties 的最后一行一般是一个英文半角小数点“.”,注意不要弄丢了。

发布所编写的Bundle有两种方式:

第一种使用 PDE(The Plug-in Development Environment)的发布工具:从 overview选项卡上的 Exporting 部分的“Export Wizard”链接可以进入,

2016070726

图 1.3.2.1 导出向导选项

然后从弹出的窗口选择待发布的Bundle(可以多选)将要存放的位置,如下图:

 

2016070727

图 1.3.2.2 选择待发布的Bundle和目的路径

上面的这个界面的入口除了上面说的overview选项卡上的 Exporting 部分的“Export Wizard”链接之外,还可以从 Eclipse 菜单的“File->Export”进入,然后从弹出的窗口选择“Plugin-in Development”下的“Deployable plug-ins and fragments”,如下图:

2016070728

图 1.3.2.3 从菜单进入导出向导

在选择待发布的Bundle和目的路径后,点击“Finish”按钮,如果你的 Bundle 代码没有问题,则会在指定路径下生成一个plugins目录,下面就存放有对应 jar包,例如刚刚发布的ServiceBundle和NeedServiceBundle的jar包。

绝大多数Bunlde都可以使用PDE的这个发布功能来将源码发布为jar 包,但是在少数情况下,由于PDE发布工具本身的一些问题(或许高版本的 Eclipse 会更完美一些),可能会导致此功能无法使用(例如循环依赖等),这时我们就可以使用下面将要描述的第二种方式来进行发布。

第二种:使用 Java 开发环境的打包工具,这个打包方式更为常见,即使你以前没有接触过插件开发,也多半使用过这种打包方式。

从 eclipse 菜单的“File->Export”进入,然后从弹出的窗口选择“Java”下的“JAR File”选项,如下图:

2016070729

图1.3.2.4 从File菜案进入发布功能界面

2016070730

图 1.3.2.5 选择导出 JAR 文件

然后在弹出的窗口中选择要打包的项目,以及项目之下要打包的部分,一般只需选择src 和 META-INF 文件夹即可,然后在下面的“JAR file”输入框处选择要存放的位置,如下图:

2016070731

图 1.3.2.6 选择待导出内容和导出目的位置

然后选择打包选项,一般默认即可,点击“Next”按钮,如下图:

2016070732

图 1.3.2.7 选择导出选项

最后需要指定MANIFEST文件,选择你所要打包的Bundle的 MANIFEST.MF 文件即可,点击“Finish”按钮,如下图:

2016070733

图 1.3.2.8 选择 MANIFEST.MF 文件

如果打包完毕,在你所指定的位置(参考 图 1.3.2.6 选择导出内容和导出目的位置),将看到对应的jar包。

至此为止,你已经成功地使用两种不同的方式来发布你自己所写的Bundle 了,下面我们来将发布完毕的Bundle安装到发布环境中去。

1.3.3 运行自定义 Bundle

到此为止,基础材料已经准备完毕,包括一个最简单的 OSGi 发布运行环境、两个自定义的Bundle发布包。

首先我们在OSGi运行环境的目录下新建一个“plugins”文件夹,将NeedServiceBundle和 ServiceBundle的jar包复制到该文件夹下,然后将configuration 文件夹下的 config.ini 文件改成如下内容:

和 1.3.1 部分的config.ini文件相比,这里增加了最后两排内容(版本号请注意使用你的实际版本号和时间戳替代),osgi.bundles的内容可以使用相对路径或绝对路径,一般我们使用样例中的相对路径方式——相对 org.eclipse.osgi_xxxx.jar 文件所在的路径,可以使用“..”来表示上级目录。如果需要发布多个 Bundle,可以使用“,”分隔,如需换行可以在行末使用“\”——通过上述的设置之后,运行环境目录结构应该类似下图这样的:

2016070734

图 1.3.3.1 安装自定义Bundle后的发布环境

双击run.bat运行后,在控制台使用相关命令,可以看到如下运行界面:

2016070735

图 1.3.3.2 运行自定义 Bundle 界面

这个界面和“图 1.2.3 服务提供者和使用者的运行界面”是一致的,只是图 1.2.3 是在 Eclipse 开发环境下运行,而现在已经成功脱离开发环境独立运行了!

备注:如果你的运行界面与上图不同,并且ServiceBundle和NeedServiceBundle的并不是ACTIVE状态,则需要手工使用start命令来分别启动 ServiceBundle和NeedServiceBundle。

如果希望强制指定Bundle自动启动,则可以在 config.ini文件中将相关的Bundle设置后加上自动启动参数,形如:plugins/ServiceBundle_1.0.0.xxxx.jar@4:start, plugins/NeedServiceBundle_1.0.0.xxxx.jar@4:start,这里数字4表示启动优先级,数字越小越先启动;start 表示自动启动。

 另外,你可以像使用Eclipse一样使用links文件夹来指定其他位置的 Bundle,也可以使用jre文件夹来存放java运行环境……关于这些知识点的详细信息,你可以尝试自行查阅资料,这里就暂不罗列啦:)

 源码下载

打赏一下
支付宝
微信
除非注明,博客文章均为原创,转载请标明文章地址
本文地址: http://www.javafxchina.net/blog/2016/07/osgi-01-fundamental/
百度已收录