Gemini <mark>BluePrint</mark>介绍

 由  yiqpku 发布

1. 背景介绍

1.1. OSGi应用中的问题

OSGi框架定义了一个基于java的动态模型系统,为系统的开发和维护带来了很多的便利,如bundle热插拨、bundle生命周期管理、面向服务开发等等。但是OSGi框架的推广也面临了大多数新技术引入时遇到的问题:已有系统的迁移问题。当前不少IT公司在使用JAVA EE进行企业级系统开发,而OSGi规范暴露的编程模型同JAVA EE存在很大的冲突,OSGi与JAVA EE容器管理各类资源的机理也存在巨大差异。 如果想要把企业原有的大量JAVA EE工程直接转换到OSGi环境,需要实现特定接口BundleActivator、服务的注册和调用需要使用硬编码。这样的话,将花费大量的人力物力,成本太高,而且系统的正确性和可维护性都将大大降低。
这时肯定有人会说,既然那么麻烦,不用已有的基于JAVA EE开发的软件不就可以了。这样的话,确实省了软件框架转换带来的工作量,但是已有的软件资源就无法被复用。当你利用OSGi开发新系统,所有模块都必须重新开发,这时的增加的工作量不见得就比较少。这样是不是没有办法了?答案当然是否定,下面介绍的Spring DM就可以很好地解决这个问题。

1.2. Spring Dynamic Modules的引入

要讲Spring DM就不能不提到Spring,想必使用过JAVA EE的程序员都或多或少都了解一些关于Spring的知识。这里引用《Spring in Action》里面的定义:Spring是一种轻量容器式框架,能够利用简单的Java对象创建企业级组件。通过使用依赖注入和AOP,Spring能够生成松耦合的代码,让老式普通的Java对象具有了以前EJB所独有的功能。
Spring能够生成松耦合的代码,这正是我们所需要的。通过Spring Dynamic Modules,我们在引入OSGi框架时就不必把OSGi的API硬编码到原有的代码中 而只需要创建一个Spring DM的配置文件,这个配置文件跟一般的Spring配置文件基本一样,只是引入了特定的OSGi标签,用来嵌入OSGi API提供功能,比如OSGi服务的声明和引用等。
具体来说对于企业应用, Spring Dynamic Modules 和 OSGi platform 结合在一起能够:

  • 更好地将应用逻辑分割成模块,模块在运行时有强制的模块边界。
  • 能够同时部署多个版本的模块(或者库)。
  • 能够动态地发现和使用系统中其他模块提供的服务。
  • 能够在运行时系统动态地安装、更新、卸载模块。
  • 可以使用Spring框架实例化,配置,组装,装饰组件模块。
  • 为企业级开发者提供了一个简单又熟悉,同时又能利用OSGi平台的功能开发模型。

1.3. Gemini blueprint的历史

看到这里有些读者可能会感到困惑,不是介绍gemini blueprint吗?前面都在介绍Spring Dynamic Modules。接下来开始讲本文的主角Gemini blueprint,看完它的历史介绍,你就明白为什么了。

Gemini Blueprint工程作为Spring OSGi工程的一部分,在2006年底启动;之后它被更名为大家熟知的Spring Dynamic Modules。
Gemini Blueprint和Spring Dynamic Modules共享许多共同的目标:

  • 更好的OSGi平台
  • 更先进的IOC( Inversion of Control)容器功能
  • 轻量级、透明,基于POJO的编程模型

Spring DM结合了Spring框架提供的强大模型与OSGi平台的动态和模块化功能。这个组合被证明是如此的成功和流行,以至于OSGi联盟决定通过OSGi Service Blueprint标准化这个编程模型,并且作为OSGi Compendium Services 4.2版本中的一部分。
在2009年年底,Spring DM 通过Gemini 将项目建议书移交给Eclipse项目组。
上面这部分介绍来自Gemini Blueprint的官方主页。简单来说,首先OSGi框架被开发出来,但是大家在使用时发现存在一些问题,比如前面提到的代码迁移问题,就有人想是不是能够利用已有的Spring技术来解决,因此Spring Dynamic Modules应运而生。但是此时Spring Dynamic Modules只是一项独立的技术,并不属于OSGi框架,就像TCP/IP协议一样;实践中好用的技术往往就成了事实的标准,在新的OSGi企业标准中将Spring Dynamic Modules对应的技术吸收进来,称为Blueprint规范。而Spring Dynamic Modules通过Gemini 项目建议书移交给Eclipse项目组,因此就改名为Gemini Blueprint。

