《OSGI实战》:第三章 生命周期(一)

 由  《OSGI实战》 发布

本章内容

  • 理解软件生命周期管理
  • 介绍bundle生命周期
  • 探究生命周期层API
  • 扩展应用程序使其具有生命周期特性
  • 阐释模块层和生命周期层之间的关系

上一章介绍了OSGi中的模块层并介绍了bundle:bundle是OSGi中为模块定义的术语,它是一个附加了额外的模块化元数据的JAR文件。借助bundle,你既可以为应用定义逻辑模块(代码封装及依赖)又可以定义物理模块(部署单元)。

OSGi中的模块层做了很多工作,来确保类加载以一种一致且可预期的方式实现。但是为了避免本末倒置,第2章没有介绍如何将bundle安装至OSGi框架的细节。本章同样不考虑,我们将学习OSGi栈中的下一层——生命周期层。

正如第2章提到的,若要使用bundle,你需要将它安装至OSGi框架的一个运行实例中。利用OSGi模块化特性可以从两方面入手,一方面是创建一个bundle,另一方面是借助OSGi框架在运行时管理并执行bundle。生命周期层非常独特,它支持创建在外部(包括远程)可管理或完全自管理的应用程序(或者是二者的组合)。本章还会介绍动态机制,这一般不是一个应用程序的组成部分。

学完本章,你将熟悉生命周期层的一些特性,并了解如何有效地使用它们。在接下来的章节中,我们将更进一步地了解什么是生命周期管理以及为何要关注它,随后介绍OSGi bundle生命周期的定义。在后续几节里你将学到管理bundle生命周期的API。本章将借助一个简单的OSGi shell示例以及一个拥有生命周期的绘图程序版本,帮助读者掌握所有知识。

3.1 生命周期管理

OSGi生命周期层提供了一个管理API,以及一个定义明确的生命周期,面向OSGi框架中执行时的bundle。生命周期层有两种不同的作用。

在应用程序外部,生命周期层精确地定义了对bundle生命周期的相关操作,这些对生命周期的操作,允许你动态地改变运行于框架中的bundle的组成,并以此来管理和改进你的应用程序。

在应用程序内部,生命周期层定义了bundle访问其执行上下文的方式,为bundle提供了一种与OSGi框架交互的途径以及一些执行时的便利条件。

让我们先回顾一下。尽管OSGi生命周期层有很大作用,但这不一定能够说服你相信其价值。相反,让我们通过一个简单例子了解一下在一个真实的场景中,生命周期层如何改进你的应用程序。

3.1.1 什么是生命周期管理

假定你有一个商业应用,它可以通过JMX报告管理事件。你是否总想启用甚至安装JMX层?假定运行在一个轻量级配置中并且只能根据需要启用JMX注释。生命周期层允许你在执行时从外部安装、启动、更新、停止以及卸载不同的bundle,进而定制应用的配置。

更进一步,假定无论管理员是否预先启用或者安装了JMX层,应用中一个重要的失败事件都必须触发JMX层向外发送一个通知。生命周期层也提供了通过编程方式访问bundle的方法,这样它就可以在执行时从内部修改其应用的配置。

一般来说,程序(或者程序的一部分)会显式或隐式地受到某种形式的生命周期的约束。典型的软件生命周期拥有四个不同阶段,如图3-1所示。

http://assets.osgi.com.cn/article/7289386/3-1.jpg

图3-1 软件生命周期的四个阶段。应用被安装后可以被执行。随后,你可以将其更新到新版本,最后,如果你不再需要它,可以将其移除

如果你正在创建一个应用程序,应从整体视角考虑该应用程序典型的生命周期。第一步你需要将其安装。假设该应用的所有依赖条件都已满足,你可以执行它,此时它开始获取资源。当不再需要该应用时,可以将其停止,此时该应用将释放所有资源,也许还要存留一些重要状态。随着时间推移,你也许要将该应用更新到新版本。最终,当不再需要该应用时,可能会将其移除。对于非模块化应用,生命周期将应用作为一个整体来操纵。但如你所见,对于模块化应用,以细粒度的方式管理一个应用的独立组成部分的生命周期是可能的。

在Java环境下创建应用有很多流行的模型,接下来介绍其中两款,同时介绍它们如何管理软件的生命周期。

