深入理解OSGI:第三章 生命周期层规范与原理(2)

 由  IcyFenix 发布

3.3 启动级别

开发人员可以使用代码来启动、停止某些Bundle,用户也可以在Equinox控制台中完成这项工作。但是从OSGi系统整体来看,各个模块的启动和停止顺序不应当由代码或人工完成,尤其是在Bundle数量很多时,OSGi框架提供一种全局的控制Bundle启动、停止的方案就显得更有必要了。OSGi规范定义了“启动级别”来满足这个需求,对于熟悉Linux系统的读者,对比下文的介绍就会发现,OSGi中的启动级别和Linux系统的启动级别非常相似。

启动级别是一个非负的整数,值为0时表示OSGi框架还没有运行或框架已经停止(根据具体上下文环境来区分这两种情况),只有Bundle ID为0的System Bundle的启动级别可以为0,除此以外,其他Bundle的启动级别都大于0(最大值为Integer.MAX_VALUE)。启动级别的数值越高,所代表的启动阈值就越高,即启动顺序会越靠后。OSGi框架还有一个活动启动级别(Active Start Level),用于确定目前的状态下应该启动哪些Bundle。读者可以把它想象成一个指针,在这个指针位置及其之前(Bundle启动级别小于或等于活动启动级别)的Bundle都是已经启动或即将启动的,而在它之后(Bundle启动级别大于活动启动级别)的Bundle都是未曾启动或即将停止的。

下列几个实际开发中常见的场景都可以通过合理安排各Bundle的启动级别来实现。

  • 安全模式(Safe Mode):把实现系统核心功能的、可以充分信任的Bundle的启动级别限制在某个阈值以内,这样在某个扩展功能的Bundle启动失败而导致整个系统无法运作时,可以调整活动启动级别至这个阈值,使系统以安全模式运行。
  • 闪屏(Splash Screen):如果整个系统启动的时间很长,就可能需要在初始化阶段显示一个欢迎屏幕,可以为实现欢迎屏幕的Bundle指定一个最小的启动级别,让它最先启动。
  • 模块优先级(Bundle Priority):某些功能,比如性能监控需要尽可能快速运行且不能有很长时间的启动延迟,应当为实现这些功能的Bundle指定较小的启动级别以便其尽快启动,而对于一些非关键的附加功能给予较低的优先级,使其延后启动。

3.3.1 设置启动级别

OSGi框架的活动启动级别和Bundle的启动级别都可以在框架启动时设定,也可以在运行期间更改。图3-2为在Eclipse中调试OSGi程序的启动界面,可在“Default Start level”和Bundle列表的“Start Level”列中分别设置OSGi框架的默认活动启动级别和具体某个Bundle的启动级别。

http://assets.osgi.com.cn/article/7289375/图3-2.jpg

图3-2 调试 OSGi程序的启动界面

在启动界面设置的信息,Eclipse最终都会通过JVM系统参数的方式传递给OSGi框架,默认启动级别存放到参数“osgi.bundles.defaultStartLevel”中,各个Bundle的启动级别和Bundle名称一起存放到“osgi.bundles”参数中。在对程序进行实际部署时,我们一般会使用配置文件来设置这些信息,图3-2中的配置就相当于在Equinox的configuration\config.ini配置文件中写入如下内容:

#Configuration File
#Wed Dec 07 09:40:32 CST 2011
osgi.bundles=reference\:file\:D\:/_DevSpace/WorkSpaces/equinox/BundleD@1\:start,re-

ference:file:D:/DevSpace/WorkSpaces/equinox/BundleA@8:start,reference:file:D:/DevSpace/WorkSpaces/equinox/BundleC@1:start,reference:file:D:/_DevSpace/WorkSpaces/equinox/BundleB@1:start osgi.bundles.defaultStartLevel=6

在编码过程中,开发人员可以使用StartLevel接口中的以下方法来调整Bundle和OSGi框架的启动级别。

  • setInitialBundletartLevel():设置Bundle的初始启动级别(Bundle初次安装时的启动级别)。
  • getInitialBundletartLevel ():获取Bundle的初始启动级别。
  • setBundleStartLevel():运行时调整Bundle的启动级别。
  • getBundleStartLevel():获取Bundle当前的启动级别。
  • setStartLevel():设置OSGi框架的活动启动级别。
  • getStartLevel():获取OSGi框架的活动启动级别。

