基于 OSGi的企业级开发框架实践——认识OSGi和SpringDM

一. OSGi基础

1. 什么是OSGi

OSGi——Open Service Gateway Initiative,最初的目的是为各种嵌入式设备提供通用的软件运行平台。后来经过10年的发展和壮大,OSGi已经不只是在嵌入式设备中应用,而是被推广到各种其他的应用领域,比如其中最成功的Eclipse IDE。目前在企业级应用开发中也开始大量使用OSGi技术,尤其是在应用服务器领域,各大主要厂商相继宣布推出支持OSGi规范的中间件产品,例如Websphere、Glassfish、JBoss等。而从应用开发人员的角度来说,OSGi应该被理解为:OSGi Service Platform,它是一个开放的并且提供统一接口标准的体系框架,基于这个体系框架,服务提供商,应用开发人员,软件提供商,服务网管运营商,设备提供商之间能够联合起来相互协作,形成一个健康的生态系统,并向终端用户提供各种各样的服务(例如:SaaS,SOA等)。目前OSGi Service规范的最新版本是4.2,可以从 http://www.osgi.org官网上获取更详细的信息。

2. OSGi能带来什么好处,能解决什么样的问题?

做为一名实用主义者,新技术或新概念除了能够带给我们暂时的新鲜感外,还应该了解它是否能给我们的工作带来实际的好处,它能帮助我们解决怎么样的问题。OSGi与所谓的“云计算”相比并不算什么新技术,它已经存在了10年,所以它是成熟的、可靠的、经过验证的。只是相对于企业级开发领域来说,它还未被充分的实践,所以对企业级开发来说OSGi的确能算得上是新技术。以下是我眼中的OSGi给我带来的好处以及能够帮助我解决的问题,但不仅限于此,更有待您的发掘。

首先,OSGi改变了我们设计系统的传统思维模式(评价一名优秀的架构师,思考问题的方式是至关重要的评判指标之一)。模块化的系统设计理念并非新事物,早在面向对象的设计思想诞生之前就已经出现。在面向过程的程序开发中,模块化是非常重要的工具,它可以提高程序的内聚性,降低程序的耦合度,并且在大规模协同开发中发挥着重要的作用。当然OSGi对模块化又做了更进一步的发展,提出了服务平台(Service Platform)的概念——在网络环境下的服务平台,从此我们从面向对象的时代步入面向服务的时代。我们不再把关注重点放在如何高度抽象出优美的对象模型上,而是更加注重根据实际的业务需求划分各种服务接口的边界以及各服务接口之间的依赖关系。当然,组成服务内部的构件仍然基于面向对象的思想,只不过不再成为我们唯一的关注重点。在当今SaaS(软件即服务)的大趋势下,能提供何种质量的服务将被作为评判企业级管理软件优劣的重要标准,因为服务是用户唯一可见的交互渠道,系统内部的对象模型对用户来说只是一个黑盒子(这里的用户并非单指终端用户,也包括使用服务的开发人员)。而OSGi正好能用来解决如何更好的抽象服务,拆分复杂业务逻辑等诸如此类的问题。

其次,OSGi使我们可以更好的控制每个模块之间的约束和依赖。先来回顾一个曾经让我倍感痛苦的回忆:经过3个月的努力,系统终于开发完成,并通过了所有的测试,可以正式上线了。当部署完成并启动应用服务器时却出现了一大堆的异常。每个开发人员都很疑惑,也很沮丧。不是通过了测试了吗,这么会出现异常呢?经过大家通宵的努力,终于找到了问题的所在,原来系统依赖的一个第三方jar包与应用服务器自带的一个jar包发生了版本冲突。为了开发的便捷,测试环境用的容器是Tomcat,而Tomcat并没有自带这个冲突的jar包,但是线上环境是WebSphere,它正好自带了这个jar包,并且当我们的系统调用这个jar包时,由于Classloader的加载顺序问题导致先找到了应用服务器自带的那个jar包,所以发生了之前所描述的异常现象。虽然问题终于解决了,但是这样的问题仍然在后续的项目中不断重演……。OSGi成为了解决此问题的最佳方案,它可以让我们明确定义各模块的运行时环境(JDK版本)、导入包、导出包、依赖包,而所有的这些依赖关系的合法性验证均由OSGi运行时容器自行完成,当出现jar包版本冲突,依赖丢失等错误时,OSGi可以明确的给出提示信息,帮助我们尽快的定位问题、解决问题。在某些场景中,我们并不希望暴露某些模块的package,这时我们只需要在定义文件中不导出这些package就可以了,实现了包级别的封装控制机制。