2.使用说明

2.1 OSGi 4.2 blueprint 容器

基于Gemini Blueprint编程模型,OSGi联盟在OSGi 4.2中引入了Blueprint容器规范(作为Compendium Service 的一部分)。Gemini Blueprint 2.0 作为Blueprint的参考实现,即规范的官方完整实现。
因此,如果用户熟悉Gemini Blueprint,将会发现Blueprint很熟悉,反之亦然。事实上,我们建议将Blueprint规范作为该文档的一个补充。一般来说,除非特别提到,Gemini Blueprint 2.x和Blueprint行为应该是一致的。
由于Eclipse Gemini Blueprint在同一个应用中既支持Spring DM 1.x 声明也支持Blueprint的声明,所以现有或者新的用户可以自由地混搭他们想要的编程模型。也就是说可以在相同的配置文件中使用两者的命名空间声明beans。
关于Blueprint的特殊行为可以参照OSGi 4.2 Compendium规范的121章节。

2.1.1 Blueprint环境需求

Blueprint容器规范是OSGi 4.2发行版的一部分,同时也依赖它的API。因此,为了使用Blueprint,你必须使用兼容OSGi 4.2的平台作为运行时环境。Gemini Blueprint本身只需要OSGi 4.0框架,所以如果没有OSGi 4.2,你可以降级使用它的基于Spring/Gemini Blueprint的功能,不过Blueprint模型部分就不能使用了。
注意:使用OSGi 4.2版本之前的版本,Gemini Blueprint将自动关闭Blueprint功能,它将使用类似下面的日志通知用户

Pre-4.2 OSGi platform detected; disabling Blueprint Container functionality

2.1.2 Blueprint和Gemini Blueprint的区别

Gemini Blueprint 1.x和Blueprint在功能和配置方面有许多相同之处。考虑到Spring DM是Blueprint规范的基础,这就不足为奇了。除了全面支持Blueprint 配置模式,DM 2.x也通过提供允许Blueprint规范行为的选项增强了它自身的声明。

2.1.3 XML声明

Spring和Blueprint的大多数声明时类似的。使用Spring命名空间机制,相同的配置文件可以包含Spring、Gemini Blueprint、Blueprint和其他的命名空间。下面的表列出了Spring命名空间中常用的和标准的元素和属性,以及在Blueprint命名空间中对应的的表示。
Table XML Configuration Differences

Element/Attribute Gemini Blueprint Blueprint
Namespace Declaration http://www.springframework.org/schema/beans or http://www.springframework.org/schema/osgi http://www.osgi.org/xmlns/blueprint/v1.0.0
Root Element <beans> <blueprint>
Default Lazy default-lazy default-activation
Default Init Method default-init-method -
Default Destroy Method default-destroy-method -
Default Autowire Strategy default-autowire, default-autowire-candidates -
Root Element <beans> <blueprint>
Bean ID id id
Bean Name/Alias name/ -
Bean Class class class
Bean Scope Name scope scope
Built-in Scopes singleton, prototype, request, session, bundle singleton, prototype
Lazy Initialization Name/Values lazy-init=true/false activation=lazy/eager
Depends depends-on depends-on
Init Method init-method init-method
Destroy Method destroy-method destroy-method
Factory Method factory-method factory-method
Factory Bean factory-bean factory-ref
Bean Inheritance parent -
Autowire Strategy autowire, autowire-candidate -
Constructor <constructor-arg> <argument>
Property <property> <property>
Value <value> <value>
Service Exporter <service> <service>
Service Importer <reference> <reference>

至于XML配置,由于Gemini Blueprint是将Blueprint配置翻译成Spring元数据,所以你可以只依赖Spring来获得超越Blueprint容器的功能。例如,你可以使用Blueprint配置一个Bean,同时在同一个实例中使用标注来进行字段的注入。

2.1.5 使用Blueprint

不需要额外的包或者步骤来启动Gemini Blueprint中包含的Blueprint功能。它是被内建到核心的,事实上,Blueprint APIs是由Gemini Blueprint核心导出的。你可以简单地安装和启动Gemini Blueprint jars (io, core, extender)和它的依赖(namely Spring and slf4j),Gemini Blueprint将会自动侦测运行时的环境和启动包的类型。

2.2. Bundles和 Application Contexts