在系统运行期间,用户还可以使用Equinox Console在不停机的情况下动态调整启动级别,Equinox框架的控制台默认提供了sl、setfwsl、setbsl和setibsl命令,用于“显示启动Bundle的级别”、“设置活动级别”、“设置Bundle初始启动级别”和“设置Bundle启动级别”的功能,这些功能在后台也是通过调用StartLevel接口来实现的。

3.3.2 调整活动启动级别

调整OSGi容器活动启动级别是一个渐进的过程。如果要将活动启动级别修改为一个新的值,我们将这个新值称为“请求启动级别”(Requested Start Level)。在OSGi容器启动或停止某些Bundle的期间,活动和请求的启动级别是不相等的,活动启动级别必须以步长为1的速度来增加或减少,逐渐接近并最后与请求启动级别相等。

在活动启动级别向请求启动级别趋近变化的过程中,如果活动启动级别在逐渐增加,那么启动级别与框架活动启动级别相等的Bundle都会被启动(执行Activator.start()方法),直到达到请求启动级别为止(启动级别与请求启动级别相等的Bundle也会被启动)。如果活动启动级别在逐渐减少,那么启动级别与框架活动启动级别相等的Bundle都会被停止(执行Activator.stop()方法),直到达到请求启动级别为止(启动级别与请求启动级别相等的Bundle不会停止)。如果某个Bundle在启动或停止过程中抛出了异常,也不能中断活动启动级别的变化过程,但框架会广播一个FrameworkEvent.ERROR事件通知所有注册了框架监听器(FrameworkListener)的对象。

当活动启动级别与请求启动级别相等之后,OSGi框架会广播出一个FrameworkEvent.STARTLEVEL_CHANGED事件通知所有框架监听器启动级别调整已经完成。 图3-3描述了活动启动级别渐进调整的过程,图中“A”代表活动启动级别的值,“R”代表请求启动级别的值。

http://assets.osgi.com.cn/article/7289375/图3-3.jpg

图3-3 活动启动级别向请求启动级别渐进的过程

当目前框架的活动启动级别小于Bundle的启动级别时,即使在代码中直接调用Bundle对象的start()方法,Bundle也无法启动。反过来却不成立,当目前框架的活动启动级别大于或等于Bundle的启动级别时,在代码中直接调用Bundle对象的stop()方法可以停止Bundle。

3.4 事件监听

事件监听在OSGi中是一种很常见的设计模式,在Bundle生命周期的不同状态相互转换时,OSGi框架会发布出各种不同的事件供事先注册好的事件监听器处理,这些事件被称为“生命周期层事件”。OSGi框架支持的生命周期层事件包括继承于BundleEvent类的,用于报告Bundle的生命周期改变的Bundle事件,以及继承于FrameworkEvent类的,用于报告框架的启动、启动级别的改变、包的更新或捕获错误的框架事件。

3.4.1 事件类型

BundleEvent和FrameworkEvent中都定义了一个返回值为int的getType()方法,这个方法用于说明该事件对象代表了什么事件类型,每一类事件用一个整数来表示。为了以后能够对事件类型进行扩充,事件监听器应当忽略不可识别的事件。Bundle事件所包含的事件类型和描述如表3-1中所示。

表3-1 Bundle事件类型
事件名称    描  述    事件值
INSTALLED   当Bundle被成功安装后发出 1
STARTED 当Bundle被成功启动后发出 2
STOPPED 当Bundle被成功停止后发出 4
UPDATED 当Bundle被成功更新后发出 8
UNINSTALLED 当Bundle被成功卸载后发出 16
RESOLVED    当Bundle被成功解析后发出 32
UNRESOLVED  当Bundle转变为未解析状态时发出  64
STARTING        当Bundle正处于启动期间发出    128
STOPPING    当Bundle正处于停止期间发出    256
LAZY_ACTIVATION 当Bundle进入延迟启动状态时发出  512

框架事件包含的事件类型和描述如表3-2中所示。

表3-2 框架事件类型
事件名称    描  述    事件值
STARTED 当OSGi框架启动完成之后发出 1
ERROR   当OSGi框架检测到错误信息后发出   2
PACKAGES_REFRESHED  当OSGi框架中的Bundle被刷新(调用了FrameworkWiring.refreshBundles()方法)后发出    4
STARTLEVEL_CHANGED  当OSGi框架的活动启动级别被改变(调用了Framework-StartLevel.setStartLevel()方法)后发出 8
WARNING 当OSGi框架检测到警告信息后发出   16
INFO    当OSGi框架检测到一般信息后发出   32
STOPPED_UPDATE  当Bundle更新操作导致OSGi框架暂时停顿时发出  64
STARTING    当Bundle正处于启动期间发出    128
STOPPED_BOOTCLASSPATH_MODIFIED  当OSGi框架因系统Bundle被更新或附加了一个提供BootClasspath的Bundle而停止时发出   256
WAIT_TIMEDOUT   当操作等待OSGi框架停止(比如调用了Framework.waitForStop()方法),但框架直到超时都仍未停止,此时发出该超时事件    512

