OSGi R4服务平台核心规范 :第三章 模块层(2)

 由  满江红开放技术研究组织 发布

版权说明

本文档版权归原作译者所有。 在免费、且无任何附加条件的前提下,可在网络媒体中自由传播。

如需部分或者全文引用,请事先征求作译者意见。

如果本文对您有些许帮助,表达谢意的最好方式,是将您发现的问题和文档改进意见及时反馈给作者。当然,倘若有时间和能力,能为技术群体无偿贡献自己的所学为最好的回馈。

本文档可从http://www.redsaga.com获取最新更新信息

3.8. 运行时类加载

框架中安装的bundle只有在解析之后才能关联到类加载器。bundle解析之后,框架必需为每一个非fragment的bundle创建一个类加载器。框架也可以延迟创建类加载器,直到它实际需要才创建。

每个bundle对应一个类加载器的机制使得bundle中的所有资源对于bundle中位于同一个包的其他资源具有包级别的访问权限。此类加载器为每一个bundle提供了它自己的命名空间,以避免命名冲突,并允许bundle之间的资源共享。

类加载器必须通过利用在解析过程中建立的连接(wire)来找到适当的exporter。如果一个类没有在导入包中找到,那么根据在附加的manifest中的定义在附加的空间进行查找。

本节定义了影响运行时类加载的因素,并定义了类和资源加载时框架必须遵循的查找顺序。

3.8.1. bundle类路径

在manifest中的Bundle- Classpath定义了bundle的内部类路径依赖。这种定义允许bundle通过在其JAR文件内使用多个JAR文件或者目录声明“嵌入”类路径。

在Bundle- Classpath manifest header中定义了逗号分割的文件名称列表,文件名称可以是:

  • 点号(‘.’,\u002E)代表bundle本身的JAR文件,如果没有指定Bundle- Classpath,那么它的默认值就是这个。

  • 在bundle的JAR文件中包含的JAR文件路径

  • 在bundle的JAR文件中包含的目录路径

Bundle- Classpath必须符合以下语法:

http://assets.osgi.com.cn/article/7289393/42.png

框架必须忽略任何不可识别的参数。

如果在Bundle- Classpath中指定的目标(目录或者是JAR文件)不能在需要的时候进行定位,那么框架必须忽略这样的目标,这在bundle解析之后随时可能发生。然而,在这种情况下,框架还应该发出一个类型为INFO的框架事件(Framework Event),带有关于它无法定位的每个条目的信息。

在定位bundle的类路径时,框架必需从bundle的JAR文件的根开始进行相对定位。如果一个类路径不能在bundle中定位,框架必需试图在附加的fragment bundle中进行定位。附加的bundle fragment根据bundle ID升序查找。这就允许fragment将一些条目插入到附主的Bundle- Classpath中。

如下示例:

http://assets.osgi.com.cn/article/7289393/43.png

在这个例子中,bundle A 的Bundle- Classpath描述了三个条目(required.jar,optional.jar,和default.jar)。required.jar条目可以是必须出现在bundle中的类和资源,optional.jar类路径条目可以是在可用的时候bundle可以使用的类和资源。

default.jar类路径条目描述的是如果optional.jar条目不可用时候的默认值,可以在optional.jar中对其覆盖。bundle A只有required.jar和default.jar条目,而bundle B可以为A提供optional.jar条目。

片断bundle B也有Bundle- Classpath描述了一个条目信息(fragment.jar),当bundle A解析后,片断bundle B附加到A之上,那么现在bundle A的类路径如下:

http://assets.osgi.com.cn/article/7289393/44.png

3.8.2. 动态导入包(Dynamic Import Package)

动态导入和导出定义进行匹配(形成包连接)是在class loading的时候进行的,从而不会影响模块的解析。

动态导入只会作用于没有建立连接,而且又找不到其他定义的包。动态导入是最后的解决方法。

http://assets.osgi.com.cn/article/7289394/45.png

DynamicImport- Package中没有规定任何指令。可以指定一些匹配属性。下面的属性会由框架进行匹配:

  • version—版本范围,用于选择导出定义的版本。默认值为0.0.0。
  • bundle- symbolic- name — 导出bundle的符号标记。
  • bundle- version — 版本范围,用于选择bundle版本。默认值为0.0.0。

包名可以是明确指定的,也可以是含有通配符如org.foo.或者是,通配符后缀可以表示任何标记,包括多个子包。

动态导入必须根据指定的顺序查找,尤其是在通过通配符指定包名的情况尤为重要。排序器在匹配的时候进行排序。也就是说越是明确指定的包,出现的也越靠前。例如,下面的例子指定了优先选择由ACME公司提供的包。

DynamicImport- Package: *;vendor=acme, *

如果多个包需要通过同一个参数进行动态导入,那么可以在参数之前通过在其中指定多个包,由分号分割。

在类加载过程中,类所在的包根据指定的包名(可能包含通配符)进行加载。每一个匹配的包名循环使用,试图使用和Import- Package同样的规则与exporter建立连接。如果连接尝试获得成功(考虑任何uses约束),搜索结果转发到导出的类加载器,继续进行类加载过程。这个连接在随后过程中都不能修改,即使类加载失败。这也就是说一旦包被动态解析了,那么随后的类或者资源的加载和普通导入是没有区别的。

为了将DynamicImport- Package解析为导出声明,动态导入中定义的所有属性必须和导出声明中的属性匹配。所有的强制任意属性(由exporter指定,参见强制属性)必须在动态导入定义中指定和匹配。

一旦建立了连接,在以后的动态导入中必须遵循exporter的任何uses约束。

动态导入非常类似于可选包,参见可选包一节,但它是在bundle解析之后处理的。

3.8.3. 父级代理(Parent Delegation)