OSGi 中部署和模块化的单元是bundle。Bundle在OSGi运行时中处于三个稳定状态installed、resolved、active)之一。Bundle可以把服务(Objects)导出到OSGi service registry。这样的话使得其他Bundle可以找到并使用这些服务。Bundles也可以导出Java 包,使得其他bundle可以导入。

在Spring模块化最重要的单元是一个 application context。它包含一些beans(由Spring application context管理的对象)。Application contexts可以分层配置,子application context 可以看见父application context 定义的bean,反过来则不行。Spring concepts of exporters and factory beans可以将beans的引用导出到application context外面,并把引用注入到application context外部定义的服务中。

OSGi bundle和Spring application context本身就有密切的联系。通过Gemini Blueprint一个active bundle可以包含一个Spring application context,其负责bundle中bean对象的实例化、配置、继承和修饰。其中的一些Bean可以作为OSGi服务被其他bundle访问;bundle中的beans也可以被透明地注入对OSGi服务的引用。

2.2主要描述bundle和它们的 application contexts之间的生命周期关系。这是在OSGi环境中Gemini Blueprint根据运行时发生的事件所引入的。

2.2.1. The Gemini Blueprint Extender Bundle

负责侦测Spring驱动的bundle并初始化它们的application context是由Gemini Blueprint extender这个组件来完成的。对于Spring web应用来说它的作用和ContextLoaderListener是相同的。一旦extender bundle被安装和启动之后,它会查找那些已经处在ACTIVE 状态的Spring驱动的bundles,并为它们创建application context。另外,它会监听bundle启动事件,并为后续启动的Spring驱动的bundle创建一个application context。extender 跟踪它管理的bundle的生命周期,当bundle被停止之后自动为其消除上下文。当extender bundle自己停止的时候,它基于服务的依赖关系自动关闭它所管理的所有上下文。extender bundle的符号名称为org.eclipse.gemini.blueprint.extender。

2.2.2. Application Context Creation

一旦启动之后,extender 会分析已经启动的bundle并且跟踪将启动的任何新bundle。一旦有一个Blueprint 或者Gemini Blueprint配置被侦测到,extender 将会在另一个线程以异步方式为其创建一个application context,这个线程启动bundle(或者发送STARTED 事件)。这个方式遵从OSGi规范的建议,确保了快速启动OSGi服务平台,互相依赖服务的bundle启动时不会发生死锁(互相等待),如下图所示:

http://assets.osgi.com.cn/article/7289212/blueprint1.png

extender 只考虑已经成功启动的bundle,也就是处于ACTIVE 状态的bundle;处于其他状态的bundle将会被忽略掉。因此一个Spring驱动的、Blueprint配置的和bundle拥有的application context是在它完全启动之后被创建的。可以强制同步/序列化地为启动的bundle创建application contexts,如何设置可以参考 Section 8.1, “Bundle Format And Manifest Headers”

假如application context因为某些原因创建失败,造成失败的原因将被记录下来。Bundle仍将处在ACTIVE 状态,application context的生命周期不会以任何方式影响bundle的生命周期。不过相对的由于context失败了,与其相关的功能也就不能使用了。例如在这种情况下将不会有服务被从application context导出到service registry。

2.2.2.1. Mandatory Service Dependencies

假如一个application context对特定导入的OSGi服务声明强制可访问,那么application context的创建将被阻塞直到强制的依赖被满足,也即在 OSGi service registry中对应的服务可以被访问。在实践中,对于使用Gemini Blueprint 构建的大多数企业应用,一旦平台和安装的bundle都被启动之后,可访问的服务和bundle将会处在一个稳定的状态。也就是说,等待强制依赖的行为只是确保了bundle A和 B可以以任何顺序被启动,其中bundle A依赖bundle B导出的服务。

对于强制依赖满足设置了超时时间,缺省超时时间被设置为五分钟,这个值可以通过timeout指令设置。

Blueprint 使用者可以通过设置Bundle-SymbolicName中声明的blueprint.timeout属性达到同样的效果。

可以改变application context创建语义,使得假如所有的强制服务在启动之后没有立即可访问,则application context创建失败。注意不管何种设置, application context创建的失败将不会影响bundle的状态。

2.2.2.2. Application Context Service Publication

一旦bundle的application context被创建完成 application context对象将自动导出成可以通过OSGi Service Registry访问的服务。context 在接口org.springframework.context.ApplicationContext 下发布。发布的服务拥有一个服务属性叫 org.springframework.context.service.name,它的值被设置成拥有这个 application context的bundle的符号名。如果是一个Blueprint bundle容器将在org.osgi.service.blueprint.container.BlueprintContainer 下发布,bundle的符号名将在 osgi.blueprint.container.symbolicname属性下发布。