再次,OSGi可以解决企业级应用系统启动速度慢的问题。随着企业业务的快速增长,员工对企业级应用系统的依赖程度也就越来越高,各种新的功能不断的被加入到原来的系统中,使得系统越来越臃肿。而系统中的有些功能并非被经常使用,却需要经历非常复杂的初始化过程,为了启动这个不被经常使用的功能,整个企业级应用系统的启动时间被整整拖延了数分钟(OK!您可能觉得我说的言过其实了,但是这的确是真的,我发誓!)。启动如此缓慢的应用,对于开发和测试来说简直就是噩梦,因为每次的代码修改都需要漫长而无谓的等待才能看到真正的结果。通过OSGi,我们可以将臃肿的业务逻辑拆分成边界清晰的功能模块,并对模块启动顺序进行规划,即那些模块需要第一时间启动,那些模块可以延迟加载,最后通过Bundle元数据文件(MANIFEST.MF)进行明确的启动策略定义。只有当正真需要这个模块时,OSGi运行时容器才会去加载它。这样就可以使启动缓慢的企业级应用系统如同Apache Server那样快速启动。

最后,OSGi可以实现我梦想中“即插即用,即拔即无”的奇妙系统。热部署可能是所有了解OSGi的人不假思索就能说出的特性。目前所有的web容器均实现了热部署功能,我们可以在不停止服务的前提下动态的部署各种应用程序。如果能将此功能带入到我们自己开发的系统中那将是多么的cool啊!OSGi将实现我们的梦想,我们可以在OSGi环境下掌控所有模块的生命周期方法,比如安装、启动、停止、卸载等,而这些操作均可在不停止当前应用的情况下进行,大大提高了无故障运行时间。并且我们还可以并发运行相同模块的不同版本,当某个新发布的版本出现问题时,我们可以暂时停止该模块,并重新启动前一个稳定版本,以隔离出问题的模块,而所有的这一切对用户来说都好像从来没有发生过一样。

最近我一直很关注网上的各类论坛和Blog,目的就是要寻找一些对于OSGi的不同声音,我需要不同的声音,因为每一件事物都不会是十全十美,只有知道它的缺陷,才能更好更全面的了解它。在我上述的四项问题中,最被大家所诟病的莫过于热部署和太过于细粒度的模块控制机制。热部署被描述为恶魔,当Bundle的数量成几何级别增长时,这个恶魔会变的越来越强大,以至于最终恶魔被释放到人间贻害无穷(其实问题的焦点聚焦于有状态的Bundle上,当热部署一个全新的Bundle时,这不存在问题,但是热部署一个更新的Bundle时,则有状态的Bunble将丢失所有已存在的状态,当然解决方案是存在的,状态的持久化是关键,但是这又增加了bundle的复杂度)。而太过于细粒度的依赖控制机制将导致bundle之间的依赖关系错综复杂,难以维护。诚然,我承认这些曾经被过度赞赏的OSGi特性在经过理性的认知和实践之后都被发现了问题,但是我个人认为,OSGi最大的好处并不是那些在各类网络文章中反复描述的“语法糖”,而是它改变了我设计系统架构的思维模式,它帮助我形成了一套面向服务的模块化架构设计思路,让我成为一名可以站在更高层次来考虑系统设计的优秀架构师(架构师如果在设计前期就陷入细节设计的话,那将是一个非常糟糕的开始)。

3. 我们真的需要OSGi吗?