框架必须将以java.开头的包交由父类加载器代理。

有一些Java虚拟机,包括SUN公司的虚拟机,错误的假定父级代理是经常发生的。这种关于严格分层的类加载器代理的假定会导致类没有定义的错误(NoClassDefFoundErrors)。如果虚拟机期待从任何类加载器中都能找到自己实现的类,而没有严格要求启动类加载器(boot class loader)只加载java.*包,就会发生上述错误。

其他必须通过启动类加载器加载的包可以通过在系统属性中指定:

http://assets.osgi.com.cn/article/7289394/46.png

通配符可以进行深度匹配,与这个序列匹配的包必须通过父类加载器来加载。而java.*前缀是默认指定的,不需要声明。

单个的通配符表示框架必须首先交由父类加载器代理,这和发布版本R3是一样的。例如,如果运行于一个SUN的虚拟机,那么这个属性值可以指定如下:

org.osgi.framework.bootdelegation=sun.*,com.sun.*

通过这个属性值,框架必须将所有的java.,sun.和com.sun.*的包交由父类加载器代理。

3.8.4. 完整查找顺序

在类和资源加载过程中,框架必须遵循以下规则。当请求一个bundle类加载器进行类加载或者资源的查找,查找必须按照以下顺序执行:

  1. 如果类或者资源是在包java.*中,那么交由父级类加载器代理完成,否则,搜索过程进入第二步。如果父级类加载器加载失败,那么查找过程结束,加载失败。

  2. 如果类或者资源在启动代理序列(org.osgi.framework.bootdelegation)中定义,那么交由父级代理完成,如果找到了类或者资源,那么查找过程结束。

  3. 如果类或者资源所在的包是在Import- Package中指定的,或者是在此之前通过动态导入加载的,那么将请求转发到导出bundle的类加载器,否则搜索继续进行下一步。如果将请求交由导出类加载器代理,而类或者资源又没有找到,那么查找过程中止,同时请求失败。

  4. 如果类或者资源所在的包是使用Require- Bundle从一个或多个其他bundle进行导入的,那么请求交由那些bundle的类加载器,按照bundle的manifest中指定的顺序进行查找。如果没有找到类或者资源,搜索继续进行。

  5. 使用bundle本身的内部bundle类路径查找。如果类或者资源还没有找到,搜索继续到下一步。

  6. 查找每一个附加的fragment的内部类路径,fragment的查找根据bundle ID顺序升序查找。如果没有找到类或者资源,查找过程继续下一步。

  7. 如果类或者资源所在的包由bundle导出,或者包由bundle导入(使用Import- Package或者Require- Bundle),查找结束,类或者资源没有找到。

  8. 否则,如果类或者资源所在的包是通过使用DynamicImport- Package进行导入,那么试图进行包的动态导入。exporter必须符合包约束。如果找到了合适的exporter,然后建立连接,以后的包导入就可以通过步骤三进行。如果连接建立失败,那么请求失败。

  9. 如果动态导入建立了,请求交由导出bundle的类加载器代理。如果代理查找失败,那么查找过程中止,请求失败。

如果代理转移到有另一个bundle类加载器,被代理请求的进入到算法第三步。

下面的非标准流程图展示了查找的过程:

http://assets.osgi.com.cn/article/7289394/47.png

3.8.5. 父类加载器

隐含导入包都是java.*包,这是由于它们都是Java Runtime所需要的,而且同时进行多版本控制不是那么容易。例如,所有的对象都是扩展自同一个类Object。

bundle中绝对不能导入或者导出java.*包,这样做是一个错误,任何这样的bundle必须安装失败。对正在执行的bundle来说,父类加载器可以获得的其他所有包必须被隐藏。

但是,框架必须明确导出和父类加载器关联的包。系统属性:

    org.osgi.framework.system.packages

包括了系统bundle导出的包的描述。这个属性采用标准的Export- Package语法描述

http://assets.osgi.com.cn/article/7289394/48.png

在启动类路径上的一些类假定他们可以使用任何类加载器来加载位于根类路径上的其他类,这对于bundle的类加载器来说是错误的。框架实现者应该试图从启动类路径加载这些类。

系统bundle(bundle的ID为0)用于从父类加载器导出非java.*的包。系统bundle导出定义看作是普通的导出,也就是说他们可以有版本号码,可以作为普通bundle的解析过程的一部分来用于解析导入定义。其他bundle也可以为同样的包提供一个替代的实现。

父类加载器中的导出定义序列也可以根据这个属性设置,或者由框架计算。导出定义必须实现了特定bundle符号名称的实现和系统bundle的版本的值。

这种风格的父类加载器中对外暴露的包必须考虑到其下的包的uses指令。例如,包javax.crypto.spec的定义必须声明包javax.crypto.interfaces和包javax.crypto的使用。

3.8.6. 资源加载

bundle中的资源可以通过bundle的类加载器进行访问,也可以通过方法getResource,getEntry 或者findEntries进行访问。所有这些方法都返回一个对象的URL或者是关于多个URL对象的枚举列表(Enumeration),这些方法返回的URL的模式可以是不一样的,由具体的实现来决定。

通常,bundle的入口URL是由框架来创建的,然而,在特定情况下,bundle需要操作URL来查找相关资源。这样框架需要保证:

  • bundle入口的URL必须是分层次的(参见统一资源标志符URI一节)

  • 作为构建其他URL的上下文环境

  • java.net.URLStreamHandler类用于bundle入口URL地址,这个类必须是类java.net.URL可以访问的,这样来根据框架定义的协议模式来初始化一个URL

  • bundle入口URL的getPath方法必须返回资源或者bundle入口的绝对路径(以‘/’开头)。例如,getEntry("myimages/test.gif")方法返回的路径必须包含有/myimages/test.gif。