与Bundle状态值类似,代表事件类型的整型值也使用位掩码来描述,这便于一个事件监听器同时处理多种事件类型,示例如下:

if((event.getType() & (INSTALLED | STARTED | STOPPED)) != 0){
    // 只能在INSTALLED、STARTED、STOPPED事件下执行的动作
}

3.4.2 事件分派

与BundleEvent和FrameworkEvent类相对应,OSGi规范定义了BundleListener和FrameworkListener接口来描述Bundle事件和框架事件的监听器。这两个监听器接口中分别包含了BundleListener.bundleChanged()和FrameworkListener.frameworkEvent()方法,接收到事件广播之后在这两个方法中进行相关处理,这些方法的处理动作都是默认异步执行的,不会阻塞Bundle和框架的状态转换过程。

另外,OSGi规范明确规定了必须以事件发生那一刻的监听器快照作为事件分派目标,因此可能出现在事件监听器执行时,这个监听器实际上已经不再监听当前所发生的事件的情况。具体情况笔者通过以下三个场景进行说明。

1)情景一:事件顺序如图3-4所示。

http://assets.osgi.com.cn/article/7289375/图3-4.jpg

图3-4 情景一示例

在这个场景中,监听器1不能接收到事件A,因为OSGi框架以事件发生那一刻的监听器快照作为事件分派目标,而监听器1不是在事件发生前注册的。

2)情景二:事件顺序如图3-5所示。

http://assets.osgi.com.cn/article/7289375/图3-5.jpg

图3-5 情景二示例

在这个场景中,已被注销的监听器2依然可以接收到事件B,同样是因为OSGi框架以事件发生那一刻的监听器快照作为事件分派目标。

3)情景三:事件顺序如图3-6所示。

http://assets.osgi.com.cn/article/7289375/图3-6.jpg

图3-6 情景三示例

在这个场景中,监听器3也无法接收到事件C,因为它的上下文对象(BundleContext)此时已经不存在(准确地说是存在但不可用)了。

BundleListener接口还派生了一个SynchronousBundleListener子接口,顾名思义,这个子接口被用来进行同步处理。因为STARTING和STOPPING这两个Bundle事件描述的是一个持续过程而非瞬间的状态转换,所以它们的监听器应当同步执行才是合理的,对于这两个事件,OSGi框架也只会分派给同步的监听器。

OSGi分别通过BundleContext.addBundleListener()和BundleContext.addFrameworkListener()添加Bundle事件和框架事件的监听器到OSGi框架的事件监听列表之中,添加监听器的动作一般在Bundle的Activator类中实现。

3.5 系统Bundle

OSGi框架本身也会以一个Bundle的形式向其他Bundle提供资源、Package和服务,比如已经在书中多次出现的Bundle、BundleContext、FrameworkListener等接口,以及后面将会介绍的EventAdmin、PackageAdmin等服务都是由系统Bundle提供的。OSGi规范规定了系统Bundle的Bundle ID固定为0,Bundle的getLocation()方法返回固定字符串“System Bundle”,这些特征使得任何Bundle都可以很方便地从BundleContex.getBundle(0)或BundleContex.getBundle("System Bundle")方法中获取到系统Bundle的对象实例。

在OSGi容器中,系统Bundle可以认为是一定存在的,每一个Bundle都默认依赖这个系统Bundle。下面列出了Equinox框架的系统Bundle的元数据信息。

osgi> headers 0
Bundle headers:
Bundle-Activator = org.eclipse.osgi.framework.internal.core.SystemBundleActivator
Bundle-Copyright = Copyright (c) 2003, 2004 IBM Corporation and others. All rights reserved. This program and the accompanying materials  are made available under the terms of the Eclipse Public License v1.0 which accompanies this distribution, and is available at http://www.eclipse.org/legal/epl-v10.html
 Bundle-Description = OSGi System Bundle
 Bundle-DocUrl = http://www.eclipse.org
 Bundle-Localization = systembundle
 Bundle-ManifestVersion = 2
 Bundle-Name = OSGi System Bundle
 Bundle-RequiredExecutionEnvironment = J2SE-1.5,OSGi/Minimum-1.2
 Bundle-SymbolicName = org.eclipse.osgi; singleton:=true
 Bundle-Vendor = Eclipse.org - Equinox
 Bundle-Version = 3.8.0.qualifier
 Eclipse-BundleShape = jar
 Eclipse-ExtensibleAPI = true
 Eclipse-SystemBundle = true
 Export-Package = org.eclipse.osgi.event;version="1.0",
