Gemini Blueprint参考文档 第8章 打包和部署基于Spring的OSGi应用
第8章 打包和部署基于Spring的OSGi应用
传统的Spring应用要么使用单个应用上下文,要么使用一个父上下文,包含服务层、数据层和领域对象,而子Context包含web层组件。应用上下文通过聚合多个配置文件才可能完整的。
当部署一个应用到OSGi时,更多的原生结构会将应用打包成一组同级bundle(peer)(应用上下文),它们之前通过服务注册表交互。独立的子系统应该打包为独立的bundle或者一组bundle(垂直分割)。一个子系统可能打包到单个bundle中,或按层次(水平分割)分成几个bundle。例如一个简单的web应用可能分成四个模块(bundle):一个web bundle,一个服务层bundle,一个数据层bundle,一个领域模型bundle。这样的应用看起来是这样的:
在这个例子中,数据层bundleIn产生数据层应用上下文,它包含几个内部组件(bean)。其中两个bean作为服务发布到了OSGi服务注册表,可以从应用上下文外部访问的。
服务层bundle产生了服务层应用上下文,它包含几个内部组件(bean)。其中一些组件依赖于数据层服务,则从OSGi服务注册表导入这些服务。还有两个服务层组件作为OSGi服务注册表的服务,可以从外部访问。
Web组件bundle产生了web应用上下文,它包含几个内部组件(bean)。其中一些组件依赖于应用服务,则从OSGi服务注册表中导入这些服务。由于领域模型bundle只提供领域模型类型,而不需要出具任何自己的组件,所以它不与任何应用上下文关联。
8.1.Bundle格式和Manifest头
每一个应用模块都应打包成一个OSGi的bundle。bundle本质上是包含META-INF/MANIFEST.MF文件的jar文件,MANIFEST.MF文件包含了OSGi服务平台能够识别的一系列头信息。参见 OSGi 服务平台核心规范第 3.2节了解细节。一些OSGi实现可能支持零散的jar文件,但是格式是相同的。
Spring extender能识别基于Spring的bundle,当这个bundle启动的时候,Spring extender将会创建与这个bundle相关的应用上下文。下面的一个条件为真:
- bundle的路径包含一个文件夹META-INF/spring,这个文件夹下有一个或多个xml文件。
- META-INF/MANIFEST.MF包含一个清单头Spring-Context。
另外,如果在bundle的manifest中声明了可选的SpringExtender-Version头,那么extender只会识别满足版本约束(Bundle-Version)的bundle。SpringExtender-Version头的只必须遵循OSGi服务平台核心规范第3.2.5节指定的版本范围。
如果缺少Spring-Context头, 那么extender期望META-INF/spring文件夹下的每一个".xml"文件都是合法的Spring配置文件,所有的指令都取默认值,如下所示:
|
小贴士 |
应用上下文就是从这些文件构造的。推荐将应用上下文配置分为至少两个文件,按惯例命名为modulename-context.xml和 modulename-osgi-context.xml。 modulename-context.xml 文件包含普通的bean定义,独立于OSGi。 modulename-osgi-context.xml 文件包含导入和导出OSGi服务的bean定义。它可能用(但是必需的) Gemini Blueprint OSGi schema作为顶级的命名空间,而不是Spring 'beans'命名空间。 |
Spring-Context manifest头信息用于指定可选的配置文件。资源路径是相对的资源路径,解析bundle和附属的fragments中的条目。 当Spring-Context头定义了至少一个配置文件位置,META-INF/spring中的任何文件都被忽略,除非Spring-Context头中直接引用。
Spring-Context头信息的语法是:
Spring-Context-Value ::= context ( ',' context ) *
context ::= path ( ';' path ) * (';' directive) *
这个语法与 OSGi服务平台公共头语义定义是一致的(OSGi服务平台核心规范第3.2.3节)。
例如,manifest条目:
Spring-Context: config/account-data-context.xml, config/account-security-context.xml
表明要用bundle jar文件中的account-data-context.xml和account-security-context.xml两个文件中的配置实例化应用上下文。
许多指令都用于Spring-Context头。这些指令包括:
-
create-asynchronously (false|true): 控制是否异步(默认值)或者同步创建应用上下文。
例如
Spring-Context: *;create-asynchronously:=false
同步创建应用上下文,用META-INF/spring文件下的所有xml文件。
Spring-Context: config/account-data-context.xml;create-asynchrously:=false
用config/account-data-context.xml配置文件同步创建应用上下文。 注意,当同保护创建应用上下文时,创建应用上下文时在OSGi事件线程中,它会阻塞后面的事件传递,直到这个上下文完全初始化完成。如果在同步创建应用上下文时发生错误,那么就产生FrameworkEvent.ERROR事件。这个bundle仍然会变为Active状态。
-
wait-for-dependencies (true|false): 控制应用上下文的创建是否应该等待强制服务依赖满足,或者不等待服务依赖启动。
例如:
Spring-Context: config/osgi-*.xml;wait-for-dependencies:=false
用配置目录下所有匹配"osgi-*.xml"的文件创建应用上下文。上下文创建立即开始,即使依赖不满足。这从本质上意味着强制的服务引用被作为可选项对待-客户端注入的服务对象可能不是注册表中实际的服务。参见 第9.2.2.9节“引用和OSGi服务动态性”了解更多细节。
- timeout (300): 在放弃和应用上下文创建失败之前,等待服务依赖满足的时间。这个设置会被忽略,如果指定了wait-for-dependencies:=false。默认超时时间为5分钟(300秒)。
例如:
Spring-Context: *;timeout:=60
创建应用上下文时,等待强制依赖的时间为1分钟(60秒)。
- publish-context (true|false): 控制应用上下文对象自己是否应该发布到OSGi服务注册表中。默认是会发布这个上下文的。
例如:
Spring-Context: *;publish-context:=false
如果没有Spring-Context manifest条目,或者条目中没有指定值,那么这个指令就取默认值。
8.2.Blueprint Manifest配置比较
下面的表格总结了Eclipse Gemini Blueprint/Spring DM和Blueprint Container之间的manifest配置选项差异:
表8.1. 配置设置区别
选项 |
Gemini Blueprint/Spring DM |
Blueprint |
默认配置位置 |
META-INF/spring |
OSGI-INF/blueprint |
自定义位置头 |
Spring-Context |
Bundle-Blueprint |
属性头 |
Spring-Context |
Bundle-SymbolicName |
异步创建属性 |
create-asynchronously |
- |
启动强制依赖属性 |
wait-for-dependencies |
blueprint.graceperiod |
启动强制超时属性 |
timeout (in s) |
blueprint.timeout (in ms) |
容器服务API发布属性 |
publish-context |
- |
下面的manifest中的配置是等价的:
Bundle-SymbolicName: org.example.account.bundle Spring-Context: config/account-data-context.xml, config/osgi-*.xml; wait-for-dependencies:=true; timeout:=10 |
1、所有Gemini Blueprint和Spring DM特定的属性都位于Spring-Context头 2、超时的单位为秒(s) |
Bundle-SymbolicName: org.example.account.bundle; blueprint.graceperiod:=true; blueprint.timeout:=10000 Blueprint-Bundle: config/account-data-context.xml, config/osgi-*.xml |
1、Blueprint设置在Bundle-SymbolicName和Blueprint-Bundle之间扩展 2、超时的单位为毫秒(ms) |
8.3.Extender配置选项
除了特定bundle的配置,Gemini Blueprint和Spring DM允许配置核心extender通用行为 。当把Spring DM嵌入到管理环境或者当期望bundle范围的功能,这就有用了。为了允许扩展配置,extender依赖OSGifragments来覆盖默认值。 extender查找位于bundle空间的META-INF/spring/extender文件夹下的所有xml文件 ,并聚合到应用上下文中(类型为 OsgiBundleXmlApplicationContext) ,这是内部用于配置。为了覆盖extender的默认设置,从下表中查找合适的bean名字,以合适的方式定义它,然而将它作为spring-osgi-extender.jar的附属fragments,使用:
Fragment-Host: org.eclipse.gemini.blueprint.extender
下面的bean当前可以被extender识别:
表8.2. Extender配置选项
Bean名 |
类型 |
角色 |
默认的行为/值 |
taskExecutor |
TaskExecutor [a] |
创建并运行与每一个bundle有关的Spring应用上下文。task executor负责管理自己的,应用上下文使用的线程池 |
默认使用SimpleAsyncTaskExecutor 这意味着每一个应用上下文都创建一个新的线程。这适用于测试和开发,我们强烈建议在生产环境中使用线程池 |
shutdownTaskExecutor |
TaskExecutor [b] |
销毁与每一个bundle有关的Spring应用上下文。task executor 负责管理自己的,应用上下文使用的线程池 |
默认使用TimerTaskExecutor,这意味着每一个应用上下文以串行方式销毁。由于关闭顺序通常很重要,对于管理环境推荐用默认实现,用一个线程池只执行一个任务(这样上下文按给定的顺序关闭) |
extenderProperties |
java.util.Properties |
定义简单属性,例如上下文优雅关闭的最大时间 |
参加下面的默认值 |
osgiApplicationEventMulticaster |
ApplicationEventMulticaster [c] |
ApplicationEventMultiCaster用于将Gemini Blueprint/Spring DM 事件传播给第三方 |
使用SimpleApplicationEventMulticaster。参见AbstractApplicationContext javadoc了解关于应用上下文中bean的更多信息 |
applicationContextCreator |
OsgiApplicationContextCreator [d] |
允许自定义extender创建的应用上下文。包括修改应用上下文的类型或者额外的处理 |
Extender默认行为 |
(irrelevant) |
OsgiBeanFactoryPostProcessor [d] |
类似于Spring的 BeanFactoryPostProcessor接口, OsgiBeanFactoryPostProcessor的bean自动检测是应用所有的上下文,不管是不是用户定义的。这种后处理器的类型是很有的,因为它允许自定义bean工程,例如增加/删除/修改已有的bean定义或则增加一个新的bean实例 |
Extender默认行为 |
osgiApplicationContextListener |
OsgiBundleApplicationContextListener [e] |
应用上下文事件监听器是由extender自动注册的 |
默认实现提供了应用上下文生命周期的日志记录功能 |
[a] org.springframework.core.task [b] org.springframework.core.task [c] org.springframework.context.event [d] org.eclipse.gemini.blueprint.extender package [e] org.eclipse.gemini.blueprint.context.event package |
从extenderProperties bean,下面的属性被识别:
另外,在Eclipse Gemini Blueprint中,引入了新的系统属性来控制提供命名空间的bundle是否被认为应该处于RESOLVED (默认)或STARTED 状态。前一种状态允许一旦安装到OSGi框架的bundle的依赖解析了,就可以获得该命名空间 -它的优势是命名空间尽可能快的变为可用,但是缺点是,要移除命名空间,就需要移除源bundle。后面的状态只有当源bundle实际启动时,才会强制考虑命名空间,要移除命名空间,只需要简单的停止它自己的bundle即可。然而,bundle的启动顺序会影响命名空间的可用性-提供命名空间的bundle需要先启动,使用这些命名空间的bundle后启动- 依赖于应用,这样的依赖图可能导致难以捉摸的问题(在运行时)。因此,如果定义了系统属性 org.eclipse.gemini.blueprint.ns.bundles.started,并设置为true,那么只有started状态的bundle的命名空间才被考虑,如果设置false,那么resolved状态的bundle会被使用。
表8.3. 可用的extenderProperties
名字 |
类型 |
描述 |
默认值 |
shutdown.wait.time |
java.lang.Long |
每一个应用优雅关闭等待的时间,单位毫秒(ms) |
10000 ms (10 s) |
dependencies.wait.time |
java.lang.Long |
新创建的应用上下文等待强制服务依赖的时间。单位毫秒(ms)。这个设置只用于上下文自己的bundle manifest不定义该值 |
300000 ms (300 s 或 5 min) |
|
注意 |
由于使用应用上下文, Spring IoC容器的全功能用于创建extender的配置bean。 |
8.3.1. 监听To Extender事件
有一些场景,应用上下文启动成功或失败要用日志记录下来(举个例子)。 For 对于这些场景,Gemini Blueprint/Spring DM提供了专门的包 org.eclipse.gemini.blueprint.context.event定了OSGi应用上下文生命周期中可能发送的事件。 这时,下面的事件都是可用的:
表8.4. Gemini Blueprint/Spring DM内置事件
事件 |
解释 |
OsgiBundleContextRefreshedEvent |
当OSGi应用上下文成功初始化或者刷新时发送该事件 (例如ConfigurableApplicationContext接口的 refresh()方法)。在应用上下文的生命周期内会收到多少次事件时不保证的,这取决于具体的实现 |
OsgiBundleContextFailedEvent |
当OSGi应用上下文由于失败而关闭时发送该事件。在应用上下文的生命周期内这个事件会出现多次-在刷新之前、之中、之后。通常原因都表明配置有错误-语法错误、不正确的接入、丢失的bean等等 |
OsgiBundleContextClosedEvent |
当OSGi应用上下文成功刷新之后关闭时发送该事件(通常是Spring bundle被关闭时发出) |
对接收这些事件感情趣的人应该实现OsgiBundleApplicationContextListener接口,并将其发布为OSGi服务。Gemini Blueprint/Spring DM extender会自动检测监听器,向其发送事件。由于利用OSGi服务注册表的优势,extender将事件的接收者和发布者解耦了,另外注册/注销过程也更加容易。例如,客户端注销监听器不需要特定的操作-简单停止bundle就会自动注销所有它的发布的服务(包括监听器),extender会检测到这个事件将监听器移除。当然,在bundle的生命周期里,也可以手动注销监听器。
|
注意 |
Gemini Blueprint和Spring DM事件语义与Spring是不同的。OSGi事件不发送给产生这个事件的应用上下文内的bean,而是发送给第三方对这个行为感兴趣的(可能是其它应用上下文中的bean)。 |
Gemini Blueprint工程提供了许多bundle构件,为了Spring extender功能正确,它们都必须安装到OSGi平台:
- extender bundle自己, org.eclipse.gemini.blueprint.extender
- 支持的核心实现, org.eclipse.gemini.blueprint.core
- Gemini Blueprint I/O支持库bundle, org.eclipse.gemini.blueprint.io
另外,Spring框架提供了许多bundle,都必需安装。从Spring框架2.5 release版本开始, Spring发布包中的jar都是合法的OSGi bundle,可以直接安装到OSGi平台。必需的最小bundle集合:
- org.springframework.aop.jar (bundle symbolic name org.springframework.aop)
- org.springframework.asm.jar (bundle symbolic name org.springframework.asm)
- org.springframework.beans.jar (bundle symbolic name org.springframework.beans)
- org.springframework.core.jar (bundle symbolic name org.springframework.core)
- org.springframework.context.jar (bundle symbolic name org.springframework.context)
- org.springframework.expression.jar (bundle symbolic name org.springframework.expression)
另外,下面的支持库bundle也是必需的。这些库的OSGi版本都在Gemini Blueprint发布包中。
- aopalliance
- cglib-nodep (when proxying classes rather then interfaces, needed in most cases)
-
commons-logging API (强烈推荐SLF4J:
- SLF4J API (com.springsource.sfl4j.api.jar)
- SLF4J Implementation Bridge (例如Log4j - com.springsource.sfl4j.log4j.jar)
- SLF4J commons logging adapter (com.springsource.sfl4j.org.apache.commons.logging.jar)
- commons-logging的日志实现 (例如log4j)
8.5. 可选的Gemini Blueprint Bundle
作为选择,你可以按照下面的bundle扩展Gemini Blueprint的核心功能:
- Gemini Blueprint专门扩展bundle:org.eclipse.gemini.blueprint.extensions
8.6. Spring XML编写支持
Spring 2.0引入了更简单的XML配置和可扩展的XML编写。扩展的XML编写允许自定义schema,它可以自动被Spring XML基础模块发现(在OSGi环境中),只要它们在类路径中。Gemini Blueprint或Spring DM能感知到这个过程,并支持它,所以不需要额外的代码或manifest声明,就可以使用自定义的schema。
在OSGi空间中部署的所有bundle(不管是否为基于Spring的)都会被Gemini Blueprint/Spring DM扫描自定义的命名空间声明(检查bundle空间的的META-INF/spring.handlers和META-INF/spring.schemas)。如果查找了,Gemini Blueprint/Spring DM会让这些schema和命名空间能够通过OSGi服务访问,基于Spring的bundle会自动使用OSGi服务找到它们。这意味着如果你想要部署一个使用自定义schema的bundle,你需要将提供命名空间解析器和schema的库部署一下。内嵌类路径库(提供自定义schema)中的bundle不会使用OSGi空间的命名空间。然而,嵌入库的命名空间不会与其它bundle共享,即它们不会被其它的bundle看到。
总之,当我们使用Gemini Blueprint/Spring DM时,自定义的Spring命名空间是透明支持的,不需要额外工作。内嵌的命名空间提供者具有更高的优先权,但是不会共享,与那些部署为bundle中能够被其它bundle看到和使用的提供者不同。
8.7. 导入和导出包
参考OSGi核心平台了解Import-Package和Export-Package manifest头的更多细节。对于bundle依赖的外部package,需要使用Import-Package条目。如果bundle提供的类型会被其它的bundle访问,你需要使用Export-Package条目将每一个外部需要访问的包导出。
|
重要 |
Export和Import-Package在定义bundle类空间上扮演了很重要的角色。如果不正确的使用,bundle可能不能够加载类或者资源,加载不正确的版本,甚至同时加载多个版本通常会导致ClassCastException、NoClassDefFoundError或者LinkageError。我们强烈建议你要非常熟悉基础,至少对于初学者,要使用工具(例如Bundlorhuo或BND)来创建合适的OSGi manifest。 |
8.8. 使用外部库的考量
什么是上下文类加载器? |
J2SE中引入的线程上下文类加载器无需再炫耀。下面是引用Java网站一个教程对它的简短定义:Java 2 平台也引入了线程上下文的概念。线程上下文类加载器默认是线程的父类加载器。线程的层次从原始线程开始(运行这个程序的线程)。原始线程的上下文类加载器被设置为加载这个应用的类加载器。所以除非你明确的修改了线程的上下文类加载器,否则它的上下文类加载器就是应用的类加载器。即,上下文类加载器可以加载应用可以加载的类。这个类记载器被Java运行时使用,例如RMI(Java远程方法调用)加载用户应用下的类和资源。上下文类加载器,就像任何Java 2平台的类加载器一样,具有父类加载器,支持前面介绍的相同的代理模式。 |
许多企业级应用库都假定构成应用的所有类型和资源都能通过上下文类加载器访问。虽然大多数开发人员不会使用上下文类加载器,但是应用服务器、容易或者多线程应用却会重度使用这个加载器。
OSGi R4没有对通过上下文加载器访问类型和资源没有进行定义。这意味着OSGi平台不保证线程上下文加载器,换句话说,它不管理线程上下文。
这样,在OSGi环境内执行时,手动加载类的代码或者动态生成新类的代码可能会引发问题。
Gemini Blueprint保证在创建某个bundle的应用上下文期间,这个bundle类路径下的所有类型和资源都可以通过上下文类加载器访问。当调用外部服务或为外部提供服务时,Gemini Blueprint也允许控制上下文类加载器能访问那些类和资源。参见第9章服务注册表了解更多细节。
处理生成类和第三方引入的库隐式类路径依赖的工作正在OSGi R5时间表中。在过渡期,你需要依赖变通方法,例如DynamicImport-Package manifest头,或者特定OSGi实现提供的工具,例如Equinox的buddy机制。Gemini Blueprint文档包含关于常见企业库已知问题和变通方法的更多细节。
8.9. 诊断问题
你选择的OSGi平台实现应该能够提供关于OSGi环境当前状态的大量信息。例如,用-console参数启动Equinox会提供一个命令行控制台,通过这个控制台你可以确定安装了那些bundle以及它们的状态、包、导出的服务,找出为什么bundle无法解析,驱动bundle的生命周期等。所有的OSGi平台都提供它们自己的日志,这可以使能,通过专门的设置自定义。更多细节请参考OSGi平台文档。
另外,Spring自己和Gemini Blueprint bundle包含大量的日志工具,可以帮助你诊断问题。推荐的做法是部署Java简单日志外观(slf4j) slf4j-api.jar和slf4j-log4j13.jar bundle(这些jar文件都是合法的OSGi的bundle)。然后你只需要在bundle的类路径根目录下创建简单的log4j.properties文件。
托管的OSGi-aware运行时环境,例如dmServer,提供了额外的日志和视图,不仅方便了bundle,而且跟应用上下文和虚拟机有关。
注意,Gemini Blueprint内部使用commons-logging API,这意味着它的日志实现完全是可插拔的。请参见FAQ和资源页面了解更多除了log4j之外其它的日志库信息。