例如,一个类可以通过一个URL访问bundle资源index.html,也可以是在同一个JAR文件目录下的对其他文件的URL映射表。

http://assets.osgi.com.cn/article/7289394/49.png

3.8.7. Bundle环

多重需求的bundle可能会导出同样的包。在包中类和资源的查找过程中,这样做会导致查找形成的bundle环。

考虑如下定义:

A: Require- Bundle: B, C C: Require- Bundle: D

下图是对定义的描述:

http://assets.osgi.com.cn/article/7289394/50.png

每一个bundle都导出包p,在这个例子中,bundle A需要bundle B,bundle C需要bundle D。当bundle A加载包p中的资源时,按照以下顺序进行查找:B,D,C,A。这是一个深度优先的搜索。如果在搜索路径中存在环,那么深度优先搜索会导致无限循环的搜索。

还是使用前面的例子,如果bundle D需要bundle A,就形成了一个环。

D: Require- Bundle: http://assets.osgi.com.cn/article/7289394/51.png

如果bundle A的类加载器从包p中加载类和资源,而且不考虑环,那么bundle的查找顺序将是:B,B,B……

由于生成了这样的环,每次查找到达bundle D时,将重新返回到A进行搜索。框架必须组织这样导致无限循环查找的依赖环形成。

为了避免无限循环,框架必须在第一次访问bundle的时候做一个标记,在以后的搜索中,对做了标记的bundle将不再访问。通过这样的访问模式,上述示例的查找路径如下:B,D,C,A。

3.8.8. 启动前执行代码

在bundle解析之后,bundle中导出的包就暴露给其他bundle了。这种状态也就是意味着其他bundle可以在导出包的bundle启动之前调用这些方法。

3.9. 本地代码加载

当一个bundle的类加载器试图通过调用System.loadLibrary来调用本地代码时,bundle类加载器的findLibrary方法被调用,然后返回一个文件路径名,这个路径名在框架的可用的请求的本地库中。bundle的类加载器必须通过测试选择的本地代码子句来试图找到本地代码库,包括bundle关联的类加载器和每一个附加的fragment。fragment的测试根据bundle的ID按照升序测试。如果在选择的本地代码子句中没有关联到搜索的库,那么返回一个null值,同时由父类加载器继续搜索。

为了在OSGi框架中加载本地代码,bundle必须有运行权限[loadLibrary.<库名称>],在manifest中的Bundle- NativeCode必须使用以下的语法形式进行描述:

http://assets.osgi.com.cn/article/7289394/52.png

当在bundle定位路径时,框架必须试图在启动bundle中进行路径的定位,启动bundle在其manifest中包含了相应本地代码子句。

定义了以下属性:

  • osname—操作系统名称。这个属性的值必须是本地代码运行的操作系统平台。在环境属性一节中定义了名称的规范。

  • osversion—操作系统版本。是一个版本的范围,参考版本范围一节的定义。

  • processor—处理器架构。运行本地代码的处理器架构的名称,参考环境属性一节。

  • language—ISO代码定义的语言名称。这个属性的名称必须是本地代码库使用的语言。

  • selection- filter—选择的过滤器。定义一个过滤器表达式,描述了本地代码子句中应该选择或者不应该选择的部分。

如下是一个典型的bundle中声明的本地代码示例:

http://assets.osgi.com.cn/article/7289394/53.png

如果多个本地代码需要安装在同一个平台,那么他们应该在同一个子句中进行声明。

http://assets.osgi.com.cn/article/7289394/54.png

上面的例子描述了本地代码库可以在Windows XP,3.1以及之后的操作系统上加载,这种描述是不正确的,单个的子句应该拆分成两个。

http://assets.osgi.com.cn/article/7289394/55.png

如果在描述子句中有一个可选的’*’号,那么即使在Bundle- NativeCode中没有找到匹配的子句,bundle的安装也不会报错。

如下就是一个典型的有星号的在bundle的manifest中的本地代码声明:

http://assets.osgi.com.cn/article/7289394/56.png

3.9.1. 本地代码算法

在算法描述中,[X]表示框架属性X的值,~=表示匹配操作,匹配是大小写不敏感的。

某些属性可以使用别名。在这种情况下,在manifest中使用一般的名称,而在框架中应该试图通过别名来进行匹配(参考环境属性一节)。如果一个属性不是别名,或者有一个错误的值,操作者(Operator)应该将系统属性设置为一般名称或者一个有效的值,这是由于java的系统属性会覆盖框架构造器的这些属性。例如,如果操作系统返回的版本号码是:2.4.2- kwt,那么操作者应该设置系统属性org.osgi.framework.os.version的值为2.4.2。

框架必须采用以下算法来选择本地代码子句:

1.只能选择以下所有表达式的值为真的子句:

  • osname ~= [org.osgi.framework.os.name]
  • processor ~= [org.osgi.framework.processor]
  • osversion 包括了 [org.osgi.framework.os.version] 或者没有指定osversion
  • language ~= [org.osgi.framework.language] 或者没有指定
  • selection- filter使用系统属性的值或者没有指定

2.如果在第一步中没有选择本地代码子句,算法没有中止,而是抛出一个bundle异常(BundleException)。

3.选择的代码子句按照以下规则进行优先级别的排序:

  • osversion:按照操作系统版本号的降序排列,然后再是没有指定osversion的
  • language:按照指定了语言的排前面,没有指定的排后面
  • 在Bundle- NativeCode中出现的顺序,从左到右的顺序

4.步骤3中选出的第一个子句用于作为本地代码子句。

无论是否指定了可选,如果在本地代码库中选择本地代码子句失败,那么bundle的安装失败,抛出bundle异常。

