深入理解OSGI
作者:魏照哲
日期:2009-3-6
欢迎转载、交流:QQ:734089783
声明:
本文的目的是帮助你更深入的了解OSCI的运行机制,假设了你已经了解了OSGI的基本概念,service、bundle等等。对插件体系结构有一定的了解。
我是最近在研究开源框架时结识的Equinox,在有了初步了解,我就被这个框架所吸引了。“微内核、系统bundles、应用bundles”,稳定、安全、可拓展、面向服务的、面向组件的等等,还有Eclipse3.x这个令人咋舌的成功实例,怎能让我没有深入学习并使用的兴趣!
OSGI R4规范:
OSGI R4规范有Framework、Standard Service、System Service、Framework Service、Porotocal Service等构成,在规范的specification中对各个部分有详细的说明,我们只学习核心的部分,了解其他部分。
OSGI的灵魂:OSGI Core Framework,如图:
这是其核心框架的构成,本身也是基于bundles机制的,由主要四个模块构成:运行环境(execution environment)、模块(mudules)、生命周期(life cycle)、服务注册(service registery)。
其中,运行环境(execution environment)提供了基于OSCI的应用系统的Java运行环境。其实,OSGI已经定义了一个可运行bundles的最小运行环境,这个运行环境是对其完善,是它达到Java的标准运行环境:符合Java2的标准、要求。
模块(mudules),提供了OSGI的类加载功能。与标准Java不同的是,一般的Java应用程序采用的是一个类加载器(classloader),为这个系统加载所的类。而在OSGI的类加载器,是单独对某个类负责。这样更体现了模块化。
生命周期(life cycle),提供了操作bundles的api。可以通过这个模块改变bundles的状态。启动、更新、运行、停止等等。
服务注册(service registery),提供了服务注册功能,即发布服务的功能。
注:在OSGI Core Framework中还包含一个安全层(Security Layer),是对Java安全机制的完善和拓展。
OSGI的规范从本质上决定了它的可扩展、动态性!为基于此框架开发的应用系统提供了运行的平台。接下来我们进行更进一步的了解:
1
Module模块
在OSGI系统中,Module的定义是Bundle,而Bundle是一个很重要的概念,曾经我已经提到过,很重要的一个区别是mf文件,又称作元数据,包含了对每个模块的描述。下面我们来解析一下mf元数据文件吧!
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: HelloWorld Plug-in
Bundle-SymbolicName: HelloWorld
Bundle-Version: 1.0.0
Bundle-ClassPath: bin/
Bundle-Activator: helloworld.Activator
Bundle-Vendor: Quasar
Import-Package: org.osgi.framework;version="1.4.0"
Export-Package: org.quasar.greet.event,
org.quasar.service
Require-Bundle: org.eclipse.osgi.services,
Org.eclipse.equinox.event
没有被特殊标注的部分我就不讲了,一看名字就都知道含义了,后面特别标注的需要我们的注意!
Import-Package:指明了需要导入的包
Require-Package:指明了需要使用的其他的bundle
Export-Package:致命了暴露的包
注:在使用Eclipse开发时,我们对于使用到的包有两种选择,一种是导入整个required bundle,另一种是导入需要的包。我们建议使用第二种,这样使程序更加轻便、利于部署。
关于bundle的导入、暴露还可以在mf文件中加以属性说明、过滤版本等。还可以通过配置实现动态加载。详细信息,请下载《osgi 实战》。
下面我们要了解一下OSGI规范中最经典的模块加强,类加载机制,它有两种类加载器:
System ClassLoader ,Bundle ClassLoader。如下图,详细的说明了OSGI的类加载过程,每个bundle模块有一个独立的classloader。
类加载示意图:
大致流程也就对于每个bundle加载引入的包、然后是自己的需要的bundle、最后是动态加载的部分。
如需要加载的为java.*的类,则直接委派给Parent Classloader,如在parent Classloader中找到了相应的类,则直接返回,如未找到,则抛出NoClassDefFoundException。
如加载的不是java.*的类,则进入这一步。判断加载的类是否属于boot delegation中配置的范围,如不属于则进入下一步,如属于则继续委派给Parent Classloader,如在Parent Classloader中找到则直接返回,如未找到,则进入下一步。可在配置文件中编写org.osgi.framework.bootdelegation的属性来决定boot delegation的范围,示例:
org.osgi.framework.bootdelegation=sun.*,com.sun.*
如属于Bundle Import package中的类,则交给export package的Bundle的classloader进行加载,如加载失败,则直接抛出NoClassDefFoundException,如加载成功则直接返回。这步就解释了之前在注意事项中所写的需要注意的包的问题。
如不属于Bundle Import package中的类,则搜索是否属于Require Bundles中export的package的类,如属于则交由export package的Bundle的Classloader进行加载,如加载成功则直接返回,如加载失败则进入下一步。
在Bundle classpath(就是在Bundle-Classpath所配置的路径)中搜索需要加载的类,如加载成功,则直接返回,如加载失败则继续进入下一步。
搜索Fragment Bundle(还记得配置的Fragment-Host吧)的classpath,如加载成功,则直接返回,如加载失败则继续进入下一步。
判断是否属于export的package,如属于则直接抛出NoClassDefFoundException,如不属于则进入下一步。
判断是否属于DynamicImport的package,如不属于则直接抛出NoClassDefFoundException,如属于则使用export package的Bundle的ClassLoader进行加载,如加载成功则直接返回,如加载失败则抛出NoClassDefFoundException。
对于Bundle中的资源文件,可使用bundle.getResource、bundle.getEntry或bundle.findEntries来获取,返回的为一个可被转变为java.net.URL的对象,通
(C) 2006 http://www.riawork.org 致力于 RIAWork、OSGI、Equinox 的推广 第 50 页 共 90 页 七、深入 OSGI OSGI 实战
过URL就可加载到相应的资源文件,如果要获取到其他Bundle的资源文件则需通过设置Require-Bundle的方式才可获取,Require-Bundle也可视为实现资源文件共享的一种方式,不过Require-Bundle并不是被推崇的一种方式,在OSGI规范中,认为Require-Bundle会造成split packages。
下面介绍两种特别的bundle:
l FragmentBundle
这种bundle是其他bundle,只有其他bundle被加载使用时,它才被激活,它自己之没有独立的ClassLoader的,这一点与其他的bundle不同。在mf文件中同构fragment host 来指明它的载体bundle。
l ExtensionBundle
这种bundle是作为system bundle的一个拓展存在的,我们通过frament host来指明他是system bundle的拓展。
Module定义了各个bundle之间的关系,为我们的模块化开发提供了很好的方案。
2
生命周期
生命周期提供了操作管理各个bundle的接口,下面,我们先了解一下bundle的几个状态阿吧,然后在学习怎样使用声明周期模块去管理各个bundle的状态。如图:
详细的操作见下表:
类别 |
命令 |
含义 |
控制框架 |
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 或者整个框架的 start level 信息 |
Setfwsl |
设置框架的 start level |
|
Setbsl |
设置 bundle 的 start level |
|
setibsl |
设置初始化 bundle 的 start level |
我们来看看生命周期层是怎样运作的吧!
一、安装bundle 通过BundleContext的installBundle方法安装INSTALLED
二、解析bundle 解析依赖关系,切换状态致RESOLVED
三、启动bundle 通过Bundle Activator的start方法来启动,若启动失败,则抛出BundleException异常,但状态会变为Active的,而当start在执行的过程中,我们的bundle状态是STARTING,start执行完毕后变为ACTIVE,如果start方法执行失败,则将状态变为RESOLVED。
四、停止bundle 通过BundleContext的getBundle得到id,然后调用其Activator的stop方法,如果执行失败,保留原状态。否则,当正在执行stop时是STOPING状态,执行完毕后为RESOLVED状态。
五、卸载bundle 通过bundle自己的卸载方法实现。
可以看出,对于bundles的管理,在OSGI中主要是通过Bundle Context和Bundle两个类实现的,Bundle除了提供了管理bundle的一些方法之外,还提供了getHeaders()、loadClass()、getResource()等方法分别用于获得MF文件的属性和加载类和获取资源等操作。
OSGI对bundles的监听,OSGI的监听结构是采用的Java的事件机制,包含了两种事件,Framework Event,Bundle Event,其中Framework Event有Framework触发,而Bundle Event由Bundle的生命周期中的状态切换触发。我们可以实现FrameworkerListener来监听Framework引发的事件,也可实现BundleListerner和SynchronousBundleListener来监听bundle对象。
4
服务层
在OSGI R4发布了Declarative Service后,实际上,这个层的作用已经被其取代了,但是我们还是来了解一下吧!
服务层完成了服务Service的发布、查询和绑定,其中,这些操作都是有BundleContext的registerService等相关方法完成的。
我们还可以实现ServiceListener来监听服务。为了更准确的响应事件,我们提供了参数可过滤事件。
本层,是基于上面说的两层的,共同构成了OSGI的动态服务机制。
其他层简介:
Start Level Layer
StartLevel在OSGI中其实代表的是启动的顺序,数字小的先于数字大的先启动。
我们怎样去设置StartLevel呢?
A.到config.inf中静态修改;
B.在应用系统中得到StartLevel Service动态修改。