虽然OSGi存在这样那样的问题,但是在企业级开发中它的确是存在价值的,我们评判一个事物是否有需要往往会考虑它的价值,是否利大于弊。所以做为架构师的你是否有一种冲动,是否会大声的对着项目经理说——下一个项目我们就用OSGi来构建。慢来!先让我们冷静一下,问一下自己,我们的项目真的需要OSGi吗?OSGi会不会像达摩克利斯之剑那样,在美妙的背后却隐藏着巨大的风险呢?对于我们技术人员来讲,没有最好的技术,只有最适合的技术。OSGi不是“银弹”,不可能帮助我们解决所有的问题。在为一个项目确定是否需要OSGi时,可以将OSGi的各项特性罗列出来,然后针对自己的系统逐一核对,评估每一项特性是否是系统真正需要的。比如你的系统只是一个部门内部使用的小工具,而且其他部门对此系统并不敢兴趣,那这样的系统就没有必要使用基于OSGi的架构,如果使用了反而会增加系统的复杂度,从而提高开发成本。而那些为企业级应用系统提供基础服务的服务端系统来说则更加的适合OSGi架构,因为通过模块化,它可以提供更加稳定的服务(7*24运行时间)。这里我需要特别的指出,OSGi需要生存在一个生态系统环境中,才能展现它的价值所在,所以更加适合解决方案级别的系统架构。如果在您的生态系统环境中只存在1,2个OSGi的系统,那就好比走在南极冰盖上的大象,等待它的只有死亡。

接下去的内容我比较纠结,因为到了最枯燥乏味的概念介绍了,本打算删除这些内容,因为网络上有太多的此类介绍,但是为了保持文章的完整性,我还是决定写下去,我尽量保持言简意赅,只把精髓描述出来。同学们,让我们继续吧。

二. OSGi原理

1. OSGi运行机制初探

1) 概述

OSGi中最重要的组成单元就是Bundle,说简单点Bundle就是一个将Java类文件、配置文件、MANIFEST.MF文件以及其他一些资源文件打包起来的Jar文件,它与普通Jar的区别就是Bundle中的MANIFEST.MF文件中包含了特殊的OSGi元数据定义,这样OSGi容器才能识别出是否是合法的Bundle。在运行时环境中,每个 Bundle 都有一个独立的 Classloader,Bundle 之间通过不同的类加载器和在 MANIFEST.MF 文件中定义的元数据来实现包级别的依赖关系控制。 隔离的Classloader 是实现这种运行方式的关键机制,每个 Bundle 的 Class Loader 必须在此 Bundle的所有依赖关系得到正确解析之后,再由 OSGi 框架创建。

2) Bundle的解析过程

Bundle 的解析是通过分析定义在 MANIFEST.MF 文件中的OSGi元数据 ( 主要定义了包约束条件 ),查找与包约束条件相匹配的 Bundle 并建立依赖关系的过程。 MANIFEST.MF 文件中的包约束条件主要是通过 Import-Package、DynamicImport-Package、Export-Package 和 Require-Bundle 这四种表达方式来实现。下面简单介绍一下它们:

    a) Import-Package:定义需要导入的包。默认是所有需导入的包必须都能够找到相应的导出Bundle (Exporter),否则解析失败。

    b) Export-Package:定义导出的包。在一个Bundle 中,一个包的导出意味着该包下的所有Class会在Bundle 自身的类路径里面可以被查找和加载。

    c) Require-Bundle:定义必须依赖的 Bundle 。

    d) DynamicImport-Package:定义需要动态导入的包。这部分定义没有在 Bundle 解析过程中使用,而是在运行时动态解析并加载共享包。

当Bundle 得到正确解析后,OSGi 容器将会生成所有Bundle之间 的依赖关系表。在实际运行过程中,容器就可以通过此依赖关系表找到 Bundle 依赖的外部 Class Loader,从而实现外部类资源的加载和运行。 某个Bundle 的依赖关系图可以通过在 OSGi 的控制台中输入内部命令“bundle {id}”来查看,其中{id}为某个Bundle的ID号,可通过“ss”命令查看所有运行在OSGi容器中Bundle的ID号。

3) Bundle的运行加载

Bundle 的运行主要依靠OSGi 容器为其创建的类加载器(Class Loader),加载器负责查找和加载 Bundle 自身的或依赖的类资源。 ClassLoader 之间的依赖关系构成了一个有向图,如下图所示:

基于 OSGi的企业级开发框架实践——认识OSGi和SpringDM_第1张图片

( 图-1)

