OSGi Service Platform V4.2 新特性
2010-02-24 00:00:00 来源:WEB开发网经过将近两年的准备,OSGi 联盟终于在 2009 年 9 月发布了最新版的 OSGi Service Platform V4.2 规范。其中,新的 Core Specification V4.2 规范中增加了 Framework launching,Service Hooks 等概念,并且对 Conditional Permission Admin 标准进行了改进和补充以方便安全管理和配置。此外,在企业专家组的大力推动下,新的 Service Compendium V4.2 规范中引入了 Blueprint Services, Remote Services, Bundle Tracker 等用以支持企业级应用的新标准,从而吹响了 OSGi Service Platform 要大踏步向着企业级市场前进的号角。
OSGi Service Platform 技术背景
近两年,OSGi 技术的发展势头十分的迅猛,引起了业界越来越多的厂商的关注。然而,事实上 OSGi 并不是一个新的名词,到今年它已走过了整整 10 年的发展历程。OSGi 联盟成立于 1999 年,是一个非盈利的国际组织,旨在建立一个开放的服务规范,为通过网络向设备提供服务建立开放的标准,是开放业务网关的发起者。起初 OSGi 技术只是关注于嵌入式领域,诸如机顶盒、服务网关、手机等应用环境,2003 年,BMW 就基于 OSGi V3.0 规范构建其智能汽车影音系统。随着时间的发展,OSGi 的诸多优秀特性,如动态性,模块性,可扩展性,被更多的开发者所逐渐认识和欣赏,从而应用到桌面程序的设计和开发中来。Eclipse 从 V3.0 M5 版本开始,设计了 Equinox 内核,使用 OSGi 技术帮助其进行类载入,大大提升了启动速度,实际上,Equinox 就是 OSGi Framework 的一个实现。与此同时,OSGi 技术的影响也延伸到了 Java 社区,JSR-232 说明 OSGi 技术已经初开始被 Java ME 家族所认知,而 JSR-291 更是将 OSGi 技术的触角扩展到了 Java SE 和 Java EE 的范畴。OSGi 联盟的成员数量已经从最开始的几个增长到现在的 100 多个,很多世界著名的 IT 公司都加入到了这个阵营中来,如 IBM,Oracle,SAP,Red Hat,SpringSource 等等,它们的很多产品都宣布支持或使用了 OSGi 技术,如 WebSphere,Weblogic,JBOSS,Spring DM 等等,这从一个侧面说明了 OSGi 技术在企业级市场大有可为。因此,OSGi 的范畴已经不是其原来的字面意义 (Open Service Gateway initiative) 所能涵盖,如今,OSGi 联盟给出的定义是“A dynamic module system for Java”,即一个动态的 Java 模块系统。
今年 9 月,OSGi 联盟发布了最新版的 V4.2 标准,这相距 V4.1 的发布时间已近两年,在这两年里,OSGi 联盟成员提出了很多新的需求,因此新的 OSGi Core Specification 与 Service Compendium Specification 增加了很多新的内容,值得一提的是,在企业专家组 (Enterprise Expert Group, EEG) 的努力下,很多新的特性都是针对企业级应用或应用服务器领域所设计,下面就让我们来一一了解一下这些新特性的问题背景与解决方案。
Core Specification V4.2 新特性
Framework Launching
即 RFC 132,为 OSGi Framework 实现定义了统一的启动方法和接口。
问题背景:
在 V4.2 以前,不同厂商实现的 OSGi Framework 往往按照自己的习惯定义启动的接口,初始化参数,资源配置方法。比如,当我们想从外部连接 Eclipse Equinox,Apache Felix 等实现时,没有一个统一的方法来完成这种调用。所以,如果基于 OSGi Framework 构建的第三方产品想支持不同的 OSGi Framework 实现,就需要准备多份的启动角本和配置文件。
解决方案:
增加了 Framework Factory 的概念来创建 Framework Bundle 的实例。定义了每一个 OSGi Framework 的实现 jar 包中必须包含在如下路径位置的一个 UTF-8 编码的资源文件:
/META-INF/services/org.osgi.framework.launch.FrameworkFactory
并在这个文件中指出此 Framework 实现中的 FrameworkFactory 实现类的名称。一个 FrameworkFactory 的实现类必须包含一个公有的无参构造函数,并且拥有一个 newFramework 方法,这个方法有一个 Map 类型的参数用以表示配置信息。新的标准中预定义了配置参数名称,如 org.osgi.framework.bootdelegation,org.osgi.framework.bundle.parent 等。清单 1 中的代码演示了启动一个 Framework 的过程。
清单 1. 启动 Framework 的过程
void launch(String factoryName, File[] bundles) throws Exception {
Map p = new HashMap();
p.put("org.osgi.framework.storage", System.getProperties("user.home")
+ File.separator+"osgi");
FrameworkFactory factory =
(FrameworkFactory) Class.forName(factoryName).newInstance();
Framework framework = factory.newFramework(p);
framework.init();
BundleContext context = framework.getBundleContext();
for (File bundle : bundles)
context.installBundle(bundle.toURL().toString());
framework.start();
framework.waitForStop();
}
另外,在 V4.1 版本的基础上,增加了初始化过程 (Initialization) 的详细定义,并对启动过程 (Startup) 和关闭过程 (Shutdown) 进行了修订。对 Framework 的生命周期中由 init,start,stop,update 方法所引发的状态改变进行了规约,如图 1 所示。
图 1. 生命周期中状态的改变
Bundle License
即 RFC 125, 为 MANIFEST 定义了一个新的头:“Bundle-License”。
问题背景:
开源社区存在着数以百计的 License,比较著名的如 Mozilla Public License, Eclipse Public
License, Apache Software License, GPL 等。一些 OSGi 的 Bundle 中常常会包含开源社区的代码,或者这个 Bundle 本身就是由开源社会所发布的,因此,Lincese 文件在这类 Bundle 中存放的形式和位置也不尽相同,如有的存放在名为“LICENSE”的文件中,有的还放在特定的目录里。当前,由于现在开源社会的影响力越来越大,很多商业公司也会把开源 Bundle 引入到自己的基于 OSGi 的产品中去,当律师在检查这类产品时,处理 Bundle 中的 Lincese 就变成了一个很棘手的问题,即无法使用一个类似于批处理的工具去方便的检测各个 Bundle 的 Lincese 定义。
解决方案:
在新标准中增加了一个名为“Bundle-License”的头。它有着如清单 2 所示的形式定义。
清单 2. Bundle-License 的形式定义
Bundle-License ::= “<<EXTERNAL>>” | ( license ( ‘ , ’ license ) * )
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error: The previous line is longer than the max of 90 characters ---------|
license ::= name ( ‘ ; ’ license-attr ) *
license-attr ::= description | link
description ::= ‘ description ’ ‘ = ’ string
link ::= ‘ link ’ ‘ = ’ <url>
上述的意思是,一个 Bundle-License 的值要么为“<<EXTERNAL>>”,要么由一个或多个 license 组成,每个 license 由逗号分隔。license 由一个唯一的名称 (name) 标识,同时也可以附加一个或多个描述信息或链接信息。下面我们就看看这些属性的具体说明,如表 1 所示:
表 1. Bundle-License 形式定义中的属性说明
属性 | 说明 |
“<<EXTERNAL>>” | Bundle-License 的默认值,并不表示这个 Bundle 没有 License。而是说它的 License 由外部提供。 |
name | 表示可唯一标识一个 License 的名称,一般为定义这个 License 的主要的公认的 URL。对于大部份开源 Bundle,这个 URL 可以从 http://www.opensource.org/licenses/alphabetical得到。 |
description | 可选的属性。用一个字符串表示的简短描述信息。 |
link | 可选的属性。大段的描述可以通过链接到一个 URL 来引用,也可以链接到当前 Bundle 中的一个文件。如果未写这个属性,则默认等于 name 中提供的 URL。 |
下面给出一些简单的例子,如清单 3 所示。
清单 3. Bundle-License 的例子
Bundle-License: <<EXTERNAL>>
Bundle-License: http://www.opensource.org/licenses/apache2.0.php;
description=Apache License Version 2.0
Bundle-License: http://www.opensource.org/licenses/apache2.0.php,
http://www.opensource.org/licenses/eclipse-1.0.php
Service Hooks
即 RFC 126,为 Bundle 提供了勾入 Service 层相关操作的方法。
问题背景:
在以前的标准中,Service 层没有为 Bundle 提供观察和控制 Service Registry 中相关操作的方法,一些基于 OSGi Framework 的系统开发者,如应用服务器开发者,无法限定一个事件可以被哪些 Bundle 得到,并且也无法限定一个 Bundle 可以得到哪些服务引用。这不利于在 OSGi 平台上对企业级系统的开发。
解决方案:
针对以上需求,在新标准中定义了如下三种类型的“勾子”:
EventHook
一个 Bundle 注册一个接口为 EventHook 的 Service 后,当 Framework 中有如 register,modify,unregister Service 操作时,这个勾子的 event 方法将会被调用。这个调用先于 ServiceEvent 的送出。EventHook 的 event 方法有两个参数,如表 2 所示。
表 2. event 方法参数说明
参数 | 类型 | 说明 |
event | ServiceEvent | 表示将要发送的事件 |
context | Collection | 为一个 BundleContext 对象的集合,表示所有会接收此 ServiceEvent 的 Bundle |
在这个勾子中,如果我们将一些 BundleContext 对象从这个集合中删去的话,那么这些 Bundle 将不会收到此 ServiceEvent。值得注意的是,仅管我们可以从这个集合中删除对象,但是标准要求不能增加对象,否则将会收到 UnsupportedOperationException。
FindHook
一个 Bundle 注册一个接口为 FindHook 的 Service 后,当 Framework 中有如 getServiceReferencet 等操作时,这个勾子的 find 方法将会被调用。这个 find 方法的参数如表 3 所示。
表 3. find 方法参数说明
参数 | 类型 | 说明 |
context | BundleContext | 表示调用 getServiceReference 方法的 BundleContext 对象 |
name | String | 表示打算寻找的 Class 的名称,“null”表示寻找所有 Service |
filter | String | 表示打算使用的 filter |
allServices | boolean | “true”表示传入的 references 参数为 getAllServiceRefereces 的结果 |
references | Collection | 表示最终会返回给最开始调用如 getServiceReference 方法的 Bundle 的 Service Reference 集合 |
通过这个勾子,我们可以把不希望返回的 ServiceReference 过滤掉,也就是说从 references 这个集合中删掉。同前一点一样,我们也不被允许向这个集合中加入对象。
ListenerHook
一个 Bundle 注册一个接口为 ListenerHook 的 Service 后,当 Framework 中有 Service Listener 加入或删除时,会调用这个勾子的 added 或 removed 方法,这两个方法都只有一个参数,如表 4 所示。
表 4. added 和 removed 方法的参数说明
参数 | 类型 | 说明 |
listeners | Collection | 为其内部类 ListenerHook.ListernerInfo 定义的 Listener 集合。表示刚加入 Framework 或刚从 Framework 中删除的一组 Listener。同样,我们也不可以向这个 Collection 增加元素。 |
有一点需要说明的是,对于 added 方法,它在这个 ListenerHook 注册入 Framework 后会立刻被调用,从而得到在这个勾子注册之前 Framework 中已经存在的 Service Listener 对象。
Security Enhancements
即 RFC 120,是对 V4.1 版本中的 Conditional Permission Admin 的补充。
问题背景:
企业级应用服务器的一个共同的特点就是用户应用程序代码与服务器系统代码运行于同一个 Java 虚拟机上。因此,通过将它们进行隔离,以保证服务器能够平稳、安全的运行,在 OSGi 之前的版本中提供了 Conditional Permission Admin 标准。但是,这个标准采用了 Java 2 的安全架构,特别是 Permission 模型,即提供了一个 Policy 文件来定义在何种条件 (Condition) 下有哪些动作被充许 (Allow)。遗憾的是,这种架构中没有一种定义拒绝 (Deny) 的方法。OSGi 联盟应该对此进行补充以简化安全管理,同时还要维护向前的兼容。
解决方案:
新的标准采用的解决方案十分的简洁,它将 Permission 分成了两种类型,即 Allow Permission 和 Deny Permission。具体的说就是,将原来的 Conditional Permission 表格排列成一个有序的列表,在增加了一个新列,表示对这个 Permission 是作 Allow 决定还是 Deny 决定。让我们用一个简单的例子来阐述这种改变。比如 Pepsi 公司有以下需求:
除了 Pepsi 公司,拒绝其它公司对包 com.pepsi.* 的连接。
允许 Coke 公司对 com.pepsi.friends.* 的连接。
所有公司都可以连接其它包。
针对以上需求,我们可以定义一个有序 Conditional Permision 表格,如表 5 所示:
表 5. Conditional Permision 表格实例
名称 (Name) | 条件 (Conditions) | Permissions | 决定 (Decision) |
R1 | Coke 公司 | Package(“com.pepsi.friends.*”) | ALLOW |
R2 | 非 Pepsi 公司 | Package(“com.pepsi.*”) | DENY |
R3 | <Empty> | Package(*) | ALLOW |
进行评估时,需要按照行的顺序进行逐行判定,一旦某行条件和 Permission 得到满足和对应,则返回此行的决定作为结果,比如:
如果 Coke 公司打算连接包 “com.pepsi.friends.foo”,则第一行的条件 “Coke 公司”得到满足,并且 Permission“com.pepsi.friends.*”包含了“com.pepsi.friends.foo”,则返回决定“ALLOW”表示允许。
如果 Coke 公司打算连接包 “com.pepsi.secrect”,则虽然第一行的条件“Coke 公司”得到满足,但 Permission 中不包括“com.pepsi.secrect”包,因此第一条不适用,从而转向第二条。在第二条中,条件“非 Pepsi 公司”得到满足,同时,包“com.pepsi.*”包括了“com.pepsi.secrect”,因此返回决定“DENY”表示拒绝。
由此我们可以看到,这种改变大大简化了安全管理的判定过程,否则,上述逻辑需要定义更多的决定为“ALLOW”的 Permission,只有当所有的都不满足时,才能表示“DENY”决定。
Service Compendium Specification V4.2 新特性
Blueprint Container
即 RFC 124,从某种程度上说,其可以理解为是对 Spring Dynamic Modules 项目的标准化。
问题背景:
在 Java 企业级市场,大多数的应用都基于 Sun 公司的 Java Enterprise Edition,如 JMS, EJB,JPA,JTA,JSF,Servlets,JAS-WS 等相关标准。Java EE 中的组件模型 (Component Model) 为 EJB 技术所提供,但是,由于其复杂性较高,市场反应一直不好。同时,在开源社区方面,SpringSource 推出的 Spring Framework 却因其易用性受到了市场的追捧。在此基础之上,SpringSource 又将目光转向了 OSGi 平台,将其在简化应用集成与配置上的优势与 OSGi 平台模块化与版本管理方面的特点结合起来,开发了 Spring Dynamic Modules(Spring DM) 项目。2007 年,OSGi 企业专家组决定将这个项目引用 OSGi 服务平台,为 OSGi 技术在企业级应用方面施展拳脚奠定基础。
解决方案:
OSGi 企业专家组把 Spring DM 引入的依赖注入 (Dependency Injection) 技术在 OSGi 服务平台的应用方法进行了定义,为 Bundle 提供一个基于 XML 编写的配置文件,位于路径“OSGI-INF/blueprint/”下。在这个配置文件中,定义了 Bundle 中 Bean 对象的初始化方式以及它们之间的关系,同时,也声明了其需要引用的服务对象和打算发布的服务。拥有这样一个配置文件的 Bundle 可以称之为 Blueprint Bundle,它可以被 Blueprint Container 发现并进行相关的操作,如图 2 所示。
图 2. Blueprint Bundle 与 Extender
查看原图(大图)
下面是一个简单的 Blueprint Bundle 的配置文件,如清单 4 所示。
清单 4. Blueprint Bundle 的配置文件实例
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0">
<bean id="hw" class="com.abc.HelloWorld">
<argument value=”Rex”/>
<argument value=”Wang”/>
<property name=”country” value=”China”/>
<property name=”city” value=”Shanghai”/>
</bean>
</blueprint>
其中 HelloWorld 的类定义如清单 5 所示。
清单 5. HelloWorld 的类定义
public class HelloWorld {
private String country;
private String city;
public HelloWorld (String FirstName, String FamilyName){
…
}
public setCountry(String country){
this.country = country;
}
public setCity(String city){
this.city = city;
}
}
因此,上述配置文件的作用相当于
HelloWorld hw = new HelloWorld(“Rex”, “Wang”);
hw.setCountry(“China”);
hw.setCity(“Shanghai”);
在配置文件中,我们也可以对需要的服务进行引用,并且发布自己的服务,比如:
<reference id="helloservice" interface="com.abc.HelloWorld" />
表示引用一个接口为 “com.abc.HelloWorld”的 Service 对象,而:
<service ref="hw" interface="com.abc.HelloWorld" />
表示将对象“hw”发布为接口为“com.abc.HelloWorld”的服务。
你可能注意到,对服务的引用和发布与已有的 Declarative Service 十分相似。但是,Blueprint 提供了更为灵活的动态加载机制,当 Service 所在的 Bundle 是一个 Lazy Bundle 时,这个 Service 可以注册一个 Placeholder 来等待其它应用的发现和引用。此外,基于 POJO 的对象实例化、服务的引用和发布方法可以使你避免在你的应用程序中引入一些 OSGi 平台的 API 代码,如 BundleContext.registerService、BundleContext.getServiceReference 等,这不仅使你的应用可以独立于 OSGi 框架而运行,同时也可以方便的编写 Mock 对象从而进行传统单元测试。
Bundle Tracker
即 RFC 121,与 ServiceTracker 的作用类似,用以简化对 Bundle 状态变化的监测。
问题背景:
以前的版本中缺少一种用来监测 Bundle 状态的方法,一个典型的用例就是 OSGi 的 Extender 模型。我们知道,一个 Extender Bundle 通过对 OSGi Framework 的观察来得到载入或载出 Bundle 的信息,一旦这个 Bundle 是其需要的关注的,则它将会提供相关功能。比如,前述的 Blueprint Container 中就定义了一个 Extender 用以监测 Blueprint Bundle。因此,我们需要定义一种标准的,易用的工具类来简化这项工作,以帮助厂商更好的建立基于 OSGi 的系统或扩充 OSGi Framework 的功能。
解决方案:
定义了一个新的类 BundleTracker 以及一个新的接口 BundleTrackerCustomizer,它们的关系如图 3 所示。
图 3. BundleTracker 的类图
有趣的是,BundleTracker 有着这样一个构造方法:
public BundleTracker(BundleContext context, int stateMask,
BundleTrackerCustomizer customizer)
它的三个参数的意义如表 6。
表 6. BundleTracker 构造方法的参数说明
参数 | 类型 | 说明 |
context | BundleContext | 这个 Tracker 所在 Bundle 的 BundleContext 对象 |
stateMask | int | 用以表示打算监测的状态掩码,即针对处于哪几种状态的 Bundle 进行监测 |
customizer | BundleTrackerCustomizer | 提供用户自定义的 addingBundle,modifiedBundle,removeBundle 方法 |
那么,既然 BundleTracker 实现了 BundleTrackerCustomizer 接口,为什么又要在其构造的时候引入一个新的 BundleTrackerCustomizer 的对象呢?从 API 的描述中对此进行了规约, BundleTracker 类中提供的只是三个方法的默认的实现,只有当构造方法中传入的 BundleTrackerCustomizer 为 null 时,才会去执行。因此,我们也可以用另一种方式来提供自定义的方法,就是创建子类继承 BundleTracker,然后覆盖三个默认方法,并在构造子类时为 BundleTrackerCustomizer 参数传入 null。
只有当 BundleTracker 的 open 方法被调用后,其才开始工作。当 OSGi Framework 中的一个 Bundle 满足 BundleTracker 的监测条件时,会调用 addingBundle 方法,这个方法返回一个 Object 类型的包装 (warpper) 对象,其内容可由用户定义。然后 Bundle 对象与这个包装对象会被保存在 BundleTracker 的一个 map 属性中,当 Bundle 的状态发生改变时或 Bundle 的状态已经不符合监测条件时,会分别调用 modifiedBundle 或 removedBundle 方法,传入的 Object 即为 addingBundle 方法返回的对象。另外,BundleTracker 的实现借助了 Synchronous Bundle Listener,因此,只有发生了改变 Bundle 状态事件,才会对 BundleTracker 的方法进行调用,所以我们可以看到这三个方法中都有一个 BundleEvent 对象作为输入参数。
Remote Services
即 RFC 119,为 OSGi Framework 定义了连接外部资源、服务的方法。
问题背景:
以前的 OSGi 服务平台的设计是建立在一个虚拟机之上的应用。它提供了一个本地的服务注册表 (Service Registry) 来为 Bundle 之间的通信建立桥梁。一个 Bundle 可以向其注册服务,则在同一个 Framework 的其它 Bundle 可以从中引用此服务,从而实现了 SOA 的设计思想。然而,现在的软件系统不仅需要集成本地的服务,同时也要求可以引用远程的服务以满足越来越多的应用需求,并减少开发周期。因此以标准应运而生。
解决方案:
在新的标准中,采用了以下的体系结构实现了对运行于不同平台中服务的远程引用。如图 4 所示。
图 4. Remote Services 所采用的体系结构
查看原图(大图)
Distribution Provider 可以为一个 Framework 中导出的服务创建 Endpoint,也可以为需要导入的服务创建一个 proxy 以连接远程的 Endpoint,并把这个 proxy 注册为一个导入的服务。一个 Framework 可以同时包含多个、不同的 Distribution Provider 实例,从而为这个 Framework 提供不同类型的 Service 导入、导出服务。可以看的出,这个设计与 SCA 的关系十分紧密。
总结
本文对 OSGi Service Platform V4.2 规范所引入的新特性进行了综述,详细介绍了其所解决的问题及满足的需求,并且阐述了其具体的解决方案。此外,OSGi 联盟企业专家组还计划在今年年底前发布一个全新的规范,它包括了如 JNDI,JPA,Transaction,Web Container 等等的相关集成标准,为 OSGi 技术在企业级市场的推广奠定基础,让我们拭目以待。
更多精彩
赞助商链接