2.3 描述元数据
Bundle的元数据信息定义在/META-INF/MANIFEST.MF文件之中,OSGi规范中明确要求实现框架必须能够正确识别那些被预定义过的标记(在R5.0规范中预定义了28项标记),对于不可识别的标记以及不符合MANIFEST.MF标记格式的内容都要忽略且不能影响Bundle的正常解析。
2.3.1 预定义标记
以下列出了MANIFEST.MF文件中常用的预定义标记,除非特别说明,所列举的标记项都是可选的。对于其中某些较重要的标记(如Import-Package和Export-Package等),我们将在后续章节着重分析讲解。
(1)Bundle-ActivationPolicy
标记Bundle-ActivationPolicy设置Bundle的加载策略,该参数目前只有一个值:lazy,设置该参数后,Bundle将延迟激活,延迟至有其他的Bundle请求加载该Bundle中的类或资源时它才会被激活,如果不设置这个参数,那么Bundle启动时就会被激活。
示例:
Bundle-ActivationPolicy: lazy
(2)Bundle-Activator
标记Bundle-Activator指明一个Activator 类,在Bundle启动和停止时会分别调用该类的start()和stop()方法,以便执行程序员所希望的动作,该类必须实现org.osgi.framework.BundleActivator接口。
Activator类通常用于在Bundle启动时注册和初始化服务,在Bundle卸载时注销这些服务。它很常用,但并不是必须的。
示例:
Bundle-Activator: com.acme.fw.Activator
(3)Bundle-Category
标记Bundle-Category指明该Bundle的功能类别,可使用逗号分隔多个类别名称。这个功能类别仅供人工分类和阅读,OSGi框架并不会使用它。
示例:
Bundle-Category: osgi, test, nursery
(4)Bundle-Classpath
标记Bundle-Classpath指明该Bundle所引用的类路径,该路径应为Bundle包内部的一个合法路径,如果有多个Classpath,使用逗号分隔。在介绍Bundle类加载过程时我们会详细介绍这个标记。
示例:
Bundle-Classpath: /jar/http.jar,.
(5)Bundle-ContactAddress
标记Bundle-ContactAddress描述Bundle发行者的联系信息,仅供人工阅读,OSGi框架并不会使用它。
示例:
Bundle-ContactAddress: 2400 Oswego Road, Austin, TX 74563
(6)Bundle-Copyright
标记Bundle-Copyright描述Bundle的版权信息,仅供人工阅读,OSGi框架并不会使用它。
示例:
Bundle-Copyright: OSGi (c) 2002
(7)Bundle-Description
标记Bundle-Description给出关于该Bundle的简短描述信息,仅供人工阅读,OSGi框架并不会使用它。
示例:
Bundle-Description: Network Firewall
(8)Bundle-DocURL
标记Bundle-DocURL给出该Bundle文档的链接地址,仅供人工阅读,OSGi框架并不会使用它。
示例:
Bundle-DocURL: http:/www.acme.com/Firewall/doc
(9)Bundle-Icon
标记Bundle-Icon给出该Bundle的显示图标,图标应为一张正方形的图片,并通过参数size指出图标的宽度。OSGi规范要求实现框架至少要支持PNG图片格式。
示例:
Bundle-Icon: /icons/acme-logo.png;size=64
(10)Bundle-License
标记Bundle-License给出该Bundle的授权协议信息。
(11)Bundle-Localization
标记Bundle-Localization给出该Bundle在不同语言系统下的本地化信息,如果不设置此标记,它的默认值为OSGI-INF/l10n/bundle。
示例:
Bundle-Localization: OSGI-INF/l10n/bundle
(12)Bundle-ManifestVersion
标记Bundle-ManifestVersion指出该Bundle应遵循哪个版本的OSGi规范,默认值为1。对于OSGi R3规范,该值为1;对于OSGi R4/R5规范,该值为2。也可能在以后的OSGi规范中使用更高的数字,但现在仅允许将它设置为1或2。
示例:
Bundle-ManifestVersion: 2
(13)Bundle-Name
标记Bundle-Name定义该Bundle的名称。注意该名称只供人工阅读,在Bundle-SymbolicName标记中定义的名称才会作为程序使用的Bundle的唯一标识来使用。根据一般开发习惯,Bundle-Name中所定义的名称会在打包发布时与Bundle-Version一起构成该Bundle的文件名,所以这个名称一般不含空格或其他不能在文件名中出现的字符。
示例:
Bundle-Name: Firewall
(14)Bundle-NativeCode
如果Bundle中需要使用JNI加载其他语言实现的本地代码,那么必须使用Bundle-NativeCode标记进行说明。这个标记有如下附加参数:
osname:操作系统名称,如Windows等。
osversion:操作系统版本号,如3.1等。
processor:处理器指令集架构,如x86等。
language:遵循ISO编码的语言,如en,zh等。
seleciton-filter:选择过滤器,该值为一个过滤器表达式,指定被选中或未被选中的本地代码。
示例:
Bundle-NativeCode: /lib/http.DLL; osname = QNX; osversion = 3.1
(15)Bundle-RequiredExecutionEnvironment
标记Bundle-RequiredExecutionEnvironment定义该Bundle所需的执行环境,支持多种执行环境的Bundle使用逗号分隔。OSGi在设计上就有非常广泛的应用范围,从嵌入式系统至大型服务器执行环境必然会有许多差异,因此在这个标记中需要指出该Bundle所适合的执行环境。在后续章节中我们还会继续介绍OSGi执行环境。
示例:
Bundle-RequiredExecutionEnvironment: CDC-1.0/Foundation-1.0
(16)Bundle-SymbolicName
标记Bundle-SymbolicName给出该Bundle在OSGi容器中的全局唯一标识符。与其他可选标记不同,这个标记没有默认值,并且是Bundle元数据信息之中唯一一个必须设置的标记。程序将基于此标记和版本号在OSGi容器中定位到一个独一无二的Bundle。
当且仅当两个Bundle的Bundle-SymbolicName和Bundle-Version属性都相同的时候,它们才是完全相同的,不允许同时安装两个完全相同的Bundle到同一个OSGi容器之中。
Bundle-SymbolicName有以下两个附加参数。
singleton:表示Bundle是单例的。如果OSGi系统中同时存在两个Bundle-SymbolicName相同的(当然,要求Bundle-Version不相同,否则是不可能同时存在的)单例Bundle,那么仅有其中一个会被解析。如果其中一个没有声明为单例Bundle,则不会受到另外一个单例Bundle的影响,默认值为false。
fragment-attachment:定义Fragment Bundle是否能附加到该Bundle之上。允许值为always、never和resolve-time,含义为允许附加、禁止附加和只允许在解析过程中附加,默认值为always,即允许附加。
示例:
Bundle-SymbolicName: com.acme.daffy
(17)Bundle-UpdateLocation
标记Bundle-UpdateLocation给出Bundle的网络更新地址。如果Bundle需要更新版本,将使用这个地址。
(18)Bundle-Vendor
标记Bundle-Vendor给出该Bundle的发行者信息。
示例:
Bundle-Vendor: OSGi Alliance
(19)Bundle-Version
标记Bundle-Version给出该Bundle的版本信息,默认值为“0.0.0”。注意,这项信息并不是仅供人工阅读的,“版本”在OSGi中是一项受系统管理的信息。维护一个Bundle的不同版本也是运行OSGi框架的重要特征之一,当一个Bundle依赖另一个Bundle时,经常需要指明它依赖的是什么版本范围内的Bundle。
版本号是有序的,在Symbolic-Name相同的前提下,两个Bundle的版本可比较大小。完整的版本号会由“主版本号(Major)”+“副版本号(Minor)”+“微版本号(Micro)”+“限定字符串(Qualifier)”构成。
示例:
Bundle-Version: 22.3.58.build-345678
根据一般的开发习惯,上述4项版本号约定俗成地表示如下含义。
主版本号:表示与之前版本不兼容的重大功能升级。
副版本号:表示与之前版本兼容,但可能提供新的特性或接口。
微版本号:表示API接口没有变化,只是内部实现改变,或者修正了错误。
限定字符串:通常用于表示编译时间戳或者编译次数。
在比较版本大小时,从前往后逐项(含限定字符串)进行比较,当且仅当4个比较项都对应相等,两个Bundle的版本才相等,否则以第一个出现差异的版本号的大小决定整个Bundle版本的大小。
示例:
1.2.3 < 3.2.1 < 4.0
有一点必须注意,对于限定字符串的处理,OSGi和Maven是恰恰相反的,在Maven里,版本“1.2.3.2012”<=“1.2.3”,但在OSGi里则是版本“1.2.3.2012”>=“1.2.3”。
(20)DynamicImport-Package
标记Dynamic Import-Package描述运行时动态导入的Package。Package的导入和导出构成了OSGi多模块之间的组织协作关系,这是一个相对复杂而又很重要的内容,我们将在后续章节中专门介绍。
示例:
DynamicImport-Package: com.acme.plugin.*
(21)Export-Package
标记Export-Package描述被导出的Package。导入导出Package是模块层的核心功能,该内容将在后续章节中具体介绍。
示例:
Export-Package: org.osgi.util.tracker;version=1.3
(22)Export-Service
标记Export-Service描述被导出的服务,这个标记在OSGi规范中已经被声明为Deprecated了,不推荐继续使用此标记。
示例:
Export-Service: org.osgi.service.log.LogService
(23)Fragment-Host
当该Bundle是一个Fragment Bundle时,标记Fragment-Host指明它的宿主Bundle。
示例:
Fragment-Host: org.eclipse.swt; bundle-version="[3.0.0,4.0.0)"
(24)Import-Package
标记Import-Package描述该Bundle需要导入的Package。导入导出Package是模块层的核心功能,该内容将在后续章节中具体介绍。
示例:
Import-Package:org.osgi.service.io;version=1.4
(25)Import-Service
标记Import-Service描述导入的服务。这个标记在OSGi规范中已经被声明为Deprecated了,不推荐继续使用此标记。
示例:
Import-Service: org.osgi.service.log.LogService
(26)Provided-Capability
标记Provided-Capability描述该Bundle提供的服务特性(Capability)。服务特性是在OSGi R4.3规范中加入的新概念。在此之前,Bundle只能通过Bundle-RequiredExecution-Environment来声明所需的执行环境;但是在某些场景下一些执行环境特性是由其他Bundle提供的,这样依赖运行环境来描述所需特性就受到限制了。因此在R4.3规范中加入了Provided-Capability和Require-Capability来声明Bundle所需要和能够提供的特性。
示例:
Provided-Capability: com.acme.dict; from=nl; to=de; version:Version=1.2
(27)Require-Capability
标记Require-Capability描述该Bundle所需要的服务特性。
示例:
Require-Capability: osgi.ee; filter:="(&(osgi.ee=AcmeMin)(version=1.1))"
(28)Require-Bundle
标记Require-Bundle描述该Bundle所依赖的其他Bundle,一旦声明了依赖某个Bundle,就意味着可以直接使用所有从这个Bundle中导出的Package。
示例:
Require-Bundle: com.acme.chess
2.3.2 使用可视化工具
记录Bundle元数据的MANIFEST.MF文件是一个纯文本文件,这意味着手工编辑它是完全可行的。不过这样很繁琐且易于出错,在实际开发中一般不会手工去做,而通常会使用一些可视化的工具来配置Bundle的元数据信息。本节将介绍如何在Eclipse IDE下配置Bundle的元数据信息。
普通的Java工程是不存在OSGi元数据标记的,因此要先在Eclipse中建立一个“Plug-in Project”,如图2-2所示。
图2-2 新建工程
在New Project中选中“Plug-in Project”,单击“Next”后出现如图2-3所示的界面。
图2-3 设置目标平台
从图2-3中可见,新建Plug-in工程的界面与新建普通Java工程界面的主要不同是增加了“Target Platform”项。如果要开发的是运行在Eclipse之上的插件,那么在此选择插件所支持的Eclipse版本号,系统会根据不同的Eclipse版本所支持的API为插件建立可用的目标平台。
如果准备开发一个标准的OSGi Bundle,那么应该选择“an OSGi framework”,后面的下拉框有“standard”和“Equinox”两个选项。它们的区别是当选择standard时,只能使用OSGi规范所定义的标准API,这样建立出来的OSGi Bundle在任何一个符合OSGi规范的实现中都能执行;而选择Equinox时,除了可以使用OSGi的标准API外,还可以使用Equinox框架自己的扩展功能,譬如专有API、扩展点机制、Buddy加载器和P2更新平台等,不过这样建立的OSGi Bundle只能运行于Equinox框架之上,无法在Felix、Knopflerfish等其他OSGi实现框架上运行。
就本书的例子来说,除了第三、四部分中专门介绍Equinox框架特有功能的例子外,其他各部分内容都可以选择“standard”,通过标准的OSGi框架API来完成。
继续单击“Next”按钮,将出现如图2-4所示的“Content”对话框。
图2-4 设置工程基本信息
在这个对话框中可以设置一些最基本的Bundle元数据信息,其中各输入项与前面元数据标记的对应如下:
ID对应于Bundle-SymbolicName
Version对应于Bundle-Version
Name对应于Bundle-Name
Provider对应于Bundle-Vendor
Execution Environment对应于Bundle-RequiredExecutionEnvironment
Activator对应于Bundle-Activator
在这里可以直接单击“Finish”按钮,之后出现如图2-5所示的MANIFEST.MF编辑界面,这个界面可以作为可视化工具对Bundle的元数据进行编辑。
图2-5 Bundle元数据编辑界面
这个元数据编辑界面中有Overview、Dependencies、Runtime、Build四个面板,这四个面板之后的MANIFEST.MF和build.properties显示对应文件的文本内容编辑器。无论是使用面板进行可视化操作,还是直接用文本编辑的方式修改MANIFEST.MF文件的内容,这两种修改方式都是等效的且内容会互相同步。
可以使用Overview面板来编辑Bundle的基础信息(在图2-4中设置的那些信息)及测试、打包Bundle;使用Dependencies面板来配置Bundle的依赖关系(元数据中的Import-Package和Require-Bundle标记);使用Runtime面板来配置Bundle的导出Package和该Bundle的Classpath(元数据中的Export-Package和Bundle-Classpath标记)。如果在这两个面板中修改了Bundle的依赖关系,那么Eclipse会自动同步更新工程的“.classpath”文件,以便用户编码时能使用依赖包中提供的API;还可以使用Build面板来配置打包发布工程时是否需要发布源码、说明文档和其他资源等细节信息。
在这个例子中,工具生成的元数据如下:
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Bundle111
Bundle-SymbolicName: org.fenixsoft.osgi.bundle
Bundle-Version: 1.0.0.qualifier
Bundle-Activator: org.fenixsoft.osgi.bundle.Activator
Import-Package: org.osgi.framework;version="1.3.0"
Bundle-RequiredExecutionEnvironment: JavaSE-1.6