标准Java——为方便讨论,我们将一个标准Java应用视作等同于一个JAR文件,在该文件的清单文件中包含Main-Class头标识,由此它可以很容易地被执行。在标准Java开发中,应用的生命周期非常简单。一个基于JAR的Java应用在下载时被安装。当用户启动JVM进程时被执行,通常通过双击它来实现。程序结束时该应用随之结束。一般情况下更新操作仅需将JAR文件替换为新版本。将JAR文件从文件系统中删除即可完成移除操作。

Servlet——在Servlet开发过程中,Web应用的生命周期由servlet容器进行管理。应用通过容器的特定进程被安装。在某些情况下,需要将一个包含应用的WAR文件放置到文件系统的指定目录下,或者通过Web管理界面上传一个WAR文件。在应用生命周期中的执行阶段,servlet容器会调用WAR文件中子模块的各种生命周期API,如Servlet.init() 和 Servlet.destroy()方法。若要更新应用,需要生成一个全新的WAR文件。原有的WAR文件必须停止,新WAR文件替换原有WAR文件后才能生效。应用被容器特定的进程移除,同样可以将WAR文件从文件系统删除,或者通过与管理接口交互来完成操作。

众所周知,现在在Java中有许多不同的生命周期管理方法。在传统的Java应用中,生命周期主要通过底层操作系统的平台特定机制进行管理,包括通过安装器和双击桌面图标来管理。对于模块化开发方法,如servlet、JavaEE以及Net Beans,它们都有自己特定的机制,管理其组件的生命周期。由此产生一个问题:为什么我们需要进行生命周期管理。

3.1.2 为什么需要生命周期管理

现在回到我们之前的讨论:为什么要将应用的代码进行模块化处理,放到相互分离的bundle中。将不同的关注点分别放到相互独立的bundle中,并避免代码的紧密耦合,这样做的好处我们已经分析过。OSGi模块层在类级别提供了必要的方式用以实现上述目标。但其并没有说明一个应用中何时需要一组特定的类或者对象。

一套对外开放的生命周期API使应用提供者关注如何配置、初始化以及维护其安装的部分代码,所以它可以决定在执行时如何操作。例如,如果一个数据库驱动程序正在被使用,那么它是否应该启动一些线程或者初始化一些缓存表来提升性能?如果它采取了上述措施,那么应该在什么时候将资源释放?它们是否在应用的整个生命周期结束后退出?如果没有,如何将它们删除?OSGi规范提供了一个对外开放的生命周期API,你可以使用任何能提供所需功能的bundle,并让它们管理其内部的功能。从本质上讲,这是一个组成与控制的问题。

由于你可以设计自己的应用程序,将应用的组成部分在任意时间自由地组合,这样应用的灵活性将得到大幅提升。你可以很容易地管理一个应用及其所需模块的安装、更新和移除。你可以为了满足需求,对应用进行配置和剪裁,这种方式打破了单一的传统开发方法。与“获得你所具备的”相比,“获得你所需要的”岂不是更好?

标准生命周期API的另一个很大的好处在于,它支持使用不同的管理工具管理应用。这并不神奇。使用提供的API完全可以实现对生命周期的管理。

我们希望这样的讨论可以激发你的兴趣。现在,将关注点放到OSGi bundle的定义以及相关的生命周期管理API上。

3.2 OSGi bundle的生命周期

OSGi的生命周期层提供了使用bundle的途径,是bundle真正发挥作用的地方。第2章介绍过的模块元数据还不错,但是只有把bundle用起来,构建bundles里的内容以及bundle自身才是有意义的。为了使用bundle,你需要同OSGi的生命周期层交互。模块层依赖于元数据,但生命周期层不同,它依赖于API。因为介绍API会比较枯燥(没人愿意看Javadoc),所以我们将采用自顶向下的方式,通过一个例子展示生命周期层的API允许你做哪些事情。

需要特别注意的是,OSGi核心框架没有指定特定的机制,来实现与生命周期层API的交互(例如命令行、GUI,或者XML配置文件)。其核心只是纯粹的Java API。这种方式展现出了强大的威力,因为这样就可以设计尽可能多的不同方式去管理OSGi框架。作为开发者,到最后只可能会被自己的想象力束缚。