……//版面关系省略其他Package
 Export-Service = org.osgi.service.packageadmin.PackageAdmin,org.osgi.service.permissionadmin.PermissionAdmin,org.osgi.service.startlevel.StartLevel,org.eclipse.osgi.service.debug.DebugOptions
Main-Class = org.eclipse.core.runtime.adaptor.EclipseStarter
Manifest-Version = 1.0

系统Bundle与OSGi框架密不可分,由于它的特殊性,其生命周期变化过程也与普通Bundle有所区别。以下是OSGi规范对系统Bundle生命周期几个过程执行的动作规定。 - 启动过程:Bundle的start()方法为空操作,因为OSGi框架一启动,系统Bundle就已经启动。 - 停止过程:Bundle的stop()方法会立即返回并在另外一条线程中关闭OSGi框架。 - 更新过程:Bundle的update()方法会立即返回并在另外一条线程中重启OSGi框架。 - 卸载过程:系统Bundle无法卸载,如果执行了Bundle的uninstall()方法,那么框架会抛出一个BundleException异常。

系统Bundle的启动级别固定为0,这个启动级别是无法使用StartLevel接口中的set-BundleStartLevel()进行修改的;如果这样做了,那么OSGi框架将会抛出一个Illegal-ArgumentException异常。

3.6 Bundle上下文

OSGi容器中运行的各个Bundle共同构成了一个微型的生态系统,Bundle的许多行为都无法孤立进行,必须在特定的上下文环境中才有意义,因为要与上下文的其他Bundle或OSGi框架进行互动。在代码中使用BundleContext对象来代表上下文环境,当Bundle启动的时候,OSGi框架就创建这个Bundle的BundleContext对象,直到Bundle停止运行从OSGi容器卸载为止。Bundle在进行以下操作时,需要通过BundleContext对象来完成。

  • 安装一个新的Bundle到当前OSGi容器之中(BundleContext.installBundle()方法)。
  • 从当前OSGi容器中获取已有的Bundle(BundleContext.getBundle()方法)。
  • 在OSGi容器中注册服务(BundleContext.registerService()方法)。
  • 从当前OSGi容器中获取已有的服务(BundleContext.getService()方法)。
  • 在OSGi容器中注册事件监听器(BundleContext.addBundleListener()、Bundle- Context.addFrameworkListener()方法)。
  • 从Bundle的持久储存区中获取文件(BundleContext.getDataFile()方法)。
  • 获取Bundle所处环境的环境属性(BundleContext.getProperty()方法)。

每个Bundle的BundleContext对象都是在调用Bundle.start()方法时由OSGi框架创建并以方法参数的形式传入到Bundle中,实现Bundle时一般会把这个对象的引用保留起来,以便在Bundle其他代码中使用,示例如下:

public class Activator implements BundleActivator {

private static BundleContext context;

// Bundle中其他代码调用Activator.getContext()方法获取
// BundleContext对象
public static BundleContext getContext() {
    return context;
}

public void start(BundleContext bundleContext) throws Exception {
    // 将BundleContext对象的引用保留起来
    Activator.context = bundleContext;
}
}

上下文对象涉及Bundle的数据安全和资源分配,它应当是Bundle私有的,不应当传递给其他Bundle使用。在Bundle的stop()方法执行完之后,Bundle对象就会随之失效,如果在Bundle停止后继续使用BundleContext对象,那么OSGi框架就会抛出IllegalStateException异常。

3.7 本章小结

本章介绍了Bundle如何启动,Bundle自安装到卸载所经历的生命周期状态和这些状态的条件转换过程,还介绍了OSGi框架是如何使用启动级别对系统中的Bundle进行管理调度的。

掌握了模块层和生命周期层的知识,我们已经可以构建一个最基本的可运行的OSGi系统了。但是OSGi对不同开发和应用场景还提供了非常多的标准服务支持,使用这些标准服务不仅能使我们的开发事半功倍,还能保证我们编写的程序具备通用性,符合业界接口标准。因此接下来我们将介绍OSGi的服务层,它是定义和执行所有标准服务的基础。

查看评论