了解了基于osgi系统设计的基本思想,进一步来研究osgi与流行的框架(spring,struts,hibernate等)的集成,这里首先讲解一下集成原理。
l 解决和spring的集成
由于spring的应用占据了大部分的java应用领域,所以解决与spring的集成是osgi必须解决的,spring-dm的推出大大促进了两者的结合,有助于osgi进军企业领域。
Spring所带来得好处主要有这么两点:
1. 不需要对外的export的服务可控制在bundle范围内进行进入使用。
2. 获取到spring 提供的pojo enhanced 的众多功能。
搭建开发环境
搭建spring-osgi的开发环境,首先我们要提供一些spring的bundle部署至osgi框架中,由此得到spring 提供的功能,部署spring-osgi所需要的bundle:
Org.springframework.osgi.aopalliance.osgi;
………………………osgi.aspectjrt.osgi;
……………………..osgi.backport-util-concurrent;
……………………..osgi.spring.aop;
……………………..osgi.spring.aspects
………………………osgi.spring.beans;
………………………osgi.spring.context;
……………………..osgi.spring.core;
………………………osgi.spring.dao;
……………………..osgi.spring-osgi-core;
………………………osgi.spring-osgi.extender;
………………………osgi.spring-osgi-io;
复制相应jar包到eclipse/plugin下,即完成了spring-osgi的环境准备。
在spring bean .xml中发布和引用osgi service
要把一个spring bean 发布为osgi service,只需在spring bean的xml进行如下配置:
<osgi:service id=”osgi 服务id” ref=”需要发布为osgi服务的spring beans” interface=”服务的接口”>
发布osgi service 时,还需要经常用到的properties属性的配置,通常在spring bean。Xml文件中增加osgi:service-properties的节点来实现:
<osgi:service>
<osgi:service-properties>
<prop key=”属性名”>属性值</prop>
</osgi:service-properties>
</osgi:service>
引用osgi service时,需在配置文件中如下配置:
<osgi:service id=”相当于spring bean name” interface=”服务接口” cardinality=”0…”/>
Spring-osgi还支持lazy-init(配置为true时,此服务只有被调用时才能激活),depends-on(配置此服务所依赖的服务),context-classloader(用于配置使用第三方的classloader来加载服务) 以上属性配置。
实现bind和unbind的设置:
在osgi:reference的元素下增加osgi:listener的节点
<osgi:reference>
<osgi:listener ref=”引用了此service的sping bean”
Bind-method=”服务可用时调用的方法”
Unbind-method=”服务不可用时调用的方法”
</osgi:reference>
Spring-osgi默认加载解析META-INF/spring目录下的Xml文件作为spring bean配置文件,也可通过在META-INF.MF中增加spring-context来指定spring bean 配置文件。
将简单留言板基于spring-osgi重构后将其重构为spring bean,
l 定义spring bean <目录为:META-INF/spring/springbeans.xml(定义bean)>
l 定义所需引用的osgi服务<配置于osgiservice.xml>
l 定义所需发布的osgi服务<配置于osgiservice.xml>
l 运行(应用bundle启动与spring-osgi所带bundle,降低spring-osgi的bundle的start-level为2。)
通过以上osgi与spring的集成使得基于osgi的应用很容易满足企业的一些基本需求,(事务管理,远程调用等等)
总结一下spring-osgi所带来的优势:
l Bundle内服务的注入支持,这样就需要将内部服务发布为osgi service了
l 企业所需的技术的支撑(事务管理,远程调用)
l 支持三方的classloader去加载osgi service.
Spring 值得改进的地方:
l 形成spring microkernel(spring对bundle的细度集成)。不必引用太多bundle.
l Spring本身所支持的功能对模块化的支持(spring-dao,spring mvc对模块化的支持不很切实)
l 需要设置启动顺序,在spring-osgi应用中。(或许可以使用扩展点方式实现)
l Spring-osgi支持动态性的效果还没有完善,(after the bundle update,in same time don’t update the osgi:service).
由于osgi与spring的集成体现了osgi进军企业应用的大好前景与人们的愿望,这里我转附了spring osgi 规范,便于大家深入理解:
Spring OSGi规范
1.0 简介
Spring框架是一个领先的full-stack Java/JEE应用框架。它提供一个轻量级的容器,依赖注入、aop、可插接的服务抽取,这些使得非侵入式的编程模型成为可能。OSGi提供了一个动态应用程序的执行环境,在这个环境中组件(bundles)可以在运行中被安装、更新、删除。它同时也可以很好地支持模块化及版本化。
Spring’OSGi的目标是使得写基于Spring的应用程序尽可能的容易,这些应用可以部署到OSGi的执行环境中,并可有效利用OSGi框架所提供的服务。通过在易用、强大的Spring框架上构建应用程序,Spring对OSGi的支持也使得开发这样的基于OSGi的应用更加简单、更加高效。
· 更好的分离应用逻辑与模块;
· 同时部署一个模块的多个版本的能力;
· 动态查找、使用系统其它模块提供的服务的能力;
· 在运行时系统中动态部署、升级、卸载模块的能力;
· 使用Spring框架在模块之间实例化、配置,集成,装饰组件;
· 让企业应用开发者使用简单、熟悉的编程模型开发OSGi平台的功能。
我们相信OSGi与Spring的结合为构建企业应用提供了最全面的可用的模型。
Spring’s OSGi的目标并不是提供一个通用的模型以支持任意的基于OSGi的应用程序开发,但是某些OSGi的开发者肯定能够发现Spring模型吸引人之处,并采纳它。目前已经存在的OSGi的bundles以及它们所export的任何服务都可以轻松的集成到使用SpringOSGi支撑的应用中,就象是Spring已经存在的配置项。
Spring OSGi定位于OSGi R4及以上版本,JDK1.3及以上版本。
这个规范假设读者已经具有一定的Spring及OSGi的知识。参见介绍白皮书“OSGi for Spring developers”以及“Spring for OSGi developers”。注意:这些白皮书现在还不存在,还处于书写阶段。
2.0 Bundles and Application Contexts
OSGi中的开发单元(以及模块单元)是bundle。OSGi中所说的bundle有三种稳定状态:installed,resolved,active。Bundles可以导出(export)服务,其它的bundles可以查找并使用这个服务。
在Spring中主要的模块化单元是一个application context,它包含很多个beans(由Spring应用环境所管理的对象)。Application contexts可以分级配置,这样一个子application context可以看到定义在其上级的beans,但是反之则不行。Spring的exporters及factory beans的概念用于导出引用给application context外部的客户端的beans,以及注入引用到定义在一个application context外部的服务。
OSGi的bundle与Spring的application context有着本质的联系:一个激活的bundle可以包含一个Spring的application context,负责在bundle中实例化、配置、组装以及装饰对象(beans)。其中一些beans可以被导出(export)为OSGi服务以供其它bundles使用,在bundle中的beans也可以被轻松的注入OSGi服务引用。
2.1 在Bundle中创建Application Context
一个Application context可以使用一个或多个定义了beans的XML配置文件来配置。(严格地说,一个application context对于配置的形式并不可知,但是XML是最常用的形式)。包含配置信息的XML文档放置于bundle中的META-INF/spring文件夹。缺省情况下,Spring将使用在这个文件夹中的所有以“.xml”为扩展名的文档,作为application context的配置定义。
缺省设置可以在Spring-Context的manifest header中重写。Header的值是由逗号分隔的资源路径及指令列表。
Spring-Context ::= context ( ’,’ context ) *
> context ::= path ( ’;’ path ) * (’;’directive) *
每一个路径都被当作在bundle中定义的资源路径处理,例如:
Spring-Context: config/application-context.xml,config/security.xml
当带有Spring-Context manifest入口文件或者位于META-INF/spring文件夹中的资源的bundle被激活时,Spring将自动创建application context。为了达到这一点,你必须首先安装(install)并启动(start)在你的OSGi运行时中的org.springframework.osgi.extender bundle。
当一个application context被首先创建后,它就判断所配置的OSGi服务引用,看是否有任何服务引用指定了cardinality(例如指定1..1或者1..n)。直到所有必须的服务都可用之后,这个context的初始化才结束。wait-for-dependencies指令可以在Spring-Conext头中设置为false从而改变这种行为。当wait-for-dependencies设置为false时,如果application context在被激活时其所必须的服务还不可用,创建application context将失败。
清单头条目(The manifest header entry):
Spring-Context: *;wait-for-dependencies:=false
表示所有在META-INF/spring中的xml文件都应该被配置,并且如果context所必须的服务不是立即可用,context的创建将会失败。
A header entry:
Spring-Context: config/application-context.xml;wait-for-dependencies:=false
表示使用config/application-context.xml配置文件来配置application context,并且如果其所必须的服务没有立即可用,context的创建将会失败。
Application context作为org.springframework.context.ApplicationContext的一个实例被自动发布为一个OSGi服务。此外,org.springframework.context.service.name用于设置驻留了application context的bundle的标识名称。通过在Spring-Context manifest entry中指定“publish-context:=false”可以禁止发布context为一个服务。
注意:application context被发布为一个服务使得测试和管理变得容易。获取一个指向在其它application context中定义的bean的首选方式是使用<osgi:reference> 和 <osgi:service>元素(相对于在application context服务中调用getBean()方法)。原因就在于通过使用<osgi:reference> 和 <osgi:service>组成服务,OSGi的基础架构将保证一个bean只能看到类的版本与之相兼容的服务,反之如果在注册表中查找一个application context,然后调用getBean(…)返回一个对象,然后类兼容性的唯一保证就是ApplicationContext类自身是兼容的。很明显在一个存在同时部署了多个版本的bundle的系统中,这种保证是不够健壮的。
2.2 Spring资源抽取
Spring在application context中使用Spring ResourceLoader加载资源。相关资源路径由application context以一种与application context类型相符的方式(例如基于context的class path,或基于context的web-app)解释。对OSGi应用来说,相关资源路径作为从bundle classpath中加载的资源解释。如果资源路径以“bundle:”前缀开头,那么只以给定的资源搜索bundle自己及其附加的fragments。
2.3 BundleContextAware
Spring鼓励基于不依赖于任何环境的简单对象开发应用。但是,如果一个Spring bean由于某种原因的确需要访问它的BundleContext,那么这个bean可以实现org.springframework.osgi.context.BundleContextAware接口。实现这个接口的Bean当在application context中实例化时将会被注入BundleContext。
2.4 使用Context ClassLoader
目前存在很多很有用的第三方包及应用,它们对OSGi一无所知,并且依靠线程环境ClassLoader动态加载类。OSGi没有定义context类加载器在任何时间点将是什么(OSGi does not define what the context ClassLoader will be at any point in time.)。这个现实与OSGi的非分级的类加载机制相符,意味着这些包将不能找到它们所需要的类和资源。
举一个简单的例子,一个应用被打包到bundle A中,使用bundle H所导出的Hibernate的类创建了一个Hibernate SessionFactory。这个SessionFactory将会需要加载定义在bundle A中的应用的类及资源,但是对于它来说,在OSGi环境中,bundle A是不可见的。为解决这个问题,Context ClassLoader对于任何从bundle A发起调用到bundle H的线程来说必须被设置为A的bundle ClassLoader。
Spring-OSGi保证了当激活一个bundle时,context ClassLoader总是被设置为能够访问被激活的bundle的资源。这样在bean实例化及配置阶段发起的对包的调用总是产生于一个合适的context ClassLoader的上下文环境中。
请求Spring管理context ClassLoader调用OSGi服务也是可能的,以及对作为OSGi服务而暴露的beans的调用。想了解更多的细节请参考第三部分。
2.4.1 Other contexual access
Spring bean能够实现BundleContextAware接口以方便被注入它所在的bundle的BundleContext引用。在bundle被激活以及其它调用某个被当作Spring bean访问的OSGi服务时,Spring也会通过一个ThreadLocal变量来提供对“当前”bundle的BundleContext的访问,这个变量可通过LocalBundleContext.getContext获取。
2.5 Web应用支持
Spring使用ServletContextListener,org.springframework.web.context.ContextLoaderListener来自动的为web应用创建WebApplicationContext。OSGi可用的WebApplicationContext,org.springframework.osgi.context.support.WebApplicationContext版本提供用来在OSGi中运行web应用。要使用这种支持,需要在你的web.xml文件中设置监听器声明的contextClass参数为“org.springframework.osgi.context.support.WebApplicationContext”。
3.0 OSGi平台服务和动态本质
OSGi是一个动态的平台:bundles可能在框架运行期的任意时刻被安装、启动、更新、停止以及卸载。在本章我们将从application context及其发布和访问的服务的角度来揭示这意味着什么。
当一个激活的bundle停止时,在它的生命周期中它所导出的任何服务也将被自动反注册,并且bundle返回到resolved状态。停止的bundle会释放所有它所获取的资源,终止所有的线程。停止的bundle所导出的Packages仍然对其它bundles可用。
处于resolved状态的bundle可以被卸载:被卸载的bundle所导出的packages也仍然对其它导入它们的bundles可用(除了新安装的bundles)。
处于resolved状态的bundle也可以被更新。更新使得bundle从一个版本迁移到另一个版本。
最后,处于resolved状态的bundle可以被启动,使它的状态转移到active状态。
OSGi PackageAdmin refreshPackages操作刷新所有OSGi框架的包或者是已安装的bundles的给定的子集。在刷新期间,受其影响的bundle中的application context将会被停止并重新启动。refreshPackages操作完成后,被更新的bundle的原有版本导出的包不再可用。完整的细节内容请参考OSGi规范。
3.1 启动停止定义了application context的bundle
当包含Spring资源的bundle被激活时,这个bundle的application context会自动创建(参见2.1节)。随后,当这个bundle被停止时,application context会被关闭然后销毁。在context中任何实现了DisposableBean接口的单例bean或在配置中指定了destroy-method的bean将会收到通知。当application context停止后,所有被bundle导出的OSGi服务会被反注册。
3.2 OSGi服务
OSGi服务是一个在OSGi服务注册表中发布的对象,提供公开的接口。服务也可以发布成为一组可查询的属性。
OSGi服务本质上是动态的因此使用服务的应用需要有这方面的处理机制。OSGi提供几种不同的机制来处理动态服务,包括声明性服务(DS)规范。
使用DS时,服务组件的所有依赖都满足后被激活,服务组件也可以用一些简单的属性值(简单类型,字符串值以及这些类型的数组或vectors)和引用已发布的OSGi服务进行配置。组件自身也可能以一个由它自己所指定的接口实现作为OSGi服务注册。
服务组件类似于Spring beans,因为它们由简单Java对象返回并且可以进行依赖注入。然而,与Spring相比,这种依赖注入是非常有限的,没有任何在Spring中的bean可以使用的更高级功能的支持,例如AOP,声明性事务,安全,管理,导出/导入其它的非OSGi服务等等。而且如果不以完全的OSGi服务的形式导出任何注入的服务引用,也不可能将这组服务组件与某个bundle进行组装(It is also not possible to assemble together a set of service components within a bundle without also exporting any injected service references as full OSGi services.)。Spring允许在一个bundle中对bean进行完全的配置与组装,而且不需要仅仅为了在相同bundle中的另外一个bean的需要就将bean导出到bundle之外。这样在导出(exported)bean与私有(non-exported)bean之间就有了明显的区别。
DS(相当合理地)要求服务以OSGi服务组件形式打包,并使用OSGi的语法进行配置。当使用Spring的OSGi时,对用户来说这是一个潜在的混淆,因为在DS的概念、语法与Spring beans之间存在重叠。
Spring-OSGi能够管理在Spring application context中声明的服务之间的依赖关系,并且支持与DS相同的延迟激活语义。Spring也完全支持动态的发布、访问服务。Spring-OSGi使得bundles能够与使用DS的bundles和谐共存,但是对于新的bundle的开发来说,我们推荐使用Spring OSGi来代替DS。
Spring目的是提供一种简单、一致的编程模型,无论是在OSGi环境之内还是之外。这方便了在OSGi之外的测试工作,使得不熟悉OSGi的复杂编程模型的企业Java应用的开发者更容易、更高效地进行测试。同时,如果一些高级功能的确需要直接与OSGi协同工作,这也是支持的。
3.3 使用OSGi服务
<osgi:reference>元素用于定义一个本地的bean代理某一个(或一组)OSGi服务。唯一需要的属性是id(定义本地bean的名字)和interface(定义目标服务所注册的接口的全质类名)。
例如,定义一个本地bean代表MessageService服务:
<osgi:reference id="messageService"
interface="com.xyz.messaging.MessageService"/>
filter属性能用来指定一个可选的OSGi框架过滤器表达式来限定一组目标服务。
可选属性depends-on保证了所声明的依赖在引用bean之前被实例化。
可选的cardinality属性允许指定引用集(cardinality)(0..1,1..1,0..n,1..n)。缺省是“1..1”。当指定cardinality为 0..1或者1..1时,<osgi:reference>元素将interface元素所指定的接口类解析为一个bean。当指定cardinality为 0..n或者1..n时,<osgi:reference>元素将interface元素解析为一个接口类元素的集合。
请看下面的配置片段:
<osgi:reference id="messageService" interface="MessageService"/>
<osgi:reference id="listeners" interface="MyEventListener"
cardinality="0..n"/>
<bean id="myBean" class="SomeClass">
<property name="messageService" ref="messageService"/>
<property name="eventListeners" ref="listeners"/>
</bean>
类“SomeClass”定义如下:
public class SomeClass {
private MessageService msgService;
private Collection<MyEventListener> listeners;
public void setMessageService(MessageService aService) {
this.msgService = aService;
}
public void setEventListeners(Collection<MyEventListener> listeners) {
this.listeners = listeners;
}
// ...
}
注意:也可以使用原生类型Collection替代集合<MyEventListener>,Spring-OSGi不要求必须使用Java5。
3.3.1 绑定/反绑定服务资源
Spring给你一个常量对象引用,由<osgi:reference>定义(可以是一个指定为0..1或1..1的指向目标服务的代理,也可以是一个指定为0..n或1..n的Spring所管理的集合)。这个引用背后的服务可以动态的来去。
对于持有一个集合的服务引用来说,调用iterator()操作会返回一个Iterator实例,可以遍历某个常量引用集(在调用iterator()时匹配的引用)。集合成员可以在任何时间发生改变,因此再次遍历集合会看到不同的成员。
在任何时刻调用某个服务引用的操作都可能会失败,抛出一个unchecked(译者注:这里应该是指不用捕获的运行时异常的意思)异常。通过指定<osgi:reference>的timeout属性,Spring可以配置为等待指定毫秒数,以使某个服务在失败之前变成可用。如果所要求的服务不可用,默认的行为不是立即失败(例如没有延时期)。
例如:
<osgi:reference id="messageService" interface="MessageService" timeout="3000"/>
对于可选的服务引用(那些带有cardinality为0..1或0..n的服务引用),属性oneway可以设置为“true”。如果目标服务不可用,那么通过cardinality0..1的服务引用发起的、且返回类型声明为空的操作的调用将不会抛出ServiceUnavailableException异常。同样,如果这些操作声明返回类型为空并且目标服务在操作调用之前不可用,对通过遍历<osgi:reference>bean cardinality 0..n集合而获取到的某个服务引用的操作也不会抛出ServiceUnavailableException异常。
对于无状态的服务,支持到这种级别应该足够了。操作调用之中或之间,目标服务可能会被透明的更新。如果当调用应用时目标服务不可用,服务可以被重试,如果还不可用,唯一的可能就是抛出ServiceUnavailableException异常。
对于有状态的服务,或者对于仅仅想加入到服务跟踪链的客户端,通过使用嵌套listener元素指明一个或多个监听器,是有可能清晰的跟踪到OSGi服务支持的服务引用(OSGi services backing a service reference)的可用性的。
<osgi:reference id="messageService" interface="MessageService">
<osgi:listener ref="aListenerBean"/>
<osgi:listener ref="anotherListenerBean"
bind-method="serviceAvailable"
unbind-method="serviceUnavailable"/>
</osgi:reference>
如果bind-method和unbind-method属性都没有指定,那么被监听器引用的bean的类名必须实现Spring的TargetSourceLisfcycleListener接口。如指定了bind-method或者unbind-method属性,那么它们的属性值必须是监听器 bean类的一个方法的名字。当一个backing service被绑定或反绑定时,Spring将会调用所声明的方法。
声明绑定或反绑定操作如下所示:
public void some-method-name(String serviceBeanName, <ServiceInterfaceType> service)
public void some-method-name(String serviceBeanName, Object service)
public void some-method-name(<ServiceInterfaceType> service)
public void some-method-name(Object service)
当绑定方法被调用时,serviceBeanName参数(如果使用了)传递<osgi:reference>绑定的bean的名字(例如“messageService”)。service参数是代理目标服务的(不变的)服务引用。无论何时更新目标服务,包括初始化目标服务绑定,绑定方法都将会被调用。
每次服务从tracked reference集合中增加或删除时,为带有cardinality 0..n或者1..n的<osgi:reference>元素定义的监听器都会被调用。
Spring将延迟创建非可选服务引用(带有cardinality 1..1或1..n的<osgi:reference>的beans)的application context,直到那些引用被满足。但是,如果application context的非可选服务依赖变成不再被满足,Spring也不会自动销毁application context。如果某个服务的功能对于application context来说真的很重要以致于context没有它就不能继续,那么可以让这个服务引用的某个监听器bean实现ConfigurableApplicationContextAware并且在它的unblind方法中调用被注入的application context对象的refresh方法。调用refresh将会销毁所有的application context中的可任意处理的bean并且使得一旦非可选服务引用重新满足时application context被再次刷新。
3.3.2 通过服务工厂获取服务引用
OSGi服务可以由ServiceFactory返回。当某个服务由服务工厂提供时,每个请求引用这个服务的bundle都会获取唯一属于它们自己的服务实例。为支持由工厂创建服务的客户端配置,Spring支持嵌套在<osgi:reference>中的property元素。某个支持服务引用的目标服务将会由Spring依赖注入这个给定的属性。注入发生于每次目标服务被绑定到服务引用上时。
注意:我们不鼓励使用带有不是由ServiceFactory返回的服务的嵌套属性元素。我们的理想是制止这情况,但是OSGi没有提供足够的关于服务的运行时元数据。
3.3.3 Context ClassLoader管理
OSGi没有定义当一个客户端bundle调用另一个bundle发布的服务上的操作时,哪些类型是通过context类加载器可见的。(注意:OSGi规范未来的版本在这一点上可能会有些保证)。对许多希望使用context的类加载器加载类与资源的企业包来说,保持context类加载器不被定义是不可接受的。
可以使用<osgi:reference>元素的context-classloader属性来控制对context类加载器的管理。缺省值是client,指明的行为如上所述。将其值设置为service-provider可以保证当操作被调用时ContextClassLoader在服务提供bundle中对类与资源可见。将其值设置为unmanaged意味着Spring根本不会试图管理context类加载器。
3.4 将Spring beans导出OSGi服务
任何在application context中定义的bean都可能被导出(注册)为一个OSGi服务。当application context停止时它也将被自动反注册。
一个bean使用<osgi:service>元素注册为OSGi服务。ref属性命名了这个被注册的bean,interface属性定义bean注册的接口类型。例如:
<osgi:service ref="myBean" interface="com.xyz.MessageService"/>
<osgi:service>元素可以有选择的包含一个或多个嵌套的service-property元素,这些元素定义注册这个服务时所使用的属性。
除了引用对被reference导出的bean的之外,它也可以定义为<osgi:service>元素的匿名内部bean。例如:
<osgi:service interface="com.xyz.MessageService">
<bean class="MessageServiceProvider">
<!-- ... -->
</bean>
</osgi:service>
如果lazy-init属性被设置为true,那么这个服务将直到它被引用到才会创建。
如果作为服务导出的bean实现了OSGi ServiceFactory接口,那么OSGi将会调用这个接口所定义的方法以保证每一个请求服务的bundle获取唯一属性自己的服务实例。
Bean可以实现Spring的FactoryBean接口,代替实现OSGi ServiceFactory接口。如果工厂bean的singleton属性被设置为true,则服务的所有客户端都将看到相同的服务实例,但服务自身直至它被引用时才会创建。如果singleton属性设置为false,则每次bundle请求时,工厂bean的getObject方法都将会调用一次。如果由非单例工厂的getObject方法所返回的服务对象实现了DisposableBean,那么当所有引用这个服务对象的关联bundle都被释放后,服务对象的destroy方法就会被调用。
3.4.1 Context ClassLoader管理
OSGi没有定义当执行一个服务方法时,哪些类型对于context ClassLoader是可见的。(注意:OSGi规范未来的版本在这一点上可能会有些保证)。Spring可以为在<osgi:service>中定义的bean的操作管理context ClassLoader。<osgi:service>元素支持context-classloader属性,这个属性缺省设置为unmanaged(Spring不会为此服务的方法执行任何其它context ClassLoader的管理)。设置这个属性为service-provider可以让Spring保证当执行这个服务时,通过context ClassLoader可以使用发布服务的bundle的类与资源。
注意:设置context-class-loader=“service-provider”将会覆盖任何context ClassLoader,而这个ClassLoader可能已经被设置到通过<osgi:reference>所定义的bean发起的服务的调用上。
4.0 OSGi配置管理服务
Spring支持从Spring配置文件来设置bean属性值,并且支持从不同的来源来检索这个值。例如,Spring PropertyPlaceholderConfigurer类可以用于将escaped属性值替换为从属性文件中加载的值。
Spring OSGi也额外提供了从OSGi Configuration Admin Service中追溯bean属性值的来源。要做到这点,需要在Spring配置中定义一个或多个propertyPlaceholder元素(若多于一个,每一个都要用一个唯一的分隔符字符串进行配置)。例如:
<osgi:property-placeholder persistent-id="com.xyz.myapp"/>
其中“persistent-id”是OSGi PID,用作配置数据的关键字。默认分隔符是“${…}”,这样像例如带有值“${timeout}”的属性的值将被替换为配置管理服务在所提供的PID下持有的“timeout”属性的值。如果“update”属性设置为true,那么通过管理服务修改配置设置时,单例beans将被重新注入新的属性值。重注入仅支持基于方法(而非构造函数)的注入。
可选的default-properties属性可被设置为一个(java.util.Properties或者java.util.Map类型的)bean的名字,如果配置管理服务没有相匹配的定义被引用属性,这提供了默认的属性值。
参考Spring osgi schema获取property-placeholder元素的全部详细内容。
4.1 原子更新
如果通过配置管理服务设置了Bean的多个属性,并使用了“update”,当对配置进行一系列相关的修改时这个bean将能看到多个属性更新(每一个更新针对一个属性变化)。
<osgi:config>元素定义了一个Map类型的bean,包含所有在persistent-id中注册的属性。
<osgi:config id="myAppProperties" persistent-id="com.xyz.myapp"/>
它支持零个或多个嵌套config-listener元素,用于定义监听器beans,当Map的内容发生变化时,这些beans将得到通知。config-listener的update-method属性是必填的。这个属性的值是当配置发生改变时要调用的被引用监听器bean的方法名。例如:
<osgi:config id="myAppProperties" persistent-id="com.xyz.myapp">
<osgi:config-listener ref="aListenerBean"
update-method="setApplicationProperties"/>
</osgi:config>
update-method属性中命名的方法必须符合以面的形式之一:
public void update_method_name(java.util.Map properties)
或
public void update_method_name(String pid, java.util.Map properties)
第二种形式是为使用managed service factories准备的(参见OSGi服务纲要104.6节)。如果<sogi:config>元素所指定的persistent-id实际是一个ManagedServiceFactory所管理的配置信息的工厂PID,那么update-method将在每次每组属性在工厂PID中注册时被调用。
5.0 安装与启动(Provisioning)
OSGi不会自动的安装并启动为其它bundle提供服务的bundle。Spring为创建一个用于安装、启动的bundle提供了基本的支持。在将来可能会实现更复杂的安装启动功能(也许会与Felix的OBR结合)。
osgi:bundle以及osgi:virtual-bundle配置元素定义了代表OSGi bundles的beans。因此,基本的安装启动服务可以实现如下:
1. 安装并启动OSGi Spring的扩展bundle
2. bean declarations 使用声明了<osgi:bundle>的application context配置文件安装并启动一个“Spring”bundle
根据这一点,Spring将管理其余所需要的OSGi bundles的bring-up。
5.1 osgi:bundle元素
<osgi:bundle>元素可以用于为application context定义bean,来代表其它的osgi bundle(这样的bean的类型为org.osgi.framework.Bundle)。可用的最简单的形式如下:
<osgi:bundle symbolic-name="com.xyz.myapp.service"/>
如果location属性也被设置,则这个bundle还未被安装的话,就会被安装。如果“start”属性设置为true,那么这个bundle还未启动,也就会被启动。
<osgi:bundle symbolic-name="com.xyz.myapp.service"
location="http://.....some-url"
start="true"/>
5.2 虚拟bundles
<osgi:virtual-bundle>支持基于jar在运行时创建bundles。这种virtual bundle的机制允许用户熟练操作所有由OSGi解释的标准headers。例如:
<osgi:virtual-bundle id="bundleA" depends-on="bundleB"
location="file:bundleA.jar" state="start"
version="1.0" group-id="com.me.bundles" artifact-id="bundleA">
<exports
<list>
<value>com.me.bundles.bundleA</value>
</list>
</exports>
<imports>
<list>
<value>com.me.bundles.bundleB</value>
</list>
</imports>
</osgi:virtual-bundle>
location属性也允许是一个maven的pom.xml文件,从这个文件的pom以及根据pom中的信息访问到的相应的jar中将能够提取合适的版本信息。
5.3 未来安装与启动(Provisioning)的支持
根据哪些bundle导出哪些包提供哪些服务,可以提供一份清晰的列表,列出被安装和启动的bundle。将来安装启动(provisioning)可能会提供判断某个bundle所需要的(导入的)包的实现,以及bundle所引用的服务,自动安装启动所遇到的那些需要的bundles。这些支持超出了本版本的范围。
6.0 集成测试
Spring框架向来提倡测试驱动开发,并使得写出好的单元测试及集成测试用例变得更容易。OSGi应用程序的单元测试没有特列的要求,仅仅独立的测试你的应用类。一个应用程序可以对OSGi没有任何依赖。如果有,例如,BundleContextAware,那么可以使用OSGi Bundle和BundleContext接口很容易的进行模拟。
对于集成测试(在OSGi环境中执行测试),我们需要一些测试助手的帮助。所推荐的最好的实践是在一个分离的测试bundle中为要测试的bundle开发集成测试包。例如,给定一个bundle“com.xyz.myapp.service”,你可以选择把集成测试放到“com.xyz.myapp.service.tests”bundle中。Spring将提供一个AbstractOsgiTests基类,以便于启动OSGi测试,安装、启动运行一个测试所必须的bundles。这保证了每个测试都运行在一个干净的OSGi环境中,并且允许基于OSGi提供者的任何的支持来轻松的运行测试(我们将看一看equinox,knopflerfish以及Felix)。OsgiAdapter接口将支持不在这个范围之内的其它的OSGi提供者的加入。
更正:Knopplerfish为OSGi提供了全套的集成,似乎适用于任何需求,并且不必绑定在Knopplerfish实现。可以采用它做为集成测试工具。请参考Knopplerfish测试支持。
注意Eclipse IDE极好的支持基于bundle的测试,但是它绑定于equinox和Eclipse IDE,而Spring需要支持更多的OSGi提供者,更多的IDEs,并且要支持基于maven和ant的持续集成。
7.0 使用Spring与OSGi开发Web应用
Martin Lippert和Gerd Wutherich已经有了Spring OSGi代码在web应用中运行的沙盒,web应用可以使用嵌入到equinox中的servlet容器,由服务器方equinox孵化器工程(server-side equinox incubator project)创建。Spring OSGi工程目标在于企业应用,其中web应用的形式是很重要的一部分。因此可以确认在底层基础设施的支持下我们能够更容易的使用OSGi来编写、部署Spring的web应用。这种支持最初将基于equinox孵化器,然后如果可能的话,放宽到支持其它的OSGi提供者。
将开发一个简单应用,展示对在实际web应用环境中工作的支持。
8.0 管理OSGi应用
对于企业应用来说,通过JMX来管理OSGi的环境是很值得期待的(列出bundles,安装/卸载/启动/停止/更新等等)。如果这种支持预先并不存在(反正我还没有找到),那么Spring将提供一个OSGi bundle能够使用Spring JMX的支持进行基于JMX对OSGi的管理。
9.0 将Spring打包为OSGi bundles
要支持基于OSGi的Spring应用的开发,必须将Spring自身转换为一组OSGi bundles(jar文件)。这意味着Spring jars中将要确保存放所有必须的manifest entries。这个工作将在Spring 2.1中完成。每一个jar都将在Spring发布的“dist/modules”中被转换为一个有效的OSGi bundle(使用在OSGi环境之外的OSGi manifest entries也是无妨的)。新的监听器jar也将提供已描述的功能。
无论在什么情况下,所有资源加载与类加载都应该使用bundle classloader,而不是context classloader。当一个application context在OSGi bundle中创建时,这个bundle的classloader被设置为application context的classloader,并且ApplicationContext所支持的ResourceLoader接口也已经在OSGi顶层服务中实现。
10.0 将Spring应用部署到OSGi环境
TODO:描述maven plugin/ant task/SpringIDE扩展,允许容易地打包一个Spring应用为一个或一组OSGi bundle。
附录. 将已存在的应用代码部署到OSGi
在对jar文件进行最小的修改(intervention)转换为有效的OSGi bundles之后,将已存在的应用代码(特别是框架或包)部署到OSGi环境中是有可能的(例如有ant和maven tasks来协助完成这项工作)。因为类加载以及OSGi的隔离属性不同于在企业应用中所遇到的典型情况,这引起一系列要注意的问题:
· META-INF中的资源不能通过类加载器跨bundle直接访问。Spring的资源加载提取为基于Spring的资源加载解决了这个问题,处理规则如下:
> 1.找到被发起请求的bundle所导入的所有导出包的宿主bundles(通过PackageAdmin服务完成)。
> 2.将对每一个bundle的请求代理给Bundle.getEntry
> 3.聚合并返回结果。
· 框架或者包不能依赖于context的类加载器获得对应用类的可见性。这导致如果一个包试图通过反射加载一个应用的类,会出现ClassNotFoundExceptions。在equinox中ContextFinder机制提供了这个问题的解决办法(work-around)。在其它平台中这种机制并不支持。(Equinox也支持“buddy”机制,提供这个问题的另一种解决方案)。我们将研究Spring AOP的使用,设置一个合适的context class loader,包围bean方法的执行,在OSGi平台不提供内在解决方案的情况下使用。这样的类加载器也将支持上面概要描述的“META-INF中的资源”的解决策略。