因为在用户与生命周期API交互时,没有标准的方法,所以你可以使用特定于框架的机制。但如果本书也使用这种方式的话,对读者来说并没有什么好处,因为这是一个非常好的学习机会。本章没有复用别人已有的工作,而是通过一些基本的步骤,引导读者开发自己的命令行接口,实现与OSGi框架的交互。结合绘图程序,这种方式为读者提供了很棒的工具,用于探索OSGi生命周期API所提供的丰富功能。

无处不在的shell
如果对使用OSGi框架稍有了解的话,你可能会注意到,大多数OSGi框架的实现(比如Apache的Felix、Eclipse的Equinox、以及Knopflerfish)都有各自的shell界面,用于与正在运行的框架交互。OSGi规范没有定义标准的shell(尽管为实现此目标已经付出了一些努力,参见http://felix.apache.org/site/apache-felix-gogo.html),但是shell并不需要与特定的框架绑定,另外正如下面将要做的,shell可以通过bundle的方式实现。

3.2.1 将生命周期引入绘图程序

闲话少叙——下面我们通过启动shell应用,并利用它安装绘图程序,来了解生命周期API。首先请在操作系统的控制台里输入下述内容(Windows用户请将 / 替换为 \):

http://assets.osgi.com.cn/article/7289386/1.jpg

这个shell以bundle的形式被创建后即处于启动状态,它开始在一个telnet套接字上监听用户的输入。这就允许客户端连接并对bundles执行安装、启动、停止、更新以及卸载操作。同时该shell还提供了一些基本的诊断功能。下面是一个连接会话,展示了如何连接到刚刚启动的框架,并使用shell安装绘图程序:

http://assets.osgi.com.cn/article/7289386/2.jpg

在图3-2中,第一步,首先安装图形 API bundle,接着安装并启动了绘图程序bundle。因为我们还没有安装其他bundle,所以将生成一个空的不包括任何图形的绘图框架。第二步,安装并启动圆形和正方形这两个bundle。神奇的一幕出现了,这两个图形自动显示到绘图框架的工具条上,并可用于绘图。第三步,安装并启动了三角形bundle;接下来,就可以在画布上绘制图形了。如果停止一个bundle以后,会发生什么事情呢?在第四步中,我们停止了圆形的bundle,你会看到,画布中的相应图形被DefaultShape的占位符图标(一个构造器)代替。

http://assets.osgi.com.cn/article/7289386/3.jpg

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

图3-2 执行时状态变化:动态增加图形,并从绘图程序中移除图形,如同施了魔法一般

这个例子向读者实际展示了如何使用生命周期API,构建一个高度动态的应用,但是其中到底发生了什么事情呢?为了便于理解,我们将采用自顶向下的方式,在上下文中以shell和绘图程序为例。

3.2.2节将解释框架在应用的生命周期里所扮演的角色。

3.2.3节将介绍为了将bundle挂接到OSGi框架中,需要对bundle的清单文件做何种修改。

3.2.4节将研究OSGi生命周期使用的关键API接口:BundleActivator、BundleContext以及Bundle。

3.2.5节将回顾OSGi生命周期的状态图,然后结束本部分内容。 我们开始吧。

3.2.2 OSGi框架在生命周期中的作用

在标准的Java程序中,为了使用JAR文件,需要将其放置到类路径上。这与使用bundle的方法不同。bundle只有被安装到OSGi框架的运行实例之中,才能被使用。从概念上讲,你可以认为将bundle安装到框架之中,与在标准Java编程中将一个JAR文件放置到类路径上相似。

这种简化的视角掩盖了一些与标准类路径的重要差异,如图3-3所示。其中一个重要差异是OSGi框架支持对bundle形式的JAR文件实现全生命周期管理,包括安装、解析、启动、停止、更新和卸载。目前,我们仅简略提到了bundle的安装和依赖解析。本章余下部分中将充分解释生命周期活动,以及它们是如何联系到一起的。例如,我们已经提过,对于一个已安装的bundle,在其所有依赖(Import-Package声明)未被满足时,框架不允许使用它。

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

图3-3 类路径与带有全生命周期管理的OSGi框架 相对于标准类路径,另一个巨大的差异是内在的动态性。OSGi框架支持运行时的bundle全生命周期管理。这与动态地修改类路径有些类似。

作为生命周期管理的一部分,框架维护了一份已安装bundle的持续缓存。这意味着,下一次启动框架时,过去已安装的任意一个bundle都会被自动从bundle缓存中重新加载,而原始的JAR文件将不再需要。或许我们可以将框架定义为易管理的、动态的、稳固的类路径。听起来很酷,不是吗?接下我们分析一下为何要通过修改元数据的方式,来实现bundle与生命周期层API的挂接。

3.2.3 bundle激活器的清单文件条目

如何在执行时告诉框架要启动哪些bundle呢?正如下面的模块性信息,答案是通过bundle元数据。下面是一个JAR文件的清单文件,用于描述将要创建的shell的bundle。

http://assets.osgi.com.cn/article/7289386/4.jpg

学完第2章,你应该已经对大部分头标识比较熟悉了。但整体来说,大部分的条目与bundle的类级模块性相关联。这些元数据的功能如下:

定义bundle的标识;
指定bundle所依赖的包;
声明额外的可读信息。

唯一的新头标识是Bundle-Activator。这是首次在实战中见到OSGi的生命周期API!Bundle-Activator头标识指定了可访问的类(导入的类,或是在bundle的类路径上设置的类)的名字,该类实现了org.osgi.framework.BundleActivator接口。这个接口为bundle提供了挂接到生命周期层的钩子,同时自定义bundle在启动或停止时执行的操作。

激活器是否是必须的
请牢记一点,并非所有bundle都需要一个激活器。只有当构建一个bundle,并需要明确地与OSGi API进行交互时,或者需要执行自定义的初始化/销毁动作时,激活器才是必须的。如果你只是构建一个简单的库bundle,则不需要配置激活器,因为即便没有激活器,类共享也是可以实现的。
这并不表示你的bundle什么都不能做。为了提供某些功能,不一定需要启动bundle。回忆一下第2章创建的绘图程序:没有哪个bundle包含激活器,也不需要启动任何bundle,但你依然创建了一个功能完整的应用。

为了便于理解shell例子中所发生的事情,我们将介绍3个接口(BundleActivator、Bundle Context以及Bundle),它们是生命周期层API的核心和灵魂。

3.2.4 生命周期API

上一节描述了shell bundle如何声明BundleActivator类,并在执行时将其挂接到框架上。基于该接口,bundle就可以使用其他生命周期API,我们将深入介绍这个接口和生命周期API的细节。这是bundle与OSGi世界挂接的通道。

bundle激活器

正如你所见,向bundle中添加一个激活器是非常简单的,只需要构建一个实现Bundle Activator接口的类,如下所示:

http://assets.osgi.com.cn/article/7289386/5.jpg

在shell示例中,激活器使得该bundle具有生命周期的属性,并获得访问框架相关设施的权限。代码清单3-1展示了shell bundle的激活器。

代码清单3-1 简单的shell bundle激活器

http://assets.osgi.com.cn/article/7289386/6.jpg

该类实现了OSGi的BundleActivator接口。当这个bundle被安装并启动以后,框架将构建该激活器类的一个实例,并触发start()方法。bundle停止后,框架会调用stop()方法。start()方法是这个bundle的运行起始点,在某种程度上类似于标准Java中的静态方法main()。从start()方法返回后,bundle将开始发挥作用,直到后面某个时候调用stop()方法。另外,stop()方法应取消所有start()函数执行过的操作。

处理BundleActivator实例时,我们必须提及一些技术性不强,但可能很重要的细节。

调用start()方法的激活器实例与调用stop()的实例是同一个。

当stop()方法被调用后,激活器实例就被丢弃并不再使用。

如果一个bundle被停止后,又重新启动,那么将创建一个新的激活器实例,同时它的start()方法和stop()方法也将被适时触发。

正如你看到的,激活器的剩余部分并不复杂。在start()方法中,将获取bundle监听连接请求的端口以及支持的并发连接数量。同时也创建了一个TelnetBinding,用于监听套接字,接收用户输入并进行相应的处理。简单起见,这里忽略了创建telnet 绑定的细节。下一步是启动绑定,并创建一个新的Thread对象运行shell。其实现过程留给了接下来将被启动4的绑定完成。

绑定会启动属于自己的进程,这一点非常重要,因为激活器方法并不应该做太多工作。这是大多数返回速度很快的回调模式的最佳实践,从而支持框架继续管理其他bundle。但有一点需要指出,如果应用程序的启动过程并不能做出保证,那么OSGi规范不会强制你启动新的进程——决定权在你手中。

对于激活器的stop()方法,你需要做的只是告诉绑定停止监听用户输入,并不再执行。为了确保停止,应等待一段时间,直到线程结束。绑定方法等待它的线程停止。你可能会在某些情况下遇到特殊情况,这是因为,正如你将要看到的,shell线程本身也可能会调用stop()方法,这就会导致bundle僵死。在后面,我们将介绍这些问题,以及其他高级用例。在通常情况下,如果在你的bundle里使用进程,当stop()方法返回后,通过这种方式可以使得所有进程终止。

线  程
OSGi是围绕普通Java线程抽象设计的。不同于其他重量级的框架,它假设使用者自己做进程管理。这样做的好处是你可以获得很多自由,但是同时需要确保程序正确同步和线程安全。在这个简单的示例中,没有什么特别要做的,但stop()通常在不同于start()方法的线程被调用(因此,需要将成员设置为volatile)。
OSGi运行库是线程安全的,在给出保证的同时,回调一般就被完成。例如,对于bundle激活器,start()方法和stop()方法一定会按顺序调用,而非并发调用。所以,从技术上讲,在这个特别的例子中,volatile可能不是必须的,但通常来说,你的代码必须考虑线程可见性。

现在你已经知道如何启动和停止bundle,但是怎样才能实现与OSGi框架的交互呢?接下来我们将视角转移到BundleContext对象,该对象作为参数传递给激活器的start()和stop()方法。它允许一个bundle与框架进行交互,并管理其他bundle。

bundle上下文

上一节介绍过,当bundle被启动时,框架调用其激活器的start()方法,当它被停止时,则调用stop()方法。这两个方法都会接收BundleContext接口的一个实例。BundleContext接口的方法可以大致分为两类。

第一类与部署和生命周期管理相关。

第二类与bundle间服务式的交互相关。

我们对第一类很感兴趣,因为它们为你提供了许多额外的能力,包括安装和管理其他bundle的生命周期,获取框架的有关信息,查找基本配置属性。代码清单3-2展示了如何使用BundleContext中的这些方法。

代码清单3-2 与生命周期相关的BundleContext方法

http://assets.osgi.com.cn/article/7289386/7.jpg

本章内容将覆盖上面提到的大部分方法。BundleContext的第二类方法由于与服务相关,将在下一章介绍。

唯一的上下文
bundle 上下文对象是与之关联的bundle的唯一执行时上下文环境,它的这种角色是它的一个重要方面。因为它代表了执行上下文,所以只有当关联的bundle处于激活状态时,它才是有效的。更明确地说是指从激活器的start()方法被调用,一直到stop()方法结束的这一整段时间内。当关联的bundle没有处于激活态时,如果使用的话,大多数bundle上下文的方法都会抛出异常。它是唯一的执行时上下文环境,这是由于每个已激活的bundle都会接收到属于自己的上下文对象。框架之所以使用该上下文是由于考虑到安全性,以及出于为每个独立的bundle分配资源的目的。基于BundleContext对象的这一特性,它们应被视为敏感的或私有的对象,并且不能在bundle之间自由传递。

代码清单3-1所示的shell激活器使用了bundle上下文获取其配置属性值2。激活器也将上下文传递给了telnet 绑定3,它被客户端连接用来与运行框架交互。最后,激活器使用上下文获取了bundle的Bundle对象,从而获得识别信息。我们将简单研究这些细节,但是现在我们仍然继续自顶向下的描述,查看最后一个生命周期层的接口:org.osgi.framework.Bundle。

bundle

对于每个已安装的bundle,框架都会相应地创建一个逻辑上代表它的Bundle对象。Bundle接口定义了一系列API,用于管理已安装的bundle的生命周期。代码清单3-3展示了Bundle接口的一部分。由于我们讨论的是Bundle接口,你将看到大多数生命周期操作都有一个相应的方法。

代码清单3-3 bundle与生命周期管理相关的方法

http://assets.osgi.com.cn/article/7289386/8.jpg

每个已安装的bundle在框架中通过它的Bundle对象唯一标别。从Bundle对象中,你可以访问到两种额外的bundle识别信息:bundle的标识符和路径。你可能会想:“第2章不是讨论过bundle标识元数据了吗?”是的,确实讨论过,不过不要感到困惑。第2章中介绍的识别元数据针对的是bundle JAR文件的静态识别。bundle的标识符和路径则是执行时的识别信息,这也就意味着它们同Bundle对象有关。你可能想知道,为什么需要两个不同的执行时标识符。

这两个标识之间最主要的区别是谁定义了标识符,如图3-4所示。bundle标识符是一个Java语言的long类型数值,由框架根据bundle安装的顺序递增赋值。bundle位置是一个String类型数值,由bundle的安装器赋值。

bundle的标识和路径值唯一确定bundle对象,并且在框架执行时,当已安装的bundle从框架的缓存中被重载时,可以持续访问。

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

图3-4 bundle标识符的不同之处

bundle位置解释
bundle位置具有唯一的特征,因为许多OSGi框架实现将其解释为指向bundle JAR文件的URL。在接下来安装bundle的过程中,框架访问该URL去下载bundle JAR文件的内容。规范没有定义这个位置字符串必须为URL,也并不需要,因为你也可以通过一个输入流安装bundle。

你可能会想:“我不确信以上所有的识别机制都是必须的。难道使用第2章介绍的bundle的符号名称和版本号找不到Bundle对象吗?”不,能找到,因为框架要求每次只能安装一个使用给定的符号名和版本号的bundle。这就意味着一对bundle符号名和版本号也可以作为执行时的标识符。

为什么有这么多标识符
历史在这里扮演了重要的角色。正如第2章提到的,使用符号名和版本号唯一识别bundle的概念,并没有在R4之前版本的规范中出现。因此R4之前分别存在内部和外部分配的标识符是讲得通的。现在这么做就没太多意义了,因为bundle的符号名和版本号在外部定义并显式地被框架在内部识别。
bundle标识符还有一个作用,因为在某些情况下,需要在其他方面都相同的两个选项中做选择时,框架认为较低的标识符值比较高的要好,例如当两个bundle导出同一包的相同版本时。在这里,最不尽人意的是bundle路径,除了可能给定一个bundle JAR文件的原始URL之外,就没有其他用处了。

尽管每安装一个bundle到框架中,都存在一个对应的Bundle实例,但是在执行时仍然存在一个特殊的代表框架本身的Bundle实例。这个特殊的bundle被称为系统bundle;尽管API是相同的,但值得单独讨论。

系统bundle

在执行时,框架由一个标识符为0的bundle表示,该bundle称为系统bundle。你不需要安装系统bundle——当框架运行时,它会一直存在。

系统bundle遵循着与普通bundle一样的生命周期,所以你可以像操作普通bundle一样操作它。但当对系统bundle执行生命周期操作时,与普通bundle相比有其特殊的含义。一个明显的例子是当你停止系统bundle时。直观来说,停止系统bundle将以友好的方式关闭框架。它会首先停止其他bundle,然后才将其自身完全关闭。

基于此,我们将结束较高层次的对生命周期层主要API参与者的研究(BundleActivator、BundleContext和Bundle)。你现在应该可以明确如下信息。

  • BundleActivator是bundle的入口,与标准Java应用中的静态main()函数非常类似。
  • BundleContext为应用提供执行时操作OSGi框架的方法。
  • Bundle代表了一个已安装到框架中的bundle,允许对其执行状态操作。

掌握了上述知识后,我们将通过定义整个bundle生命周期状态图以及查看这些接口与它的联系,最终结束自顶向下的描述。

3.2.5 生命周期状态图

到目前为止,为了能够对组成生命周期层的API有一个更高层次的认识,我们推迟了对整个bundle生命周期的明确描述。这使得你可以快速展开工作。现在你可以更好地理解这些API与整个bundle生命周期状态图有何关联,如图3-5所示。

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

图3-5 OSGi bundle生命周期

bundle生命周期的入口是BundleContext.installBundle()操作,该操作会创建一个处于INSTALLED状态的bundle。从图3-5中可以看出,从INSTALLED状态到STARTING状态之间并没有直接连通的线。这是因为框架要保证在使用某个bundle之前(也就是说,所有类都不被加载),其依赖的所有bundle都是存在的。从INSTALLED状态到RESOLVED状态的转换提供了这种保证。除非所有依赖都已满足,否则框架是不会允许bundle转换到RESOLVED状态的。根据定义,如果不能转换到RESOLVED状态,那么该bundle也就不能转换到STARTING状态。当一个bundle启动时,或其他bundle需要加载它的类时,它到RESOVED状态的转换通常是隐式的,但在本章后面的内容中,你会发现也可以显式解析bundle。

从STARTING状态到ACTIVE状态的转换一直是隐示的。当执行bundle激活器里的start()方法时,该bundle就处于STARTING状态。如果start()方法执行成功,bundle会转换到ACTIVE状态,但是如果抛出异常,它将重新回到RESOLVED状态。

处于ACTIVE状态的bundle可以被停止,这会导致其状态经由STOPPING退回到RESOLVED。STOPPING状态同STARTING状态一样,也是隐式的,即在bundle激活器的stop()方法执行时,该bundle处于STOPPING态。因为依赖关系依然满足,并不需要再次解析,所以一个已停止的bundle会回到RESOLVED状态,而非INSTALLED状态。强制框架通过刷新或更新操作,重新解析一个bundle也是可以的,我们将在后面讨论。刷新或更新一个bundle将使其状态退回到INSTALLED状态。

一个处于INSTALLED状态的bundle可以被卸载,并且其状态转换到UNINSTALLED。如果要卸载一个处于激活状态的bundle,框架会首先自动停止该bundle,使其状态转换为RESOLVED状态,然后在卸载该bundle 之前将其状态转换为INSTALLED状态。一个处于UNINSTALLED状态的bundle只要还需要使用,就会一直保持该状态(后面将解释这意味着什么)。现在你应该理解整个bundle的生命周期了,接下来我们将讨论这些操作对框架的bundle缓存以及随后的框架重启的影响。

3.2.6 bundle缓存和框架重启

为了使用bundle,必须首先将其安装至OSGi框架中,并检查无误。但这意味着什么?从技术上讲,你知道必须通过调用BundleContext.installBundle()安装一个bundle。为了实现该目的,你必须指定链接到bundle JAR文件的位置,一般是一个URL,或者一个可以读入bundle JAR文件的输入流。无论哪种方式,框架都会读取JAR文件,并保存一份拷贝到被称为bundle缓存(bundle cache)的私有区域。这有两层含义:

  • 将bundle安装到框架中是持久操作。
  • 当一个bundle被安装后,框架将不再需要bundle JAR文件的原始拷贝。

bundle缓存的具体细节依赖于框架的实现;规范没有明确格式或者结构,只是要求在框架执行的过程中缓存空间必须是持久的。如果启动了一个OSGi框架,安装了一个bundle,关闭并重启了框架,之前安装的bundle仍然需要存在,如图3-6所示。相比于类路径要求你手动地去管理所有的事情而言,使用框架缓存和管理构件的方式会把你从大量工作中解放出来。

站在应用的角度,你可以将bundle缓存看做应用的部署配置。这类似于第2章中对构建不同配置的绘图程序的讨论。应用的配置是指将哪一个bundle安装到框架中。你可以通过API和本章讨论的技术维护和管理配置。

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

图3-6 框架重启时的bundle缓存

bundle的安装并非是影响bundle缓存的唯一生命周期操作。当一个bundle通过Bundle. start()方法启动时,框架将bundle持久地标记为已启动,即便Bundle.start()抛出异常也是如此,例如bundle不能被解析,或者bundle的BundleActivator.start()方法抛出异常。当bundle被持久地标记为已启动时,在框架随后的执行过程中,不仅会重新安装bundle,而且也会尝试启动它。从管理的角度讲,你可以通过安装并激活一系列bundle的方式来部署应用的一个配置。框架随后的执行会自动重启你的应用。如果你使用Bundle.stop()方法停止一个bundle,将会删除bundle的持久启动状态标志。随后的框架执行将不再重启bundle,尽管它仍然被重新安装。这是另一个修改应用配置的方法。

你可能会问,“更新或卸载一个bundle会怎么样呢?这些也会影响bundle缓存,是吗?”简短的回答,是这样的,但并不完整。Bundle.update()和Bundle.uninstall()各自通过保存新的bundle JAR文件或者删除已存在的bundle JAR文件来影响bundle缓存。但这些操作可能不会立即影响缓存。3.5节讨论模块化和生命周期层的关系时,将解释这些奇怪的现象。接下来将探讨shell bundle的细节,从而更全面的研究如何使用生命周期层的API。


泪雨迷情 2013-11-25 12:33

不错,好东西<br>

顶(0) 踩(0) 回复
查看评论