如果对一个选择过滤器计算,它的语法无效,那么bundle的安装过程失败,同时抛出一个bundle异常。如果没有计算出一个选择过滤器的值(有可能在一个操作系统版本或者处理器这些属性不匹配的本地代码子句中),那么这种无效的过滤器不应该导致安装的失败。在指定了可选的情况下,上述规定同样有效。

由于使用了不同的操作系统,代码库和语言,导致了设计bundle本地代码的头标变得非常复杂。可以采用将所有的参数用一个表格描述,而每一个目标环境都是表格的一行,这不失为一种很好的方法。如下所示:

http://assets.osgi.com.cn/article/7289394/57.png

通过上述表格,我们可以很容易就检查出遗漏的组合。然后,这个表格就和下述表格进行映射处理。

http://assets.osgi.com.cn/article/7289394/58.png

3.9.2. 考虑使用本地库

基于类加载器的本质特点,在加载本地代码时存在一些约束。为了保持类名称空间的独立性,只允许一个类加载来进行本地代码的加载,本地代码通过一个绝对的路径指定。多个类加载来加载本地代码(例如多个bundle)会导致连接错误。

直到加载本地代码的类加载器被垃圾回收器回收,加载的本地代码才被释放。

如果卸载或者是更新一个bundle,任何由bundle加载的本地代码都保留在内存中,直到bundle的类加载器由垃圾回收器回收才释放。而这需要所有的对象引用都已经被回收,而且所有从更新或者卸载的bundle中导入了包的bundle都已经更新完毕。这也就是说,系统类加载器加载的本地代码会一直停留在内存中,这是由于系统类加载器是绝对不会被垃圾回收器回收的。

3.10. 本地化

bundle中包括了大量的可读性很强的有效信息。有些信息需要根据用户的语言,国籍以及其他指定的参数,或者称之为本地化(locale)的这样一些参数来变换。本节描述了bundle中为manifest和其他依赖于本地化的资源配置所提供的一种通用的变换方法。

bundle本地化条目有一个通用的名称。为了找到潜在的本地化条目,下划线加上一个编号作为后缀,用下划线分开,最后加上一个.properties的后缀。在java.util.Locale中定义了这样的后缀规格。后缀的排序必须是:

  • 语言(language)
  • 国籍(country)
  • 变量(variant)

例如,下面的示例表示用英语,德语和瑞典语描述的manifest:

    OSGI- INF/l10n/bundle_en.properties
    OSGI- INF/l10n/bundle_nl_BE.properties
    OSGI- INF/l10n/bundle_nl_NL.properties
    OSGI- INF/l10n/bundle_sv.properties

框架通过在基本名称后面添加后缀来查找本地化条目,即添加指定的本地化参数最后加上.properties后缀。如果没有找到对应的变换,那么就逐步的减少参数,首先去掉变量信息,然后去掉国籍信息,最后是语言信息,直到找到了一个匹配的变换为止。例如,查找本地化的信息enGBwelsh的查找过程如下:

    OSGI- INF/l10n/bundle_en_GB_welsh.properties
    OSGI- INF/l10n/bundle_en_GB.properties
    OSGI- INF/l10n/bundle_en.properties
    OSGI- INF/l10n/bundle.properties

这样允许本地化文件通过指定更详细的信息来覆盖没有指定详细信息的本地化文件。

3.10.1. 查找本地化条目

本地化条目既可以在bundle中,也可以在片断fragment中。框架必须首先查找bundle,然后再查找fragment。而fragment必须将查找委托给具有最小bundle ID的附主bundle。

bundle的类加载器不能进行本地化条目的搜索。只对bundle的目录和附加的fragment进行搜索。而即使在bundle的类路径中没有逗号,也会对bundle搜索本地化条目。

3.10.2. 本地化manifest

本地化的值保存在bundle的属性资源中,bundle的基准本地化属性文件是OSGI- INF/l10n/bundle。Bundle- Localization可能已经为本地化文件定义了一个基准的名称。这样的本地化依赖于启动bundle和bundle片断。

本地化的条目包含了本地化信息的键值对信息。bundle中所有的manifest信息都可以本地化。但是,框架必须使用一个与本地化无关包含有框架语义的的版本信息。

本地化的键可以通过以下的规则指定为bundle的manifest中的值:

    header- value ::= ’%’text
    text ::= < any value which is both a valid manifest header
    value and a valid property key name >

例如:考虑如下的bundle条目:

    Bundle- Name: %acme bundle
    Bundle- Vendor: %acme corporation
    Bundle- Description: %acme description
    Bundle- Activator: com.acme.bundle.Activator
    Acme- Defined- Header: %acme special header

用户定义的头标也可以本地化。在本地化的键中可以使用空格。

前面的例子中的条目可以通过在OSGI- INF/l10n/bundle.properties的manifest中的条目进行本地化。

    # bundle.properties
    acme bundle=The ACME Bundle
    acme corporation=The ACME Corporation
    acme description=The ACME Bundle provides all of the ACME \services
    acme special header=user- defined Acme Data

也可以将上述的manifest条目本地化为法国的OSGI- INF/l10n/bundlefrFR.properties文件

3.11. Bundle 有效性

如果没有指定Bundle- ManifestVersion,那么默认的manifest属性是1,一些特定的R4 语法,例如一些新的manifest header,将被忽略而非被认为是一个错误。R3的bundle必须根据R3的规范处理。

下面的清单(不完全包括)列举了导致bundle安装失败的错误:

  • 不能根据Bundle- RequireExecutionEnvironment找到一个匹配可执行环境

  • 没有Bundle- SymbolicName.

  • 重复的属性或者指令

  • 对一个包的重复导入

  • 导入或者导出了java.*包

  • 没有定义带有强制属性的Export- Package

  • 安装一个和已经安装好的bundle具有同样的符号名称和版本的bundle

  • 将一个bundle更新成为和已经安装好的bundle具有同样的符号名称和版本的bundle

  • 任何语法错误(例如,不合法的版本格式或者是符号名称,不能识别的指令等)

  • 如果认为Specification- version是version的一个替代,而且指定了他们的值但是却不一样,例如:

Import- Package p;specification- version=1;version=2

将导致一个错误。而

Import- Package p;specification- version=1, q;version=2

不会导致错误。

  • 在manifest中列举了OSGI- INF/permission.perm但是没有这样的一个文件

  • Bundle- ManifestVersion的值不等于2,除非在今后的版本中有规定。

3.12. 可选项

规范提供了一系列的可选机制(optional machanism)。这些机制被设定为“可选”,是为了使得框架实现者可以选择性的让总体代码变得更小一些。所有的必选机制都必须实现,而所有的可选机制可以或多或少的实现。

下面定义了一些框架的可选部分,他们的名称是自解释的:

  • org.osgi.supports.framework.requirebundle
  • org.osgi.supports.framework.fragments
  • org.osgi.supports.framework.extension
  • org.osgi.supports.bootclasspath.extension

如果这些属性没有设置或者是他们的值不可认,那么就解析成默认值为false。

如果框架没有实现和上述机制相关的头标,那么框架必须拒绝安装或者更新带有这些头标的bundle。并且必须在安装和更新的时候抛出一个异常。

3.13. bundle的需求(Requiring Bundles)

框架可以支持bundle之间的直接连接机制,而不管在 package中指定的信息。本节定义了一些相关的头标,并讨论的可能的情形。最后说明了使用Require- Bundle会导致的一些后果(有些时候是未预见到的)。

3.13.1. Require- Bundle

在manifest中的Require- Bundle头标中定义了一个bundle symbolic name的列表,它们会在import 之后,在bundle的类路径搜索之前进行搜索。但是,对提出require的bundle,只有在被require 的bundle中,标记为exported的包才是可见的。

Require- Bundle必须遵循以下规范:

http://assets.osgi.com.cn/article/7289394/59.png

在Require- Bundle中可以使用以下指令:

  • visibility?—如果值为 private (默认),那么来自被需求bundle的所有的可见包不会再次导出。如果值为reexport,那么所有的这些包都会被提出需求的包进行导出,就好像这些包是位于提出需求bundle本地的一样。

  • resolution—如果它的值为mandatory(默认),那么被需求bundle必须在解析提出需求的bundle时存在;如果这个值为optional,那么即使被需求bundle不存在,也不会影响提出需求bundle的解析。

在框架中还使用了以下属性:

  • bundle- version—这个属性是一个版本范围,描述需求bundle的版本范围,参考版本范围一节。默认值为[0.0.0,∞)

某个给定包可能同时在一个或者多个被需要的bundle中提供,这样的情况是被明确允许的,这种包叫做拆分包(split packages)。拆分包没有在惟一的提供者中一次性提供,它的内容可以来自于不同的bundle。如下例:

http://assets.osgi.com.cn/article/7289394/60.png

如果bundle C导入了包p,那么C将连接到包A.p,但是,实际的内容来自于B.p > A.p。B导出定义中的mandatory属性确保了B并不是随机的作为包p的提供者。拆分包也有一些缺陷,“在bundle 需求中的一些问题”中进行了讨论。

来自拆分包的类和资源是必须按照Require- Bundle中的顺序进行搜索。

例如,假设一个bundle由一系列的bundle和可选的语言资源(也是bundle)组成:

http://assets.osgi.com.cn/article/7289394/61.png

bundle可以进行同时import 包(通过Import- Package)和require 一个或多个bundle(通过Require- Bundle),但是如果包通过Import- Package导入,那么它对于Requre- Bundle就是不可见的,这是由于Import- Package比Requre- Bundle具有更高的优先级别,而且,由 require bundle导出和通过Import- Package导入的包不能被看作是拆分包。

如果要require 一个具有名字的bundle,提出请求的bundle必须有bundle权限(BundlePermission [, REQUIRE]),bundle的符号名称即为被需要的bundle名称。被需求的bundle必须提供这样的bundle而且必须有这样的权限:

BundlePermission[, PROVIDE],其中,name和提出请求者指定的相同。

3.13.2. bundle需求中的一些问题

提倡的连接到bundle的方法是通过Import- Package和Export- Package来进行,这是由于通过这个途径,importer和exporter之间耦合度更小。可以将bundle进行重构,来具有不同的包结构,而不会导致其他bundle的安装失败。

Require- Bundle提供了一种无视export内容,将某个bundle的所有输出都绑定到另一个bundle的方法。尽管这种方法初看上去很方便,它也有一系列的缺陷:

  • 拆分包(Split Packages)——同一个包中的类来自不同的required bundle,这样的包称之为拆分包。拆分包有以下缺陷:

        - 完整性(Completeness)——拆分包是开放式的,没有一种方法可以保证拆分包中所有的部分是否已经完全被包括了。
        - 有序性(Ordering)——如果相同的类出现在多个需求的bundle中,那么对Require- Bundle来说顺序就非常重要了。排序的错误会导致难以跟踪的错误,类似于传统的类路径模型。
        - 性能(Performance)——如果是拆分包,那么类搜索必须要搜索所有的提供者,这样会导致抛出ClassNotFoundException的次数上升,从而产生显著的额外开销。
    
  • 导出可变(Mutable Exports)——visibility:=reexport 这一特性可以导致发起请求的bundle的导出特征(export signature),受到被需求bundle的导出特征改变的影响,从而发生不可预料的改变。

  • 遮蔽(Shadowing)—— 在发起请求的bundle中的类,可能被被需求的bundle中的类遮蔽,取决于被需求的bundle的导出特征和其中包含的类。(相反的是,在Import- Package中,除非指定resolution:=optional,将屏蔽整个export中的包)。

  • 不可预料的声明改变(unexpected signature changes)——在Require- Bundle中的指令visibility:=private(默认值),可能会在某些特定环境下形成意外的重载。如下所示:

http://assets.osgi.com.cn/article/7289394/62.png

bundle A的导出特征中只有包p,而p是一个拆分包。框架查找包p中的类时按照以下顺序:bundle B,C,最后是A。

因此, 导致”Require- Bundle C ;visibility:=private”这条指令对于包p来说是无效的。但是,如果B停止了导出包p,那么这条指令重新生效,导致在A的导出声明中失去了包p。如下图所描述:

http://assets.osgi.com.cn/article/7289394/63.png

3.14. Bundle片断 (Fragment Bundles)

片断(fragment)指的是由框架附加(attach)在附主bundle(Host bundle)之上的bundle。附加的过程是解析过程的一部分:在附主bundle解析之前,框架将片断bundle的相关定义添加到附主的定义之上。Fragment被认为是Host的一部分,它们不能有自己的类加载器。

片断的一个关键用途是用于提供不同区域的翻译文件。这样就可以实现翻译文件从主要应用的bundle中独立出来。

当更新已经attach的fragment时,原先fragment的内容必须仍然attach在附主bundle之上。更新的片断内容直到框架restart,或者附主bundle更新之后才可以附加上来。

当附加一个fragment到附主bundle之上时,框架必须按照以下步骤运行:

  1. 将与Host的导入定义无冲突的fragment的导入定义附加在Host 的导入定义上。冲突是指:有同名的包,但是却存在不同的指令或者是属性。如果有一个这样的冲突存在,那么fragement bundle就不会附加在Host上。Fragement可以为Host的私有包增加导入。在这种情况下,附主中的私有包就被遮蔽。

  2. 将与Host的Require- Bundle无冲突的fragment的Require- Bundle条目附加到Host的Require- Bundle条目上。冲突仅指:如果fragement和Host的Require- Bundle条目中bundle的符号名称相同,但版本范围不同。如果有一个这样的冲突存在,那么fragement bundle就不会附加在Host上。

  3. 将与Host的导出定义不冲突的fragment的导出定义附加到Host的导出定义上。冲突指的是:包名相同,如果存在冲突,那么忽略fragement中的导出定义,但继续将fragement附加到host上。 只有当fragement成功的附加在附主bundle上之后,才进入对frament的解析阶段。

在运行时,fragement的JAR文件将在Host bundle类路径的搜索之后进行搜索,对此后文“运行时Fragment”一节中有描述。

3.14.1. Fragment- Host

Fragement(片断)是一个附加到其他的称之为附主(host bundle)之上的bundle。Fragment的组成部分,例如Bundle- Classpath以及其他定义,都添加到附主的相关定义之后。片断中所有的类和资源必须使用附主的类加载器进行加载。

在manifest中的Fragment- Host的头标定义必须遵循以下语法:

http://assets.osgi.com.cn/article/7289394/64.png

同时框架在Fragment- Host中还提供了以下指令: - extension—指出了扩展的是一个系统类路径还是启动类路径的扩展。此指令只有在Fragment- Host是一个系统bundle时才有作用。在后文“扩展bundle”一节中进行了讨论。它支持以下值:

- framework——片断bundle是一个框架扩展bundle
- bootclasspath——片断bundle是一个启动类路径的扩展bundle

fragment必须具有“实现”所指定的系统bundle的bundle符号名称,或者是system.bundle这一别名。如果bundle的符号名称和系统bundle不相符,那么框架对扩展bundle的安装失败。

框架在Fragment- Host中还提供了以下属性:

  • bundle- version—用于选择附主bundle的版本范围。参考版本匹配一节,默认值为[0.0.0,∞)。

当片断bundle解析完毕,框架必须将片断附加在选择的具有最高版本的附主bundle之上。当片断附加在附主之上后,逻辑上它成为附主的一部分。片断中所有的类和资源必须使用附主的类加载器载入。一个附主的所有片断必须按照片断安装的顺序进行添加,即按照bundle ID的升序排列。如果在片断附加过程中发生错误,那么片断就不能附加在附主上。只有当片断附加成功之后才可以对片断进行解析。

如果一个bundle指定了Fragment- Host,那么就不能指定:

  • Bundle- Activator

附主要允许附加片断,必须有权限BundlePermission[,HOST]。而在片断中为了允许附加,片断必须要有权限BundlePermission[,FRAGMENT]。

3.14.2. 运行时Fragment ( Fragment During Runtime)

Fragment中所有的类和资源都是通过附主的类加载器来处理,而片断是不能有自己的类加载器的。片断被看作是附主的内在的一部分。

虽然片断没有自己的类加载器,当它不是扩展片断时,它还是必须有一个单独的保护域(Protection Domain)。每一个片断都可以有一个自己的关联到到片断bundle的位置和签名的权限。

附主bundle的类路径在片断类路径之前进行搜索。这也就是说包可以在附主和它的片断之间拆分。对片断的查找必须是按照bundle ID升序的顺序进行,这也是片断安装的顺序。

http://assets.osgi.com.cn/article/7289394/65.png

上图展示了两个片断,bundle B在C之前安装,而B和C都附加在A之上。下面的表格描述了在初始化过程中不同包的起源。注意其中的(>)符号。

http://assets.osgi.com.cn/article/7289394/66.png

在上面的例子中,如果包p从bundle D中导入,那么这个表格将变得完全不同。包p来自bundle D,而bundle A以及bundle B的内容将被忽略。