可以使用bundle manifest中的指令阻止application context的发布。具体可以参考Section 8.1, “Bundle Format And Manifest Headers”。

注意:application context被发布成一个服务主要是为了测试和管理的方便。在运行时访问context 对象和调用getBean() 或者相似的方法是不推荐的。访问其他application context中定义的bean更好的方法是将bean作为一个服务从所定义的上下文中导出,然后在需要访问这个服务的上下文中导入服务的引用。这种方式经由service registry确保了一个bean只看到兼容版本的服务,利于OSGi平台的动态性

2.2.3. Bundle Lifecycle

OSGi是一个动态的平台:bundle可以在运行时的任何时候被installed, started, updated, stopped, 和 uninstalled。

当一个激活的bundle被停止,它活动时期导出的所有服务将被自动注销,bundle 变回resolved 状态。一个停止的bundle必须释放它获取的所有资源,并停止所有线程。一个停止的bundle导出的包对于其他bundle仍然是可访问的。

一个处于resolved 状态的bundle可以被卸载:卸载bundle导出的包对于导入它们的bundle来说仍然是可访问的(对于后来安装的bundle则不可以)。一个处在resolved 状态的bundle也可以被更新。更新过程将同一个bundle从一个版本迁移到另一个版本。

最后,理所当然一个resolved 的bundle可以被启动,这将使它迁移到激活状态。

下图展示了bundle的状态以及它们的转换:

http://assets.osgi.com.cn/article/7289212/blueprint2.png

OSGi PackageAdmin refreshPackages 操作更新整个OSGi 框架或 已安装bundle一个给定子集的包。在更新时,受影响bundle的application context将被停止并重启。在refreshPackages 操作完成后,已更新bundle的旧版本所导出的包或者被卸载bundle所导出的包都将不可访问。具体细节参照OSGi规范。

当一个Spring-powered或者Blueprint bundle被停止,为它创建的application context将被自动销毁。Bundle所导出的所有服务都将被注销(从service registry删除)

如果一个Spring-powered bundle 被停止后又被重启, 将会为它分配一个新的application context。

2.2.4. The Resource Abstraction

