火龙果软件使用Equinox开发OSGi应用程序.docx
《火龙果软件使用Equinox开发OSGi应用程序.docx》由会员分享,可在线阅读,更多相关《火龙果软件使用Equinox开发OSGi应用程序.docx(49页珍藏版)》请在冰豆网上搜索。
火龙果软件使用Equinox开发OSGi应用程序
关于本教程
OSGi是目前动态模块系统的事实上的工业标准,虽然一开始只是作为嵌入式设备和家庭网关的框架来使用,但是实际上它适用于任何需要模块化、面向服务、面向组件的应用程序。
而Equinox则是的Eclipse所使用的OSGi框架,是Eclipse强大的插件体系的基础,Eclipse的稳定可靠性也为该框架带来了声誉。
本教程就将演示如何在Eclipse环境下利用Equinox框架进行OSGi应用开发。
首先解释了实现上述应用程序所必需了解的基本概念和基础知识,并结合示例代码演示OSGi开发的一些重要技术,最后探讨了基于OSGi应用程序一般所采用的架构,以及如何将EquinoxOSGi应用程序脱离Eclipse而部署为一个标准的Java应用程序。
目标
在本教程中,您将学习:
∙OSGi及框架简介
∙编写第一个OSGi应用程序
∙重要的理论知识
∙开发一个真实的OSGi应用程序
∙探讨OSGi应用架构
∙部署OSGi应用程序
先决条件
本教程假设读者熟悉基本Java语言以及Eclipse开发环境的使用。
系统需求
本教程假设您有一个可以工作的Eclipse3.x环境。
如果还没有,请在Eclipse网站上找到相关下载的链接,以帮助您在自己的系统上操作示例步骤以及运行示例代码。
OSGi及框架简介
OSGi简介
OSGi是目前动态模块系统的事实上的工业标准,虽然一开始只是作为嵌入式设备和家庭网关的框架来使用,但是实际上它适用于任何需要模块化、面向服务、面向组件的应用程序。
目前OSGi规范已经发展到第四版(R4),由OSGi联合组织(OSGiAlliance)负责进行维护管理,相关的规范资料也可以从该网站获得。
(参考资料)
OSGi框架
开发基于OSGi的应用程序离不开实现了OSGi标准的框架,就好比是基于J2EE的开发离不开应用服务器一样。
目前比较流行的基于OSGiR4标准实现的OSGi框架有三个:
1.Equinox:
这是大名鼎鼎的Eclipse所使用的OSGi框架,Eclipse强大的插件体系就是构建在OSGibundles的基础之上,Eclipse的稳定可靠性为该框架带来了声誉,而且由于有IBM公司的强力支持,其后续的开发和文档资料也有了一定的保障。
一般情况下,我们推荐您使用该框架进行OSGi开发。
本教程的后续部分也将演示如何使用Equinox框架来进行OSGi应用程序的开发。
2.MakewaveKnopflerfish:
这是另外一个比较知名的OSGi框架,目前的版本已经支持R4规范,其特点在于为应用程序的开发提供了大量的bundle。
3.ApacheFlex:
由Apache基金组织开发的面向社区的OSGi框架实现,提供了标准的服务和一些有趣的和OSGi相关的服务实现。
HelloWorld!
编写第一个OSGi应用程序
准备工作
1.从附属资料中下载Eclipse3.x版本,Eclipse3.2+版本已经全面支持OSGiR4规范。
目前最佳实践是下载Eclipse3.3.2版本。
(下载请见:
参考资料)
2.将Eclipse解压缩到d:
\work\seclipse目录,开始我们的OSGi之旅。
HelloWorld
一般情况下,学习一门新的技术,程序员都习惯于首先开发一个helloworld应用程序,这似乎也是一种“工业标准”。
好的,让我们开始吧,开发一个简单的OSGi应用程序并不难,步骤如下:
1.建立一个plug-in工程,File>New>Project,选择Plug-indevelopment>Plug-inProject
图1.新建plug-in工程
2.在建立工程的第一个向导,填入工程的名称:
osgi.test.helloworld,使用缺省的工程路径。
注意目标平台的选择,由于我们的项目是一个通用的OSGibundle,所以选择equinox。
图2.填入工程名及选择目标平台
3.在下一个向导界面中,填入需要的一些插件信息(注意Eclipse中的插件概念基本类似于OSGi中的bundle的概念),这里需要填入的是OSGi的provider(供应商)和classpath。
如果没有特别的设计,一般可以忽略这两个字段。
最后是关于activator的部分,如果不是一个fragmentbundle则需要填入,除非您的bundle自己实现框架的事件监听,这个似乎也没有必要。
因此,建议使用缺省的设置,如图3:
图3.使用缺省设置
Activator:
这是bundle启动时首先调用的程序入口,相当于Java模块中的main函数。
不同的是,main需要通过命令行调用,而OSGi的Activator是被动的接受OSGi框架的调用,收到消息后才开始启动。
最佳实践:
不要在Activator中写太多的启动代码,否则会影响bundle启动速度,相关的服务启动可以放到服务的监听器中。
4.最后一步,不使用任何的模板,所以勾掉缺省的选项,点击完成,如图4:
图4.勾掉缺省的选项
5.完成,基本的插件视图如图5,Eclipse会在工程名下建立相同路径的JavaPackage,其中包含了Activator类,插件的配置信息也都放在MANIFEST.MF文件中,将来我们相当多的工作都是在其中完成。
图5.基本的插件视图
6.编辑Activator.java,输入helloworld语句,代码如下:
清单1.编辑Activator.java
packageosgi.test.helloworld;
importorg.osgi.framework.BundleActivator;
importorg.osgi.framework.BundleContext;
publicclassActivatorimplementsBundleActivator{
/*
*(non-Javadoc)
*@seeorg.osgi.framework.BundleActivator
*#start(org.osgi.framework.BundleContext)
*/
publicvoidstart(BundleContextcontext)throwsException{
System.out.println("helloworld");
}
/*
*(non-Javadoc)
*@seeorg.osgi.framework.BundleActivator
*#stop(org.osgi.framework.BundleContext)
*/
publicvoidstop(BundleContextcontext)throwsException{
}
}
7.
8.我们可以看到每个Activator实际都是实现了BundleActivator接口,此接口使Activator能够接受框架的调用。
在框架启动后,启动每个bundle的时候都会调用每个bundle的Activator。
9.注意:
bundle的Activator必须含有无参数构造函数,这样框架才能使用Class.newInstance()方式反射构造bundle的Activator实例。
10.这里我们在start方法中填入了我们希望输出的helloworld字符串。
那么,怎么才能启动这个bundle呢?
11.执行:
选择Run>OpenRunDialog,进入运行菜单,在OSGiframework中右键点击选择new一个新的OSGi运行环境,如图:
图6.新建OSGi运行环境
在右边的运行环境对话框中,输入运行环境的名字、startlevel和依赖的插件,由于我们目前不需要其它的第三方插件,因此只需要勾上系统的org.eclipse.osgi插件,如果不选择此插件,helloworld将无法运行。
如图7,只有当您点击了validatebundles按钮,并且提示无问题之后,才表明您的运行环境基本OK了。
图7.选择org.eclipse.osgi插件
依赖插件的选择:
图8.依赖插件的选择
好的,如果您的运行环境已经OK,那么就点击Run吧。
图9.运行OSGi项目
恭喜您,成功了!
OSGi控制台
OSGi控制台对于习惯开发普通Java应用程序的开发人员来说,还是比较新鲜的。
一般来说,通过OSGi控制台,您可以对系统中所有的bundle进行生命周期的管理,另外也可以查看系统环境,启动、停止整个框架,设置启动级别等等操作。
如图10,键入SS就可以查看所有bundle的状态:
图10.查看所有bundle的状态
下面列出了主要的控制台命令:
表1.EquinoxOSGi主要的控制台命令表
类别
命令
含义
控制框架
launch
启动框架
shutdown
停止框架
close
关闭、退出框架
exit
立即退出,相当于System.exit
init
卸载所有bundle(前提是已经shutdown)
setprop
设置属性,在运行时进行
控制bundle
Install
安装
uninstall
卸载
Start
启动
Stop
停止
Refresh
刷新
Update
更新
展示状态
Status
展示安装的bundle和注册的服务
Ss
展示所有bundle的简单状态
Services
展示注册服务的详细信息
Packages
展示导入、导出包的状态
Bundles
展示所有已经安装的bundles的状态
Headers
展示bundles的头信息,即MANIFEST.MF中的内容
Log
展示LOG入口信息
其它
Exec
在另外一个进程中执行一个命令(阻塞状态)
Fork
和EXEC不同的是不会引起阻塞
Gc
促使垃圾回收
Getprop
得到属性,或者某个属性
控制启动级别
Sl
得到某个bundle或者整个框架的startlevel信息
Setfwsl
设置框架的startlevel
Setbsl
设置bundle的startlevel
setibsl
设置初始化bundle的startlevel
MANIFEST.MF
MANIFEST.MF可能出现在任何包括主类信息的Jar包中,一般位于META-INF目录中,所以此文件并不是一个OSGi特有的东西,而仅仅是增加了一些属性,这样也正好保持了OSGi环境和普通Java环境的一致性,便于在老的系统中部署。
表2列出此文件中的重要属性及其含义:
表2.MANIFEST.MF文件属性
属性名字
含义
Bundle-Activator
Bundle的启动器
Bundle-SymbolicName
名称,一般使用类似于JAVA包路径的名字命名
Bundle-Version
版本,注意不同版本的同名bundle可以同时上线部署
Export-Package
导出的package声明,其它的bundle可以直接引用
Import-Package
导入的package
Eclipse-LazyStart
是否只有当被引用了才启动
Require-Bundle
全依赖的bundle,不推荐
Bundle-ClassPath
本bundle的classpath,可以包含其它一些资源路径
Bundle-RequiredExecutionEnvironment
本bundle必须的执行环境,例如jdk版本声明
重要的理论知识
好的,刚才我们已经从头到尾开发了一个基于Equinox框架的Helloworld应用程序。
我们发现似乎并不是很困难,很多工作Eclipse已经帮我们做好了,例如Activator代码框架和MANIFEST.MF文件,我们也学会了如何控制OSGi的控制台和编写MANIFEST.MF文件,但是,您真的明白它们是如何运行的么?
下面我们将重点介绍一些OSGi运行必备的基础知识。
什么是bundle?
我们已经看到,编写一个很普通的Helloworld应用,必须首先创建一个plug-in工程,然后编辑其Activator类的start方法,实际我们这样做的本质是为OSGi运行环境添加了一个bundle,那么一个bundle必须的构成元素是哪些呢?
1.MANIFEST.MF:
描述了bundle的所有特征,包括名字、输出的类或者包,导入的类或者包,版本号等等,具体可以参考表2.MANIFEST.MF文件属性。
2.代码:
包括Activator类和其它一些接口以及实现,这个和普通的Java应用程序没有什么特殊的区别。
3.资源:
当然,一个应用程序不可能没有资源文件,比如图片、properties文件、XML文件等等,这些资源可以随bundle一起存在,也可以以fragmentbundle的方式加入。
4.启动级别的定义:
可以在启动前使用命令行参数指定,也可以在运行中指定,具体的startlevel的解释,请参考后面的说明。
框架做了些什么?
好了,我们已经明白bundle是什么了,也知道如何开发一个基本的bundle了,那么我们还必须要明白,我的bundle放在Equinox框架中,它对我们的bundle做了些什么?
图11.Equinox框架架构
实际上,目标平台已经为我们准备了N个bundle,它们提供各种各样的服务,OSGi中,这些bundle的名字叫systembundle,就好比精装修的房子,您只需要拎包入住,不再需要自己铺地板,装吊顶了。
我们的bundle进入Equinox环境后,OSGi框架对其做的事情如下:
1.读入bundle的headers信息,即MANIFEST.MF文件;
2.装载相关的类和资源;
3.解析依赖的包;
4.调用其Activator的start方法,启动它;
5.为其提供框架事件、服务事件等服务;
6.调用其Activator的stop方法,停止它;
Bundle的状态变更
OK,现在我们大概明白了一个bundle的定义和其在OSGi框架中的生命周期,前面我们看到控制台可以通过ss命令查看所有装载的bundle的状态,那么bundle到底具有哪些状态,这些状态之间是如何变换呢?
我们知道了这些状态信息,对我们有何益处?
首先,了解一下一个bundle到底有哪些状态:
表3.Bundle状态表
状态名字
含义
INSTALLED
就是字面意思,表示这个bundle已经被成功的安装了
RESOLVED
很常见的一个状态,表示这个bundle已经成功的被解析(即所有依赖的类、资源都找到了),通常出现在启动前或者停止后
STARTING
字面意思,正在启动,但是还没有返回,所以您的Activator不要搞的太复杂
ACTIVE
活动的,这是我们最希望看到的状态,通常表示这个bundle已经启动成功,但是不意味着您的bundle提供的服务也是OK的
STOPPING
字面意思,正在停止,还没有返回
UNINSTALLED
卸载了,状态不能再发生变更了
下面请看一张经典的OSGibundle变更状态的图:
图12.OSGibundle变更状态图
Bundle导入导出package
OK,到现在为止,似乎一切都是新鲜的,但是您似乎在考虑,OSGi到底有什么优势,下面介绍一下其中的一个特点,几乎所有的面向组件的框架都需要这一点来实现其目的:
面向服务、封装实现。
这一点在普通的Java应用是很难做到的,所有的类都暴露在classpath中,人们可以随意的查看您的实现,甚至变更您的实现。
这一点,对于希望发布组件的公司来说是致命的。
图13.OSGibundle原理
OSGi很好的解决了这个问题,就像上面的图显示的,每个bundle都可以有自己公共的部分和隐藏的部分,每个bundle也只能看见自己的公共部分、隐藏部分和其它bundle的公共部分。
bundle的MANIFEST.MF文件提供了EXPORT/IMPORTpackage的关键字,这样您可以仅仅export出您希望别人看到的包,而隐藏实现的包。
并且您可以为它们编上版本号,这样可以同时发布不同版本的包。
Bundleclasspath
这一点比较难理解,一般情况下您不需要关心这个事情,除非事情出现了问题,您发现明明这个类就在这里,怎么就是报告ClassNotFoundException/NoClassDefExcpetion呢?
在您垂头丧气、准备砸掉电脑显示器之前,请看一下bundle中的类是如何查找的:
1.首先,它会找JRE,这个很明显,这个实际是通过系统环境的JAVA_HOME中找到的,路径一般是JAVA_HOME/lib/rt.jar、tools.jar和ext目录,endorsed目录。
2.其次,它会找systembundle导出的包。
3.然后,它会找您的import的包,这个实际包含两种:
一种是直接通过require-bundle的方式全部导入的,还有一种就是前面讲的通过importpackage方式导入的包。
4.查找它的fragmentbundle,如果有的话。
5.如果还没有找到,则会找自己的classpath路径(每个bundle都有自己的类路径)。
6.最后它会尝试根据DynamicImport-Package属性查找的引用。
启动级别Startlevel
在Equinox环境中,我们在配置helloworld应用的时候,看到我们将frameworkstartlevel保持为4,将Helloworldbundle的startlevel设置为5。
startlevel越大,表示启动的顺序越靠后。
在实际的应用环境中,我们的bundle互相有一定的依赖关系,所以在启动的顺序上要有所区别,好比盖楼,要从打地基开始。
实际上,OSGi框架最初的startlevel是0,启动顺序如下:
1.将启动级别加一,如果发现有匹配的bundle(即bundle的启动级别和目前的启动级别相等),则启动这个bundle;
2.继续第一步,直到发现已经启动了所有的bundle,且活动启动级别和最后的启动的bundle启动级别相同。
停止顺序,也是首先将系统的startlevel设置为0:
1.由于系统当前活动启动级别大于请求的startlevel,所以系统首先停止等于当前活动启动级别的bundle;
2.将活动启动级别减一,继续第一步,直到发现活动启动级别和请求级别相等,都是0。
开发一个真实的OSGi应用程序
我们不能只停留在helloworld的层面,虽然那曾经对我们很重要,但是现实需要我们能够使用OSGi写出激动人心的应用程序,它能够被客户接受,被架构师认可,被程序员肯定。
好的,那我们开始吧。
下面将会着重介绍一些现实的应用程序可能需要的一些OSGi应用场景。
发布和使用服务
由于OSGi框架能够方便的隐藏实现类,所以对外提供接口是很自然的事情,OSGi框架提供了服务的注册和查询功能。
好的,那么我们实际操作一下,就在Helloworld工程的基础上进行。
我们需要进行下列的步骤:
1.定义一个服务接口,并且export出去供其它bundle使用;
2.定义一个缺省的服务实现,并且隐藏它的实现;
3.Bundle启动后,需要将服务注册到Equinox框架;
4.从框架查询这个服务,并且测试可用性。
好的,为了达到上述要求,我们实际操作如下:
1.定义一个新的包osgi.test.helloworld.service,用来存放接口。
单独一个package的好处是,您可以仅仅export这个package给其它bundle而隐藏所有的实现类
2.在上述的包中新建接口IHello,提供一个简单的字符串服务,代码如下:
清单2.IHello
packageosgi.test.helloworld.service;
publicinterfaceIHello{
/**
*得到hello信息的接口.
*@returnthehellostring.
*/
StringgetHello();
}
3.
4.再新建一个新的包osgi.test.helloworld.impl,用来存放实现类。
5.在上述包中新建DefaultHelloServiceImpl类,实现上述接口:
清单3.IHello接口实现
publicclassDefaultHelloServiceImplimplementsIHello{
@Override
publicStringgetHello(){
return"Helloosgi,service";
}
}
6.
7.注册服务,OSGi框架提供了两种注册方式,都是通过BundleContext类实现的:
a.registerService(String,Object,Dictionary)注册服务对象object到接口名String下,可以携带一个属性字典Dictionary;
b.registerService(String[],Object,Dictionary)注册服务对象object到接口名数组String[]下,可以携带一个属性字典Dictionary,即一个服务对象可以按照多个接口名字注册,因为类可以实现多个接口;
我们