如果bundle D中的包p没有,那么就搜索类路径,而选择了A.q,这是由于A.q > C.q。

框架必须保持附加状态,只要附主是解析的。当附主变为没有解析的状态时,所有附加之上的片断必须要从附主分离。当片断变为没有解析状态时,框架必须:

  • 将它从附主分离
  • 重新解析附主bundle
  • 重新附加其他的片断

片断可以通过调用自己或者附主的refreshPackages方法或者是resolveBundle方法变为未解析状态。

3.15. bundle的扩展

框架可以有选择的部分实现扩展bundle,或者提供在启动类路径上必须的功能。这些包不能通过一般的导入导出机制来提供。

启动类路径的扩展是必须的,这是由于特定包的实现是假定它们在启动类路径上,或者是要对所有的客户端可用的。扩展启动类路径的一个例子是java.sql的一个实现JSR 169。

框架扩展对于框架的实现来说是必须的。例如,一个框架的供应商可以通过框架扩展的bundle提供可选的一些服务如权限管理服务,启动级别服务。

扩展的bundle应该使用bundle的符号名称来实现一个系统的bundle。或者是使用同样也是系统bundle的系统bundle替换。

下面的例子使用了Fragment- Host来为特定的框架实现来指定一个扩展的bundle:

    Fragment- Host: com.acme.impl.framework; extension:=framework

下面的例子使用了Fragment- Host来指定扩展bundle的启动类路径:

    Fragment- Host: system.bundle; extension:=bootclasspath

下面的步骤描述了扩展bundle的生命周期:

  1. 扩展bundle安装完成之后,进入安装完毕(INSTALLED)状态。

  2. 通过框架的判断,扩展bundle可以进入到解析完毕(RESOLVED)状态。

  3. 如果扩展bundle进行了更新,框架必须要关闭,附主虚拟机必须终止,而框架也需要重新启动。

  4. 如果是一个处在解析完毕状态的扩展bundle进行刷新,框架必须要关闭,附主虚拟机必须终止,而框架也要重新启动。

  5. 如果对一个处在解析完毕状态的扩展bundle进行更新或者是卸载(UNINSTALLED),那么就不能再次进入到解析完毕状态。如果对扩展bundle进行刷新,必须关闭框架,附主虚拟机终止,然后重新启动框架。

3.15.1. 扩展bundle的非法manifest

如果在bundle中指定了以下的头标,那么在扩展bundle安装或者是更新的时候,必将抛出bundle异常(BundleException):

  • Import- Package
  • Require- Bundle
  • Bundle- NativeCode
  • DynamicImport- Package
  • Bundle- Activator

规范允许同时在启动类路径和框架扩展bundle中指定Export- Package。当对扩展bundle解析时,任何由框架扩展bundle指定的导出的包必须由系统bundle来导出。

3.15.2. 类路径处理

bundle的JAR文件的扩展启动类路径必须添加到附主的虚拟机的启动类路径上。框架扩展的bundle的JAR文件附加在框架的类路径上。

扩展bundle的类路径的附加顺序按照它们的安装顺序来进行,即按照bundle ID的增序排列。

框架如果配置自己以及启动类路径如果附加扩展bundle是由具体的实现来完成。在一些执行环境下,也许并不支持扩展bundle。在这样的环境下,如果安装扩展bundle将抛出bundle异常。产生的bundle异常有一个原因标志为UnsupportedOperationException

3.16. 安全

3.16.1. 扩展bundle

在Java 2安全机制下的框架环境下允许安装扩展bundle之前还需要进行额外的安全检查。为了保证扩展bundle的成功安装,框架必须检查扩展bundle是否拥有分配给它的所有权限(All Permissions)。这也就是说扩展bundle的权限必须要在它安装之前设立。

必须授予扩展bundle的AllPermission权限,这是由于扩展bundle需要在启动类路径或者是框架实现的保护域(Protection Domain)之下加载。这两个保护域都有AllPermission权限。这样,如果一个扩展bundle没有AllPermission权限,那么是不允许安装的。扩展bundle的安装者必须要有管理权限(AdminPermission[,EXTENSIONLIFECYCLE])来安装扩展bundle。

3.16.2. bundle权限

大部分的包在包权限管理之上共享权限,而片断和需求的bundle通过bundle的符号名称来进行共享处理。bundle权限就用于这种类型的包共享。bundle权限是可选的,但是如果框架支持Require- Bundle,那么也必须要支持bundle的权限。