Spring 框架定义了一个资源抽象用来在application context中加载资源。所有的资源加载都是通过与application context关联的org.springframework.core.io.ResourceLoader完成。org.springframework.core.io.ResourceLoader对于希望以编程方式加载资源的bean来说也是可以访问的。资源路径有特定的前缀 – ,比如classpath: - ,其在所有application context类型中被同样对待(比如web application context和基于classpath的application contexts。相关的资源路径基于被创建的application contexts的类型进行不同的解释。这使得很容易在最终部署的环境上集成测试。

OSGi 4.0.x规范定义了资源可以被加载的三个不同空间。Gemini Blueprint通过专门的OSGi-specific application context和专门的前缀支持所有的空间:

OSGi resource search strategies:

Feature Gemini Blueprint Blueprint

Object Instantiation

Constructor Instantiation Y Y
Static Factory Instantiation Y Y
Instance Factory Instantiation Y Y

Dependency Injection

Constructor Injection Y Y
Setter Injection Y Y
Field Injection Y N
Method Injection Y N
Arbitrary Method Injection Y N
Autowiring Y N

Component Lifecycle

Lazy Initialization Y Y
Bean Scopes Y Y
Custom Bean Scopes Y N
Built-in Callbacks Y N
Custom Callbacks Y Y
Initialization Processing Y N

注意:如果没有指定前缀,bundle空间(osgibundle:)将被使用。

注意:由于OSGi动态特性,一个bundle classpath在它的生命周期里可以被改变(例如使用动态导入)。在基于运行时环境或者目标平台的模式匹配时可能导致不同的classpath 资源返回。

所有常用的Spring resource 前缀都是被支持的比如file:和http:以及模式匹配通配符。使用这样的前缀可以从任何地方加载资源,在resource-loading bundle或者它附属的片段中没有被严格地定义。

OSGi平台可以为将访问的bundle内容定义独自的前缀。例如:Equinox 定义了bundleresource: 和bundlentry: 前缀。这些特定平台的前缀可能被Gemini Blueprint使用,这需要自己在特定的OSGi实现中进行尝试。

2.2.5. Bundle Scope

Gemini Blueprint引入了一个新的bean域bundle。这个域与作为OSGi服务导出的bean相关,可以被描述为每个bundle一个实例。导出为OSGi服务的bean如果拥有bundle域,对于通过 OSGi service registry导入服务的每一个bundle来说将会创建一个不同的实例。相同bundle(不管是否通过Gemini Blueprint定义)的使用者将看到相同的bean实例。当一个bundle停止导入bundle(因为任何原因),这个bean实例将会被处理。对于bundle声明,一个bundle域bean的行为就像单例(也就是所有的bundle只有一个实例,包括正在声明的那个),这个生命周期约定与 org.osgi.framework.ServiceFactory接口相似。

重要:bundle域只有在声明bean是通过 OSGi service registry被使用时才有意义。也就是只有当bean作为服务导出,并被其他bundle作为服务需要或释放时实例才会相应的创建和销毁。

2.2.6. Accessing the BundleContext

通常情况在使用 Gemini Blueprint支持是不需要依赖OSGi,假如你确实需要在你的bundle中使用OSGi BundleContext 对象,Spring将会使其变得很容易。被Spring extender 创建的OSGi application context 将自动包含名字为bundleContext 类型为BundleContext 的bean。你可以通过by-name或者by-type的方式将该bean 的引用注入到application context中的任一个bean。另外Gemini Blueprint定义了接口org.eclipse.gemini.blueprint.context.BundleContextAware:

public interface BundleContextAware {
public void setBundleContext(BundleContext context);
}

任何实现这个接口的bean如果是使用Spring进行配置,则将会被注入bundle context的引用。假如你想在bundle中使用这个功能,记得在你的bundle manifest 中导入包org.eclipse.gemini.blueprint.context,否则,这个接口对于你的bundle来说是不可见的。

2.2.7. Application Context Destruction

application context被绑定到它赖以生存的bundle。假如声明的bundle被关掉(因为任何原因),application context也将被关掉,所有导出的服务都将被注销,所有服务的导入都将无效。与应用的创建相反,application context 的销毁是以同步的方式进行,由停止bundle的同一个线程执行。这是需要的,因为一旦被停止,bundle将不再被使用(甚至包括加载类),不能够保证application context正常执行后关闭。

http://assets.osgi.com.cn/article/7289212/blueprint3.png

注意:bundle可以被单独关闭或者作为更大事件的一部分关闭,比如整个OSGi平台关掉。在这种情况下,当 extender bundle 被关掉,application contexts将在基于服务依赖关系的可控方式下被关掉。具体细节看下一部分。

2.2.8. Stopping the Extender Bundle

假如extender bundle被停止,所有extender创建的application contexts也将被销毁。这边列出的算法是在Blueprint规范中定义的(section 121.3.11)。Application contexts将以下列顺序被关闭:
● 没有导出任何服务或者导出的服务没有被立即引用,将会以bundle id的逆序被关闭。(最近安装的bundle将首先关闭它的application contexts )。
● 关闭第一步里面的application contexts 时将会释放它原来拥有的引用,使得有更多的application contexts 可以被关闭。假如这样,重复步骤一。
● 假如没有更多活跃的application contexts,我们的任务就已经完毕。假如还有活跃的application contexts,则肯定有循环依赖引用。可以通过决定每个context中导出的最高等级的服务来打破循环:在这个集合中拥有最低等级服务的bundle将被关掉,重复步骤一。

Shutdown algorithm change in 2.x
The shutdown algorithm implementation in Gemini Blueprint 1.0 has been revised to be better aligned with the Blueprint Container spec. Namely, the previous implementation performed ordering in only one pass while the latter performs multiple steps to accommodate the service changes in the OSGi space. Users should not discover any differences at runtime however, if that's not the case, please let us know.   

总结:文章首先介绍了blueprint的历史背景,让读者对blueprint的来源有一个初步的了解。接着介绍了对blueprint使用者来说比较重要的容器和应用程序上下文,这部分来自官方文档第六章和第七章。如果想要进一步学习blueprint的相关知识,可以参考官方使用文档,地址是:http://www.eclipse.org/gemini/blueprint/documentation/reference/1.0.1.RELEASE/html/index.html


wmz 2015-06-08 19:56

http://osgi.jxtech.net 平台在OSGI方面运用相当灵活,把前端代码、后台业务逻辑、甚至建表SQL语句都集成在一个Bundle中,实在是太方便了。

顶(0) 踩(0) 回复

lexiyao 2015-02-10 18:33

写的不错

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