Bundle 的 Class Loader 能加载的所有类的集合称为 Bundle 的类空间。Bundle的类空间由以下5部分组成:

    a) 父Class Loader 可加载的类集合;

    b) 由Import-Package定义的依赖的包;

    c) Require-Bundle 定义的依赖的 Bundle 的类集合;

    d) Bundle 自身的类集合,通常在 Bundle-Classpath 中定义;

    e) 隶属于Bundle 的 Fragment 类集合;

网上的大部分文章很少对Fragment Bundle做深入的解释,所以我这里重点的介绍一下神奇的Fragment Bundle。Fragment Bundle可以把它看作为某一Bundle的片段,虽然它本身是一个Bundle,但是它的生命周期确实依附于宿主Bundle,我更愿意将它理解为“寄生虫”,当宿主消亡时它也就没有存在的意义了。不过这条“寄生虫”却不会给宿主Bundle带来麻烦,反而它可以改变宿主Bundle的一些行为,通过一个实例,可能会让你更加的了解Fragment Bundle的作用。在我们即将介绍的OSGi企业级开发框架中会用到Jetty容器来发布Hessian远程服务,Jetty容器需要一个jetty.xml配置文件才能正确的启动Servlet容器。但是在一些场景中,我们需要自定义jetty.xml中的一些参数,比如监听端口等参数。默认的配置文件包含在Jetty容器的启动Bundle中,我们无法对JAR包中的配置文件做任何的修改,所以此时Fragment Bundle就有了它用武之地。在这个Bundle的MANIFEST.MF文件中定义Fragment-Host配置项指定被扩展的Jetty容器启动Bundle的Bundle-SymbolicName属性值。这样当Jetty容器启动Bundle被加载时就会将Fragment Bundle中定义的配置文件覆盖自身的默认配置文件从而实现配置的自定义功能。下图展示了宿主Bundle和Fragment Bundle之间的关系:

( 图-2)

其中Master=2表示该Bundle是一个Fragment Bundle,它附属于ID为2的宿主Bundle,而Fragments=1表示该Bundle存在ID为1的附属Bundle。需要注意的是,Fragment Bundle是不能用start命令启动的,否则会报错。因此该Bundle的状态为Resolved。

最后让我们来关注一下Bundle 的 Class Loader 是根据什么规则来搜索类资源的:

    a) 如类资源属于 java.* 包,则将加载请求委托给父加载器,(图-1)中的Parent/System classloader;

    b) 如类资源定义在 OSGi容器中启动委托列表(org.osgi.framework.bootdelegation)中,则将加载请求委托给父加载器,(图-1)中的System Bundle class loader;

    c) 如类资源属于在 Import-Package 中定义的包,则将加载请求委托给导出此包的Bundle的Class Loader;

    d) 如类资源属于在 Require-Bundle 中定义的 Bundle,则将加载请求委托给该Bundle的Class Loader;

    e) Bundle 搜索自己的类资源 ( 包括Bundle-Classpath 里面定义的类路径和属于 Bundle 的 Fragment 的类资源);

    f ) 若类在DynamicImport-Package 中定义,则开始尝试在运行环境中寻找符合条件的 Bundle 。

如果在上述的每个步骤中没有正确的加载到指定的类资源,则OSGi容器会抛出NoClassDefFoundException或ClassNotFoundException异常。这2个异常是OSGi环境下开发最容易遇到的问题,因此一旦遇到此问题就应该按照上述步骤检查每个Bundle是否正确导出了被其他Bundle引入的包或是正确引入了其他Bundel导出的包。

4) Java Classloader vs. OSGi Classloader

通过对上述Bundle类加载器运行机制的描述我们已经了解了OSGi环境下类加载器的运行原理,但是它和我们平时在非OSGi环境下开发的Java程序的类加载过程到底有什么不同呢?

基于 OSGi的企业级开发框架实践——认识OSGi和SpringDM_第2张图片

(图-3)

