对基于spring的osgi应用打包和部署
一个传统的spring应用使用单一的应用上下文,或者一个父上下文,它包括服务层、数据层和领域对象这样的子上下文。一个应用上下文也可以由多个配置文件聚合而成。
部署一个应用到osgi中结构更自然,应用的打包由一组对等的绑定(相当于spring应用上下文)组成,绑定通过向osgi注册为服务互相访问。独立的子系统可打包为独立的绑定或者一组绑定(垂直划分)。比如一个简单web应用可以划分为四个模块(绑定):web绑定、服务层绑定、数据层绑定和领域模型绑定。结构见下图:
在这个例子中数据层绑定包含数据层应用上下文,后者又包含一些内部的组件(bean)。其中两个bean注册为osgi服务,供外界公开访问。
服务层绑定包含服务层应用上下文,后者可包括一些内部组件(bean)。一些组件依赖数据层服务,并从osgi服务注册中导入这些服务器。两个服务层组件作为osgi服务注册用于被外部使用。
web组件绑定包含web应用上下文,后者包括一些内部组件(bean)。一些组件依赖应用应用服务,并且从osgi服务注册中导入这些服务。由于领域模型绑定只提供领域模型类型,并不需要创建任何组件,所以没有关联到应用上下文。
绑定格式和清单头信息
每个应用模块可打包为一个osgi绑定。一个绑定本质上是一个jar文件和一个META-INF/MANIFEST.MF文件,后者包括一些一些头信息,用于osgi的识别。详细描述见osgi服务平台核心规范3.2部分。一些osgi实现可能支持解压缩的jar文件,但是格式是一样的。
spring扩展器能识别spring创建的绑定,并在绑定已启动为之创建关联的应用上下文,条件是符合下面条件之一或者二者都满足:
- 绑定的路径包含META-INF/spring文件夹,其中有一到多个扩展名为“.xml”的文件;
- META-INF/MANIFEST.MF包含一个清单头信息Spring-Context
另外,如果在绑定的清单文件中声明了可选的SpringExtender-Version头信息,则扩展器将只识别满足指定版本约束的绑定。SpringExtender-Version头信息版本范围取值的语法,见osgi服务平台核心规范3.2.5部分的规定。
扩展器将查找META-INF/spring目录中每个“.xml”文件,如果缺少Spring-Context头信息,则将直接使用默认值。
一个应用上下文由一组文件生成。实践中建议将该应用上下文的配置分散到至少两个配置文件中,习惯上命名为modulename-context.xml和modulename-osgi-context.xml。modulename-context.xml文件包含与osgi独立的正规的bean定义。modulename-osgi-context.xml文件包含用于导入导出osgi服务的bean定义。而且在这里可以使用spring-dm osgi模式作为顶级命名空间,替代spring的’beans’命名空间。
Spring-Context清单头信息可被用于替换多个配置文件。资源的路径被视作相对资源路径并解析定义到绑定中的条目和附加片段。当在配置文件中找到Spring-Context头信息,所有META-INF/spring中的文件将被忽略,除非从Spring-Context头信息中直接引用。
Spring-Context头信息值的语法:
Spring-Context-Value ::= context ( ‘,’ context ) *
context ::= path ( ‘;’ path ) * (‘;’ directive) *
这个语法和osgi服务平台核心规范中3.2.3部分的osgi服务平台通用头信息语法定义是一致的。
例如,清单条目:
Spring-Context: config/account-data-context.xml, config/account-security-context.xml
将使用绑定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时间。绑定仍然会执行到ACTIVE状态。
wait-for-dependencies (true|false):控制是否要等待所依赖的强制服务满足后再执行(默认),或者立刻执行不等待。例如:
Spring-Context: config/osgi-*.xml;wait-for-dependencies:=false
表示创建一个应用上下文,使用所有匹配“osgi-*.xml”的配置文件。上下文立刻创建,即使有依赖的服务未满足要求。这基本上可以认为强制服务是作为该绑定中可选的服务。
timeout (300):超时(秒)用于等待强制依赖,如果失败将放弃它并导致应用上下文创建失败。本设置可通过wait-for-dependencies:=false忽略。默认值为5分钟(300秒)。例如:
Spring-Context: *;timeout:=60
表示创建一个应用上下文,等待强制依赖1分钟。
publish-context (true|false):控制是否发布应用上下文对象为osgi服务,默认为发布。例如:
Spring-Context: *;publish-context:=false
如果没有Spring-Context清单条目,或者没有为条目指定指令的值,那么指令使用默认值。
扩展器配置选项
除了绑定规范中的设置,spring-dm允许设置核心扩展器的通用行为。这对于spring-dm在一个被管理环境,或者需要绑定范围的功能,是很有用的。为了支持可扩展的配置,扩展器依赖osgi片段覆盖默认的设置。扩展器查找属于该绑定空间的META-INF/spring/extender文件夹下的xml文件,并装配到应用上下文(OsgiBundleXmlApplicationContext类型),该上下文用于内部的配置。覆盖扩展器的默认设置,需要在下表找到对应bean名称,用合适的方式定义并作为片段附加到spring-osgi-extender.jar,使用:
Fragment-Host: org.springframework.bundle.osgi.extender
目前扩展器可以识别下面的bean:
|
bean名称 |
类型 |
作用 |
默认腥味/值 |
| taskExecutor | TaskExecutor | 创建和运行spring应用上下文,该上下文和每个绑定关联。任务执行器的职责是管理应用上下文使用的线程池。 | 默认使用SimpleAsync
,它将为每个应用上下文创建新的线程。这适合测试和开发过程,强烈推荐在生产环境下使用线程池(而不是默认值)。 |
| shutdown
TaskExecutor |
TaskExecutor | 销毁spring应用上下文联系的所有绑定。任务执行器的职责是管理应用上下文使用的线程池。 | 默认使用TimerTask
|
| extender
Properties |
java.util.
Properties |
定义简单的属性,比如正常关闭上下文的最大时间值 | 见下面默认值 |
| applicationEvent
Multicaster |
ApplicationEvent
Multicaster |
用于将spring-dm时间传给第三方 | 默认使用SimpleApplication
EventMulticaster 。更多有关可用在应用上下文的bean信息见javadoc |
| applicationContext
Creator |
OsgiApplication
ContextCreator |
允许扩展器创建自定义的应用上下文。包括应用上下文类类型的修改或者附加过程 | 是扩展器默认行为。 |
| 其他 | OsgiBeanFactory
PostProcessor |
类似spring的BeanFactory
PostProcessor接口,该bean类型自动监测和应用扩展器创建的所有上下文(无论是否是用户定义的)。对于自定义bean工厂比如创建、删除、修改已存在的bean定义或者增加新的bean示例很有用。 |
是扩展器默认行为。 |
extenderProperties bean中,可识别如下属性:
| 名称 | 类型 | 描述 | 默认值 |
| shutdown.
wait.time |
java.lang
.Number |
扩展器等待每个应用上下文正常关闭总的时间。以毫秒计。 | 1000毫秒(10秒) |
| process
.annotations |
java.lang
.Boolean |
表示扩展器是否使用spring-dm注释的标志位。注意,可以为每个绑定增加类似的bean处理器,使之可用。 | false |
注意:若使用了一个应用上下文,spring ioc容器的所有功能均可用于创建扩展器的配置bean。
监听扩展器事件
比如,当应用上下文启动成功或者失败,需要记录日志。这种情况,spring-dm提供org.springframework.osgi.context.event包,用于表示osgi应用上下文生命周期中发送的事件。当前有以下事件可用:
| 事件 | 解释 |
| OsgiBundleContext
RefreshedEvent |
当osgi应用上下文成功吃石化或者刷新时(比如在Configurable
使用refresh()方法)发布。不能确保在应用上下文生命周期中接收到该事件的次数,这是具体实现决定的。 |
| OsgiBundleContext
FailedEvent |
当osgi应用上下文关闭失败时发布。该事件可以在应用上下文生命周期中的任何时间出现,刷新之前、期间或者之后。通常该事件表示配置中有错误,语法拼写错,错误的关联,未找到的bean或者其他。 |
第三方程序可实现OsgiBundleApplicationContextListener并发布为osgi服务,用于接收这些事件。spring-dm扩展器自动监测到监听器并将发送事件给它。扩展器利用osgi服务注册,解耦从发布者接收事件,而且使注册和注销过程更简单。比如,不需要指定客户端为注销监听器做任何事情,只需要简单的停止绑定就可自动注销所有它发布的服务(包括监听器),扩展器监测到这个事件后将删除监听器。当然,绑定生命周期中客户端也可以手工注销监听器。
注意:spring-dm事件的语义和spring有细微差别。osgi事件不能向引起事件的应用上下文包含的bean发送,但是可以发送给第三方监控者(可能是其他应用上下文的bean).
所需的spring框架和spring动态模块绑定
spring-dm项目提供了一些为了支持spring扩展器功能必须安装到osgi平台的绑定制品:
- 扩展器绑定自身,org.springframework.osgi.extender;
- spring-dm模块支持的革新实现绑定,org.springframework.osgi.core;
- spring-dm模块i/o支持库绑定,org.springframework.osgi.io。
另外,spring框架提供一些需要安装的绑定,spring框架2.5版本的jar文件是合法的osgi绑定,而且可直接安装到osgi平台。最少需要如下绑定:
- spring-core.jar(绑定名称:org.springframework.bundle.spring.core)
- spring-context.jar(绑定名称:org.springframework.bundle.spring.context)
- spring-beans.jar(绑定名称:org.springframework.bundle.spring.beans)
- spring-aop.jar(绑定名称:org.springframework.bundle.spring.aop)
另外,还需要以下支持库绑定。spring-dm分发包中已经包括了osgi可用的版本。
- aopalliance
- backport-util(如果使用jdk1.4)
- cglib-nodep(当需要类的代理而不是接口的代理时,大多数情况下需要)
- commons-logging API(高度推荐slf4j)
- 日志的实现,比如log4j
spring xml编写支持
spring2.0引入(还包括其他内容)了更简单的xml配置和可扩展的xml编写。后者提供了创建自定义模式的功能,spring xml底层功能(在非osgi环境里)自动发现类路径下的自定义配置文件并包含他们。spring-dm可识别这个过程并在osgi环境中支持它,因此自定义模式在绑定中可用,而且不需要额外的代码或者在清单中声明。
所有部署在osgi空间中的绑定,无论是否spring创建的,spring-dm都会扫描它们中自定义spring空间的声明(通过检查绑定空间的META-INF/spring.handlers和META-INF/spring.schemas)。如果发现了,spring-dm将通过一个osgi服务使该模式和名字空间可用,spring创建的绑定通过该服务自动使用。这意味着如果部署的绑定使用自定义模式,只需将提供了名字空间和模式的库部署就可以了。内置在类路径库中提供了自定义模式的绑定,将使这些覆盖那些在osgi空间中可用的。然而,内置库中的名字空间不能和其他绑定共享,也就是说,他们对其他绑定不可见。
简单的说,使用spring-dm,自定义的spring名字空间被透明支持,而不需要而外的工作。内置的名字空间提供者有优先权但是不能共享,相反的,作为绑定部署提供者可被其他部分访问(使用)。
导入和导出包
有关Import-Package和Export-Package清单头信息的详细信息参阅osgi服务平台。绑定为了每个依赖的外界的包,需要一个Import-Package条目。如果绑定提供的类型要求被其他绑定访问,需要Export-Package条目,使该包对外界的绑定可用。
使用外部库的考虑
很多企业应用程序库假定所有组成应用的类型和资源通过上下文类加载器可用。虽然大多数开发者不使用上下文类加载器,该加载器被应用服务器、容器或者多线程的应用重量级的使用。
在osgi r4,未定义通过上下文类加载器访问这些类型和资源。这意味着osgi平台不能确保线程上下文类加载器的值,或者换句话说,它根本不管理类加载器。
因此代码(比如库)在osgi环境中执行时,执行手工类加载或者动态生成一个新的类,可能引发问题。
spring-dm确保为指定绑定创建应用上下文期间,所有在绑定类路径下类型和资源都可通过上下文类加载器访问。spring-dm还允许当调用外部服务和向外部服务发送服务请求时控制通过上下文类加载器访问哪些资源。
正在制定中的osgi r5将提供处理生成类和隐含的对第三方库类路径依赖的标准支持。在过渡时期,可以依赖比如DynamicImport-Package清单头或者osgi具体实现提供的方法,比如equinox的伙伴机制。spring-dm文档包含更多一般企业库的已知的问题。
什么是上下文类加载器?
线程上下文类加载器引入java没有被大肆宣传。下面简要描述一下:
java2平台还引入了上下文类加载器的概念。一个线程上下文类加载器是,在默认情况下,设置为其父线程的上下文类加载器。线程的继承关系开始与初始线程(它运行了程序)。初始线程的类加载器被设置为加载应用程序的类加载器。所以,除非显式的变更线程上下文类加载器,线程的上下文类加载器应该是应用程序的类加载器。也就是说,这个上下文类加载器能加载应用程序能加载的类。这个加载器用于java运行时比如rmi代表用户应用程序加载类和资源。上下文类加载器,像其他java2平台的类加载器,有一个父类加载器并支持相同的代理模式,用于上面提到的类加载。
问题的诊断
选择的osgi平台的实现应该能提供较好处理有关osgi环境的当前状态信息能力。比如,使用-console参数启动equinox提供了命令行控制台,通过它能控制哪些绑定需要安装、它们的状态、绑定导出的包和服务、发现绑定解析失败的原因,以及驱动绑定生命周期的状态变化。
另外,spring自身和spring-dm包含广泛的日志支持,可帮助诊断问题。推荐的方法是部署slf4j.jar和slf4j-log4j13.jar绑定(本项目发布的jar文件是合法的osgi绑定)。需要做的是在绑定的类路径根下简单的创建一个log4j.properties文件。
注意spring-dm内部使用commons-logging API,这表明日志的实现是完全可插拔的。请查看FAQ和资源页,获取更多有关除了log4j其他日志库的信息。
这篇文章上的评论的 RSS feed TrackBack URI