bundle权限的名称参数使用了bundle的符号名称。符号名称用于鉴别目标bundle(target bundle)。可以在名称后面使用通配符('.*’)。

例如,如果片断A附加到bundle B之上,那么A需要BundlePermission("B", "fragment")权限,这样A才允许附主bundle。这个指令的动作描述如下:

http://assets.osgi.com.cn/article/7289394/67.png

定义了如下动作:

  • provide – 对目标bundle提供包权限。
  • require – 从目标bundle中获取包权限。
  • host – 附加目标片断的权限。
  • fragment – 作为片断附加到附主上的权限。

3.16.3. 包权限

bundle只能导入导出有获取权限的包。包权限(PackagePermission)必须要用于一个包的所有的版本。

包权限由两个参数:

  • 可能导入或者导出的包,可以使用通配符。权限的粒度是针对包而不是包中的类。

  • 动作,导入(IMPORT)或者导出(EXPORT)。如果bundle有导出一个包的权限,那么框架必须自动授予导入这个包的权限。

如果一个包权限的参数为通配符*和EXPORT,那么就允许导入导出任何包。

3.16.4. 资源权限

为了访问以下资源,框架必须授予bundle RESOURCE,METADATA和CLASS 管理权限(AdminPermission):

  • 自身
  • 任何附加的片断
  • 导入包中的任何资源

bundle中的资源也可以通过使用bundle中特定的方法来访问。这些方法的访问者必须要拥有管理权限AdminPermission[bundle,RESOURCE]。

如果访问者没有必需的权限,那么就不能对资源进行访问,并且返回一个空值。否则返回一个资源的URL对象。这些URL对象称之为bundle资源链接(bundle resource URLs)。一旦返回了URL对象,当对资源进行访问时就不再进行权限检查。URL对象必须使用框架实现中定义的配置(scheme)。

bundle链接通常由框架来创建,但是,在一些情况下,bundle需要通过链接来查找到相关资源,例如,对于和给定资源处于同一个目录下的资源,可以创建它的资源链接。

对于不是由框架创建的资源链接,由于java.net.URL类的设计的原因,它的安全策略略有不同。 并不是所有的URL类构造方法会和URL流处理类(URL Stream Handler)进行交互(实现规范部分)。其它的构造方法至少会调用URL流处理类中的parseURL方法,而在URL流处理类中会进行安全检查。这种设计使得框架不可能在构建bundle链接时进行权限检查。

下面的构建方法在构建bundle资源链接时使用parseURL方法,并进行检查:

    URL(String spec)
    URL(URL context, String spec)
    URL(URL context, String spec, URLStreamHandler handler)

当使用上述构造方法进行bundle资源链接的创建时,框架的实现必须要在parseURL方法中对调用者进行必要的权限检查。如果没有必需的权限,parseURL方法抛出安全异常(Security Exception)。这将导致URL的构造方法抛出一个非正常的URL异常。如果调用者有必需的权限,那么就创建对资源访问的链接对象,而且以后再也不需要进行检查了。

在下面的构造方法中没有调用parseURL方法,这样在创建URL类期间就不可能进行权限的检查了:

    URL(String protocol, String host, int port, String file)
    URL(String protocol, String host, int port, String file,
    URLStreamHandler handler)
    URL(String protocol, String host, String file)

这些构造方法没有进行权限检查就创建了bundle的资源链接,因此,对权限的检查就推迟到调用时进行。访问这样创建的URL对象时,如果访问者没有权限(AdminPermission[bundle, RESOURCE])那么框架就会抛出一个安全异常。

3.16.5. 权限检查

由于多个bundle会导出具有相同类名的权限类,框架必须要保证权限检查时使用了正确的类。例如,调用checkPermission方法提供了权限类的一个接口:

void foo(String name) { checkPermission(new FooPermission(name,"foo")); }

权限接口类来自于独特的途径,权限只能用相同来源的实例来测试。

因此,框架需要基于类而不是类名来查找权限。如果需要实例化一个权限,那么必须要使用检查为实例的权限类。这对框架的实现来说很复杂,但是不会影响到bundle的开发人员。

考虑如下示例:

Bundle A:
    Import- Package: p
    Export- Package: q
Bundle B:
    Import- Package: p
  • bundle A使用p.FooService,使用这个类来检查q.FooPermission当调用它的任何方法时。

  • bundle B在其保护域中的权限信息对象中有一个FooPermission。

  • bundle B调用bundle A中FooService中的方法。

  • FooService通过使用一个新的FooPermission接口来调用checkPermission方法。

  • 在调用FooPermission中的方法之前,框架必须使用来自同一个类加载器中的FooPermission对象作为给定的FooPermission对象。在这种情况下,FooPermission类来自包A.q。

权限检查完毕之后,bundle B就有了一个FooPermission实例,这个实例使用了没有导入的包中类的。因此,框架需要实例化多种不同FooPermission类来满足不同bundle的需求。

3.17. 参考

[19] The Standard for the Format of ARPA Internet Text Messages STD 11, RFC 822, UDEL, August 1982 http://www.ietf.org/rfc/rfc822.txt

[20] The Hypertext Transfer Protocol - HTTP/1.1 RFC 2068 DEC, MIT/LCS, UC Irvine, January 1997 http://www.ietf.org/rfc/rfc2068.txt

[21] The Java 2 Platform API Specification Standard Edition, Version 1.3, Sun Microsystems http://java.sun.com/j2se/1.4

[22] The Java Language Specification Second Edition, Sun Microsystems, 2000 http://java.sun.com/docs/books/jls/index.html

[23] A String Representation of LDAP Search Filters RFC 1960, UMich, 1996 http://www.ietf.org/rfc/rfc1960.txt

[24] The Java Security Architecture for JDK 1.2 Version 1.0, Sun Microsystems, October 1998

[25] The Java 2 Package Versioning Specification http://java.sun.com/j2se/1.4/docs/guide/versioning/index.html

[26] Codes for the Representation of Names of Languages ISO 639, International Standards Organization http://lcweb.loc.gov/standards/iso639-2/langhome.html

[27] Zip File Format The Zip file format as defined by the java.util.zip package.

[28] Manifest Format http://java.sun.com/j2se/1.4/docs/guide/jar/jar.html#JAR%20Manifest

[29] W3C EBNF http://www.w3c.org/TR/REC-xml#sec-notation

[30] Lexical Structure Java Language http://java.sun.com/docs/books/jls/second_edition/html/lexical.doc.html

[31] Mathematical Convention for Interval Notation http://planetmath.org/encyclopedia/Interval.html

[32] Uniform Resource Identifiers URI: Generic Syntax RFC 2396 http://www.ietf.org/rfc/rfc2396.txt

[33] Codes for the Representation of Names of Languages ISO 639, International Standards Organization http://lcweb.loc.gov/standards/iso639-2/langhome.html


fth735010 2014-10-31 18:06

回复fth735010: 好的

顶(0) 踩(0) 回复

fth735010 2014-10-31 18:06

学习了

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