通过(图-3)我们可以清楚的看到OSGi环境下的classloader和普通Java环境下classloader的显著区别。在普通Java环境下classloader是以继承关系相互连接的,为了加载一个class,classloader首先会去询问它的父classloader是否可以加载这个class,如果不能加载,则它就尝试自己完成这个class的加载。这个机制是运用在每一个层次上的classloader的。从上图中,我们就可以看到这种机制的运行以及所有classloader之间的关系。正如我们所预见的,在Java中一个应用程序的所有class被同一个classloader装载,因此要实现不同class之间的隔离是非常困难的。这里我们有必要讲一下JSR294——Java模块化系统,JSR294的目的是为了提供一个Java自身内部的静态模块化系统。它采用与OSGi相类似的基于Require-Bundle头部的方式但是它更加强调语言级别上的支持以及它自身JDK中的模块化运行时。JSR294不会拥有如OSGi那样在同一个进程中支持相同类的不同版本共存的功能。

OSGi则提供一种不同的方式来使用classloader。它并没有采用基于继承的方式而是使用了一种叫Classloader Chaining的概念,它允许细粒度的控制每个类之间的可见性。在一个上下文中,每个组件(Bundle)与一个专有的Classloader所关联。根据组件(Bundle)的配置(manifest.mf),Classloader可以被连接到当前组件(Bundle)之外的其他组件(Bundle)的Classloader。但是在默认情况下,所有class都不能看到除自身之外的其他组件的class。因此我们必须通过在manifest文件中配置相应的package来显式的导入或导出它们。

如下图所示,这个处理流程被叫做“Classloader Chaining”。为了简单起见,我们只展示了2个彼此关联的组件(Bundle),但是在实际的实现中依据不同的配置则依赖关系也有所不同。

基于 OSGi的企业级开发框架实践——认识OSGi和SpringDM_第3张图片

(图-4)

从(图-4)中我们可以发现,2个Bundle是完全隔离的,因为他们有各自属于自己的Classloader。与之前的非OSGi的Classloader有着明显的区别。

5) 关键的MANIFEST.MF文件

Bundle与普通Jar文件的唯一区别就在于MANIFEST.MF文件的OSGi元数据,以下介绍一些常用的Bundle元数据定义:

    a) Bundle-Activator:定义Activator的实现全限定类名称,此类必须实现BundleActivator接口,并实现start和stop方法。当Bundle被OSGi容器启动或停止时就会去调用start和stop方法。Bundle-Activator并非是必须的,只有在需要初始化或是销毁资源时才有用,并且不推荐在start方法中进行复杂的处理,以免加重OSGi容器启动的负担。在Spring DM中,ApplicationContext的创建和销毁就是在extender中通过实现BundleActivator接口实现的。

    b) Bundle-Classpath:有些jar文件是某个Bundle专属的,此时就应该把这些jar包设置到Bundle的classpath中。比如JDBC驱动等Jar包。

    c) Bundle-ManifestVersion:设置Bundle所遵循的OSGi规范版本,目前情况下应该将该值设置为2,表示OSGi R4版本。

    d) Bundle-Name:必须的,Bundle的名称,与Bundle-SymbolicName对应,Bundle-Name类似于name,而Bundle-SymbolicName类似于id。

    e) Bundle-SymbolicName:必须的,Bundle的唯一标识名,一般推荐采用类包的机制,保证其唯一性。

     f) Bundle-Version:必须的,Bundle的版本号,可用于在import-package中进行过滤。

    g) Export-Package:Bundle对外公开的可被其他Bundle导入的包。

    h) Import-Package:Bundle需要导入的包。Bundle自身使用到的所有依赖类必须被Import进来,否则就会在OSGi容器启动时抛出NoClassDefFoundException或ClassNotFoundException异常。

    i) Require-Bundle:Bundle引用到的其他Bundle,应该将该属性值设置为被引用Bundle的Bundle-SymbolicName属性。

    j) Fragment-Host:指定被附属的Bundle的Bundle-SymbolicName,在前面部分已经进行了详细的讨论。

6) 让人纠结的Import-Package和Require-Bundle

OSGi 框架提供了两种导入包依赖的方式,即 Import-Package 和 Require-Bundle 。从下图中我们可以看出 Require-Bundle 会对整个 Bundle 产生依赖,也就是说 Bundle 所 Export 出的包都会被 A 加入到自己的类空间,而 Import-Package 只会对指定的包产生依赖关系。

