3.1 介绍
Java platform只是提供了打包、部署、Java基础应用和组件验证的最小化支持。因此,许多java基础工程经常借助于专用的类加载器来创建用户模块层,用户实现打包、部署、应用和组件验证,如JBoss、NetBeans。OSGi Framework提供了一个通用标准的java模块化解决方案。
3.2 Bundle
Framework定义了模块化单元,这个模块化单元称为Bundle。一个Bundle由Java类和其他资源文件组成,并且可以为终端用户功能。Bundle通过良好的定义,Bundle之间可以通过导入(import)和导出(export)来共享Java package。在OSGi Framework中,只有Bundle是部署Java应用的实体对象。
Bunlde部署以Jar文件方式进行,其中Jar文件采用zip格式存储了应用以及资源数据,这个格式参考文献[9]Zip File Format。Bundle通常使用.jar后缀,但是为OSGi Bundle保留了一个特殊的MIME type,用于区分普通的Jar文件。MIME type为:application/vnd.osgi.bundle,这个type定义在参考文献[15] OSGi IANA Mime Type。
Bundle的Jar文件内容:
* 包含提供服务所必需的资源文件,这些资源文件可以是java的class文件,或者是HTML文件、帮助文件、图标文件等。一个Bundle Jar文件也可以嵌入其他Jar文件,但是不支持多层嵌套的Jar。
* 包含manifest文件,这个文件描述了Jar文件内容和Bundle信息。该文件是Jar的头文件,提供了Framework安装和激活Bundle时所需的特殊信息。例如,manifest提供了java package的依赖状态信息,这些信息必须要在Bundle运行之前加载。
* 在Jar文件根目录或者子文件夹下的OSGI-OPT目录中可以包含文档信息,这个目录下的内容都是可选的。例如,可以在OSGI-OPT目录下存储Bundle的源代码,而且管理系统可以删除这个文件夹下的内容以减少Framework的存储空间。
当Bundle启动后,这个Bundle可以为安装在OSGi Framework中的Bundle提供功能和服务。
3.2.1 Bundle Manifest Headers
Bundle的描述信息在manifest文件中,这个文件是指Jar中的META-INF/MANIFEST.MF。
Framework定义了OSGi manifest文件头,如Export-Package和Bundle-ClassPath。Bundle开发人员可以使用这些描述信息,Manifest头文件必须严格遵守参考文献[10] Manifest Format。
Framework必须实现:
* 处理manifest的主要部分。Manifest中的个别部分只用于Bundle的签名验证。
* 忽略不可识别的manifest头信息。因此,Bundle开发人员可以在manifest文件中定义额外的附加信息。
* 忽略不可识别的属性和指令。
下面列出了指定的manifest头信息,除非特别申明,所有标记信息都是可选的。
3.2.1.1 Bundle-ActivationPolicy: lazy
Bundle-ActivationPolicy指定了framework第1次如何启动Bundle。
3.2.1.2 Bundle-Activator: com.acme.fw.Activator
Bundle-Activator指定了如何启动和停止Bundle的类,具体参考4.4.5启动Bundle
3.2.1.3 Bundle-Category: osgi, test, nursery
Bundle-Category表示逗号分隔的分类名称。
3.2.1.4 Bundle-ClassPath: /jar/http.jar
Bundle-ClassPath定义了逗号分隔的路径,内容为Jar文件路径和类路径(Bundle内部)。点号(’.’ \u002E)表示Jar的根目录,而且也是默认的。具体参考3.9.1Bundle类路径。
3.2.1.5 Bundle-ContactAddress: 2400 Oswego Road, Austin, TX 74563
Bundle-ContactAddress提供发行者的联系地址。
3.2.1.6 Bundle-Copyright: OSGi (c) 2002
Bundle-Copyright指Bundle的版权信息。
3.2.1.7 Bundle-Description: Network Firewall
Bundle-Description定义了Bundle的简短描述信息。
3.2.1.8 Bundle-DocURL: http://www.example.com/Firewall/doc
Bundle-DocURL指Bundle的文档链接地址。
3.2.1.9 Bundle-Icon: /icons/acme-logo.png;size=64
Bundle-Icon是可选项,提供了不同大小的icon图标URL列表来表示这个Bundle,下面的属性是允许的:
- size-(integer),指定了icon图标的水平像素大小,建议始终包含一个64x64的icon。 icon的URL关联Bundle,也就是说,如果提供URL,那么需要提供一个绝对地址URL。否则,在Jar文件的路径入口处,可能会使用到任意链接片段。这个头信息的实现至少需要支持Portable Network Graphics (PNG) 格式, 具体参考文献 [17] Portable Network Graphics (PNG) Specification (Second Edition)。
3.2.1.10 Bundle-License: http://www.opensource.org/licenses/jabberpl.php
Bundle-License提供了一个机器可读的许可证信息(license),这个是可选项。提供这个头信息的目的是多个组织自动化处理许可证,例如Bundle被使用前的许可证验证。而且这个头结构提供了唯一的license命名,license信息是被读者可以阅读的,但是这个头信息只是纯粹的信息管理代理,OSGi Framework并不会处理。
语法如下:
Bundle-License ::= ’<<EXTERNAL>>’ |
( license ( ’,’ license ) * )
license ::= name ( ’;’ license-attr ) *
license-attr ::= description | link
description ::= ’description’ ’=’ string
link ::= ’link’ ’=’ <url>
头信息有如下属性:
- name-为license提供了一个全局唯一的名字,最好是互联网范围下唯一命名,但是至少需要遵守互联网中的其他条款。<<EXTERNAL>>表示本文不包含任何license信息,但是授权信息在其他地方提供,这也是该头文件的默认内容。 Bundle的客户端能识别相同名字的license引用了同一个许可证。例如,为了最小化点击license,name需要使用URL,他不应该局限于翻译者,这个URL必须存在而且指明最新版本的许可证信息。建议使用参考文献[18]Open Source Initiative的URL规范,其他许可证建议使用如下结构,这不是强制的: http://<domain-name>/licenses/<license-name>-<version>.<extension>
- description-(可选项),提供了许可证的描述信息。这个简要描述可以在UI中的列表选择框可以用到。
- link-(可选项),提供了一个URL页面定义或者解释许可证。如果这个链接地址不存在,URL可以关联Bundle根目录下的某一个具体文件。 如果Bundle-License声明不存在,并不意味着这个Bundle没有许可证。许可证可以在Bundle外部,<<EXTERNAL>>内容会被认可。这个头信息没有任何法律支撑,在使用许可证之前请咨询律师。
3.2.1.11 Bundle-Localization: OSGI-INF/l10n/bundle
Bundle-Localization包含Bundle的本地化文件地址,默认值是OSGI-INF/l10n/bundle。其他翻译文件如OSGI-INF/l10n/bundle_de.properties, OSGI-INF/l10n/bundle_nl.properties,具体参考3.11本地化。
3.2.1.12 Bundle-ManifestVersion: 2
Bundle-ManifestVersion定义了Bundle需要遵守本规范的规则,默认值是1表示第3个版本的Bundle,2表示第4个版本的或者更后发布的版本,也可以为OSGi Framework定义更高的数字。
3.2.1.13 Bundle-Name: Firewall
Bundle-Name定义了一个有可读性的名字来表示Bundle,名字应该是简短易读而且没有空格的。
3.2.1.14 Bundle-NativeCode: /lib/http.DLL; osname = QNX; osversion = 3.1
Bundle-NativeCode包含本地代码库的规范,具体可以参考3.10加载本地代码库
3.2.1.15 Bundle-RequiredExecutionEnvironment: CDC-1.0/Foundation-1.0
Bundle-RequiredExecutionEnvironment指示在OSGi framekwork上必须支持的可执行环境,用逗号分隔。具体参考3.4可执行环境,这头信息不建议使用。
3.2.1.16 Bundle-SymbolicName: com.acme.daffy
Bundle-SymbolicName为Bundle提供了一个全局唯一的名字。在framework中多次安装Bundle时,Bundle SymbolicName和version必须唯一。SymbolicName是基于反域名解析的。具体可参考3.6.2Bundle标志符一节,这部分头是必须的。
3.2.1.17 Bundle-UpdateLocation: http://www.acme.com/Firewall/bundle.jar
Bundle-UpdateLocation指定了Bundle的更新URL,如果Bundle需要更新,那么会使用这个地址更新Bundle。
3.2.1.18 Bundle-Vendor: OSGi Alliance
Bundle-Vendor描述Bundle的发行者信息。
3.2.1.19 Bundle-Version: 1.1
Bundle-Version表示Bundle的版本信息,默认值是0.0.0 ;具体参考3.2.5版本(verison)
3.2.1.20 DynamicImport-Package: com.acme.plugin.*
DynamicImport-Package包含了一个逗号分隔的动态导入包清单。具体可参考3.9.2 Dynamic Import Package(动态导入包)
3.2.1.21 Export-Package: org.osgi.util.tracker;version=1.3
Export-Package包含导出package声明信息。具体可参考3.6.5 Export Package
3.2.1.22 Export-Service: org.osgi.service.log.LogService
不建议使用
3.2.1.23 Fragment-Host: org.eclipse.swt; bundle-version="[3.0.0,4.0.0)"
Fragment-Host定义了本片段中的主Bundle,参考3.14.1片段主体(Fragment-Host)
3.2.1.24 Import-Package: org.osgi.util.tracker,org.osgi.service.io;version=1.4
Import-Package声明Bundle导入的包。具体可参考3.6.4 Import-Package
3.2.1.25 Import-Service: org.osgi.service.log.LogService
不建议使用
3.2.1.26 Provide-Capability: com.acme.dict; from=nl; to=de; version:Version=1.2
Provide-Capability描述了Bundle提供的一组Capability,具体可参考3.3依赖
3.2.1.27 Require-Bundle: com.acme.chess
Require-Bundle描述了该Bundle import了其他Bundle export的内容。具体可参考3.13 Require-Bundle
3.2.1.28 Require-Capability: osgi.ee; filter:="(&(osgi.ee=AcmeMin)(version=1.1))"
Require-Capability描述了一个Bundle需要其他Bundle提供的capability。具体可参考3.3依赖
3.2.2自定义Header
manifest有一个极好的空间用来提供Bundle相关的元数据,这规则对于OSGi联盟和其他组织都有效。由于历史原因,OSGi联盟声明了默认namespace指定OSGi header,如Bundle、Import、Export等。其他组织如果想使用这些header信息,为了避免与OSGi联盟定义的头信息冲突,可以采用自定义头前缀x-,例如x-LazyStart。
OSGi联盟外部组织可以在OSGi命名空间中请求头信息,OSGi联盟注册的name可以参考文献[16] OSGi Header Namespace Registry.
3.2.3 Header值语法
每一个Manifest header信息都有自己的语法,语法定义在参考文献[11] W3C EBNF。接下来的部分定义了一些常用的规则。
3.2.4 通用Header语法
许多Manifest header都有一个通用的语法,语法表示式如下:
header ::= clause ( ’,’ clause ) *
clause ::= path ( ’;’ path ) *
( ’;’ parameter ) * // 具体参考 1.3.2
参数可以是指令或者属性。一般指令在框架中用于潜在语义支持,属性用于匹配和比较。
3.2.5 版本(Version)
version规范在很多地方都会用到,version说明采用以下的结构:
version ::=
major( '.' minor ( '.' micro ( '.' qualifier )? )? )?
major ::= number // 具体参考 1.3.2
minor ::= number
micro ::= number
qualifier ::= ( alphanum | ’_’ | '-' )+
version内容不能有任何空白,默认值是0.0.0。version对应的API参考10.1.32 Version calss
3.2.6 版本范围
版本范围采用数值区间的方式描述,具体可以参考文献[12] Mathematical Convention for Interval Notation.版本范围的语法如下:
version-range ::= interval | atleast
interval ::= ( '[' | '(' ) floor ',' ceiling ( ']' | ')' )
atleast ::= version
floor ::= version
ceiling ::= version
如果version的值只有一个,那么这个表示范围区间[version,∞)。无版本信息,表示区间值为[0.0.0,∞)。
注意在表示版本范围的时候,用双引号包含 版本开区间,用逗号分隔版本。例如,
Import-Package: com.acme.foo;version="[1.23, 2)", «
com.acme.bar;version="[4.0, 5.0)"
在下表中,展示了每个范围区间用法,x表示可以在区间中使用的值:
如:
[1.2.3, 4.5.6) 1.2.3 <= x < 4.5.6
[1.2.3, 4.5.6] 1.2.3 <= x <= 4.5.6
(1.2.3, 4.5.6) 1.2.3 < x < 4.5.6
(1.2.3, 4.5.6] 1.2.3 < x <= 4.5.6
1.2.3 1.2.3 <= x
版本范围的API支持参考 VersionRange class
3.2.7 Filter语法
OSGi规范中广泛使用了filter表示式。filter表达式为约束提供了简洁的表达方式。filter字符串过滤语法基于LDAP查找过滤器语法,具体参考文献[5] A String Representation of LDAP Search Filters. 需要注意的式LDAP查找过滤器的字符串表达语法已经由RFC 2254代替了RFC 1960,只是添加了扩展匹配。但是OSGi框架的API不提供这个语法支持。
一个LDAP查找过滤器使用前缀形式的字符串表示,采用如下的语法定义:
filter ::= ’(’ filter-comp ’)’
filter-comp ::= and | or | not | operation
and ::= ’&’ filter-list
or ::= ’|’ filter-list
not ::= ’!’ filter
filter-list ::= filter | filter filter-list
operation ::= simple | present | substring
simple ::= attr filter-type value
filter-type ::= equal | approx | greater-eq | less-eq
equal ::= ’=’
approx ::= ’~=’
greater-eq ::= ’>=’
less-eq ::= ’<=’
present ::= attr ’=*’
substring ::= attr ’=’ initial any final
initial ::= () | value
any ::= ’*’ star-value
star-value ::= () | value ’*’ star-value
final ::= () | value
value ::= <see text>
attr ::= <see text>
attr的内容表示用key或者name。属性名称是不区分大小写的,即cn和CN是指同一个属性。attr中不能有这个字符:'=', '\>', '\<', '~', '(' 或 ')',属性名称中可以有空格,但是开始和末尾的空格会被忽略。
value通过和过滤属性的值进行比较而得到的部分值。
如果value中间包含字符(’\’ \u005C), ('*' \u002A), 左括号(’(’ \u0028)或者右括号(')' \u0029)中的任何一个,那么必须使用反斜杠进行转义处理。在value中,空格是有含义的,空格在Character.isWhiteSpace()中定义。
虽然substring和现有产品中都可以生成attr=*结构,但是这种结构只是用于表示一个现有过滤器。
substring只能是String、String[]的属性,在其他情况下,结果一定是错误的。
近似计算filter类型(’~=’)具体实现由framework实现,但是至少要忽略大小写和空格,这个功能需要使用探测法和其他一些智能逼近的代码实现。
filter求值之后,会对具体的属性值和filter的值进行比较。
对于值的比较不是直接进行的,字符串的比较和数字比较不一样,value的比较属于多值字符串比较。
Property必须是字符串类型,object class中的property值定义了比较类型,这些属性值需要使用如下类型:
type ::= scalar | collection | array
scalar ::= String | Integer | Long | Float
| Double | Byte | Short
| Character | Boolean
primitive ::= int | long | float | double | byte
| short | char | boolean
array ::= <Array of primitive>
| <Array of scalar>
collection ::= <Collection of scalar>
下面的规则用于进行比较:
- String – 使用字符串进行比较
- Integer, Long, Float, Double, Byte, Short, Character objects and primitives –利用数字进行比较,这个值比较前应该先去除多余的空格。
- Boolean object –利用Boolean.valueOf(value).booleanValue()定义的值进行比较。在使用valueOf方法前应先去除value中多余的空格。
- Array 或者 Collection–根据保存内容的具体类型进行比较,内容可能有多种类型或者为null。如果以前保存不是上述类型,就会创建一个对应的实例化对象,然后再根据下述顺序进行比较:
- 通过public static type valueOf(String value)来比较。
- 实例化只带一个String参数的构造函数对象来进行比较 如果构造函数中的任何一个方法有效,那么Framework必须通过带String参数构造函数来实例化一个临时对象。如果构造函数和函数不能直接访问,那么将使用setAccessible方法打开访问权限。
Object对象比较使用如下比较属性的方式进行:
- Comparable对象–通过Comparable接口实现进行比较
- 其他对象–使用equal方式比较
如果上面没有一条规则可以对应,那么比较结果为false。
如果一个属性具有多个值,那么filter只要满足其中一个值即可成立,例如:
Dictionary dict = new Hashtable();
dict.put( "cn", new String[] { "a", "b", "c" } );
dict可以被filter(cn=a)或者(cn=b)匹配满足。
Service属性定义通常使用基础类型、collection类型、array类型。在这些情况下,使用简单的+放到type后面,例如String+,表示一个String,或者String[],或者Collection<String>。
filter支持的API可以参考FrameworkUtil.createFilter(String)或者BundleContext.createFilter(String)。