基于 OSGi的企业级开发框架实践——认识OSGi和SpringDM_第4张图片

(图-5)

那到底应该使用Import-Package还是Require-Bundle呢?在大多数情况下,都应该使用 Import-Package 而不是 Require-Bundle 。 Import-Package 比 Require-Bundle 更利于 Bundle 的部署和版本维护,同时在查找类的时候有更高的效率。

好了,我认为OSGi最核心的原理也就是这些了,其实OSGi并非大家所想的那么高深莫测,其核心理论就是Classloader的隔离,这是OSGi的根本。只有在实现了这种机制的基础上才有可能存在细粒度的包控制,动态加载等被大家津津乐道的特性。

我们的重点是基于OSGi的企业级开发框架,我本人也不止一次的宣称自己是一名实用主义者,所以我不想花太多的笔墨沉迷于理论的纠缠中。毕竟我不是搞科研理论研究的,我是利益驱动的生物,我做的一切都必须给我带来利益。不过,做为一名严谨的中生代码农,我也不能完全的将理论抛在一边。实践是检验真理的唯一标准,但是没有真理的实践也是空洞和乏味的。这里我推荐2本书,一本是《OSGi in Action》,已经有中文版的发售,虽然从书名上看,它是一本更注重实战的书,但是观后我却感觉它更适合做为一本理论教学课本。另外一本是《Spring Dynamic Modules in Action》,该书虽然没有中文版本,但是也是一本不错的好书,由于Spring不再继续开发Spring DM了,所以这本书估计也不会再有人去翻译了。我也只翻译了前三章而作罢,因为Spring DM不复存在了。不过该书中的理论还是可以参考的,有助于我们理解Spring DM的工作原理。因为我们的基于OSGi的企业级开发框架就是在Spring DM基础上发展而来的。

二. Spring DM(Eclipse Gemini Blueprint)与OSGi

在我写这篇培训材料的时候,Spring DM还属于SpringSource。当时的最新版本是2.0.0.M1,从那时起到现在的近3年中,Spring DM却始终处于停滞的状态。直到近期,SpringSource终于宣布放弃Spring DM并将其代码捐献给了Eclipse,更名为Gemimi Blueprint。庆幸的是名字虽然变了,但是其核心代码功能却没有发生任何的变化。只不过包名由原来的org.springframework.osgi变更为org.eclipse.gemini.blueprint,如果要移植则可能需要一些小修改。也正因为如此,我当时写这些培训教材才不至于被遗弃,能够继续发挥它的作用。

化了很大的篇幅讨论了OSGi的原理和实现,原因很简单,因为只有了解了这些基础概念,才能对Spring DM有更深入的理解。因为SpringDM并不是OSGi的标准实现,它的运行必须依赖OSGi的标准容器,比如Equinox、Felix或是Knopflerfish等。SpringDM只是帮助我们用一种我们所熟悉的编程方式来完成OSGi服务的注册、查询、使用和监听。我们也可以将这些OSGi服务称之为Bean。所以SpringDM依赖于OSGi容器,没有OSGi容器,则SpringDM就无法运行,相反的,如果没有SpringDM,则OSGi服务将变的非常的难以使用,不利于OSGi技术的推广和发展。

1. 什么是Spring DM?

Spring DM的前身是Spring OSGi,目的是为了提供一套在OSGi环境下简化开发的模型(基于POJO的编程模型),将Spring现有的机制如DI和AOP无缝的引入到OSGi环境下。现在,Spring OSGi更名为Spring Dynamic Modules(Spring动态模型),更加明确了Spring DM的实现目标,即Spring Dynamic Modules致力于整合Spring框架和OSGi平台:前者有强大且非侵入性的企业级编程模型,而后者具有动态性、模块化的特点。将两家之长融会贯通,扬长避短,则更加能成为企业级开发领域的“大杀器”。

2. Spring DM能带来什么好处,为什么要使用Spring DM?

Spring框架提供一个轻量级的容器和一种非侵入编程模型,它基于DI、AOP和可移植服务抽取。OSGi服务平台提供一个动态应用程序执行环境,在这个环境里Bean(bundles)可以被即时地安装、更新或者移除。它同样对Bean的版本控制有着优秀的支持。

Spring Dynamic Modules使得编写一个可部署在OSGi执行环境下的Spring应用程序变得很轻松。由于Spring框架的简单易用和强大,Spring对OSGi的支持同样使得OSGi应用程序的开发变得更简单和高产。对企业级应用来说,Spring Dynamic Modules和OSGi平台的整合提供如下特性:

    1) 更好的模块间的应用逻辑隔离,这些模块具有运行时强制的模块边界;

    2) 同时部署同一个模块(或库)的不同版本的能力;

    3) 动态发现和使用系统内其他模块提供的服务的能力;

    4) 在运行着的系统中动态地安装、更新和卸载模块的能力;

    5) 使用Spring框架在模块内部和模块之间进行实例化、配置、整合组件的能力;

    6) 对于企业级开发人员来说Spring是一个简单而熟悉的编程模型,可以降低开发人员的入门门槛和学习成本;

相信OSGi和Spring的结合将为构建企业级应用程序提供一个方便易懂的编程模型。

3. OSGi Bundle和Spring的ApplicationContext上下文

在Spring中最核心的概念就是应用程序上下文(Application Context),在应用程序上下文中则包含了一些Bean定义。应用程序上下文可以被配置成具有层次关系,这样一个子应用程序上下文可以使用其父应用程序上下文中定义的Bean,反之却不行,这种层次关系与classloader之间的层次关系非常的相似。Spring中的输出器Bean和工厂Bean则用于将Bean的引用输出到应用程序上下文之外的客户端中,并且将服务的引用注入到客户端中。

在OSGi bundle和Spring应用程序上下文之间有很自然的紧密联系。使用Spring Dynamic Modules,一个处于Active状态的Bundle可以包含一个Spring 应用程序上下文,它负责在Bundle中实例化、配置、组装和装饰Bean。这些Bean即可作为OSGi服务输出并提供给其他Bundle引入,也可以注入其他OSGi服务输出的Bean引用。

Spring DM提供了一个OSGi bundle:org.springframework.osgi.bundles.extender(在Gemini Blueprint中不再是这个包名),这个Bundle负责为应用程序Bundle实例化Spring应用程序上下文。它的功能和用于Spring Web应用程序的ContextLoaderListener一样。一旦Extender Bundle被安装和启动,它就会寻找所有基于Spring的且已经在ACTIVE状态的Bundle,并且替它们创建应用程序上下文。另外Extender Bundle还监听Bundle启动事件和为所有后启动的基于Spring 的Bundle创建应用程序上下文。Extender Bundle异步地创建应用程序上下文。这样使得OSGi 服务平台能快速地启动,而且不会导致具有内部依赖性服务的Bundle在启动时死锁。一旦Bundle的应用程序上下文创建成功,应用程序上下文对象会自动通过OSGi服务注册表输出为可用服务。上下文发布为org.springframework.context.ApplicationContext接口。被发布的服务有一个名为org.springframework.context.service.name的服务属性,该属性值为Bundle在应用程序上下文里注册的标记名称。把应用程序上下文发布为服务主要用于方便测试和管理。在运行时通过getBean或类似操作来获取上下文对象并不被鼓励使用。获取在其他应用程序上下文里定义的Bean的最好方式是将该Bean在它的定义上下文里输出为OSGi服务,然后在要用到服务的上下文里导入该服务。通过这种方式使用服务注册表确保Bean仅能获取和所需服务类型兼容的服务,而且OSGi平台的动态性也得到了尊重。

4. 打包和部署基于SpringDM的OSGi应用程序

一个典型的Spring应用程序使用一个单独的应用程序上下文或者使用一个具有子上下文的父上下文,父上下文包含了服务层、数据层和域对象,子上下文包含了网络层组件。应用程序上下文可以由多个配置文件的内容集成。

部署一个应用程序到OSGi,更自然的结构是把应用程序打包成一系列的Bundles,每个Bundle使用一个单独的应用程序上下文,这些Bundle通过服务注册表进行交互。独立的子系统应该被打包成独立的Bundle或者一系列的Bundles。例如一个简洁的web应用程序应该被分成四个模块: Web Bundle,服务层Bundle,数据层Bundle,域模型Bundle。这样的一个应用程序如下图:

基于 OSGi的企业级开发框架实践——认识OSGi和SpringDM_第5张图片

(图-6)

这是一个网上引用比较频繁的示意图,我也拿来主义一下。在本例中,数据层Bundle生成了一个包括有许多内部组件的数据层应用程序上下文。其中两个Bean在服务注册表里被发布成了服务,从而使得两者变得在应用程序上下文之外公共可用。

服务层Bundle生成了一个包括有许多内部组件的服务层应用程序上下文。其中的一些组件依赖于数据层服务,它们从服务注册表里导入了这些服务。其中的两个Bean在服务注册表里被发布成了服务,从而使得它们在外部可用。

Web组件Bundle生成了一个包括许多内部组件的Web应用程序上下文。其中的一些组件依赖应用程序服务,并且从服务注册表里导入了这些服务。由于域模型Bundle仅提供域模型类型,但是不需要创建任何自己的组件,所以它与应用程序上下文没有任何关系。

5. 基于Spring的Bundle格式

Bundle要能被Spring DM识别并为其创建关联的上下文就必须要符合Spring DM定义的Bundle格式。当满足如下两个条件之一时,Spring Extender会识别一个Bundle是“基于Spring的”(Spring-Powered),并且会为之创建一个关联的应用程序上下文:

    1) Bundle路径包含了一个文件夹 META-INF/spring,在该文件夹下有一个或多个XML文件;

    2) META-INF/MANIFEST.MF 文件里包含了一个Spring-Context元数据。

基于“约定优于配置”的原则,通常情况下我推荐采用第一种方式,即在META-INF目录下创建一个Spring目录,并将所有的Spring Bean的XML配置文件放入其中,应用程序上下文(ApplicationContext)从这些配置文件中构造。一个推荐的做法是将上下文配置文件分成至少两个文件,习惯上分别命名为“模块名-context.xml”和“模块名-osgi-context.xml”。前者包含独立于OSGi之外的标准Bean定义。后者包含输出和导入服务的Bean定义。

6. 必须的Springframework Bundles和Spring DM Bundles

Spring Dynamic Modules工程提供了很多现成的Bundles,如果要正常使用Spring Extender这些Bundles必须安装到OSGi平台上。

    1) Extender Bundle本身org.springframework.osgi.extender;

    2) Spring Dynamic Modules的核心实现,org.springframework.osgi.core;

    3) Spring Dynamic Modules I/O 支持库bundle,org.springframework.osgi.io;

如果需要Web的支持,还必须包含以下Bundles:

    1) org.springframework.osgi.web;

    2) org.springframework.osgi.web.extender;

另外Spring Framework 提供了一些Bundles 必须安装。安装的最小集为:

    1) spring-core.jar (org.springframework.core) ;

    2) spring-context.jar (org.springframework.context) ;

    3) spring-beans.jar (org.springframework.beans) ;

    4) spring-aop.jar (org.springframework.aop) ;

另外如下几个支持包是必需的。这些库的OSGi版本和 Spring Dynamic Modules的分发包在一起:

    1) aopalliance;

    2) backport-util (如果在JDK1.4上运行);

    3) cglib-nodep (如果服务代理有类,而不仅仅是接口,那么一般都需要此包) ;

    4) commons-logging API (推荐为SLF4J版本) ;

    5) logging 实现包,例如 log4j;

关于Spring DM我就介绍到这里吧,我想最最核心的东西基本上都已经呈现出来了。更多的细节,您可以到 http://www.eclipse.org/gemini/官方主页上查看。

其实Spring DM的使用是非常简单的,因为对于一个熟悉Spring开发模型的人来说,很多东西都是老朋友了。即使是有点生疏的面孔也可以凭借自己的经验来快速的了解它。我们的基于OSGi的企业级开发框架之所以考虑采用Spring DM在很大的程度上就是考虑到Spring编程模型的深入人心。接下去,我们就将进入本系列文章的关键内容,基于OSGi企业级开发框架的实践,敬请关注!



作者:jacktan 发表于2013-2-15 16:40:21 原文链接
阅读:76 评论:0 查看评论

你可能感兴趣的:(开发,企业,osgi)