《OSGI实战》:第一章 揭开OSGi的面纱

 由  《OSGI实战》 发布

Java平台获得了巨大的成功。小到移动设备,大到企业应用,Java已被广泛用于开发各种应用程序,从而证明了它那经过深思熟虑的设计和持续不断的演进是成功的。但在Java的成功之下,除了支持普通的面向对象的数据封装之外,却并没有明确地支持构建模块化系统。

这对于你来说意味着什么?Java缺少对高级模块化的支持,如果仍然说它是一种成功,那么你可能想知道缺少这种支持是否会引起问题。为了弥补Java在模块化方面的不足,大多数管理得当的项目都必须针对具体项目要求去建立一整套技术。这些技术可能会包括如下方面。

  • 适应逻辑结构的编程实践。
  • 多个类加载器的技巧。
  • 进程内部组件间的序列化。

但是这些技术在编译时或执行时没有强制执行检查,因此本质上是脆弱的而且容易出错,从而最终会对应用程序生命周期的多个阶段造成不利的影响。

  • 开发——无法清楚、明确地将开发划分为独立的部分。
  • 部署——很难分析、理解并解决某些需求,这些需求来自一个完整系统的各个独立部分。
  • 执行——无法管理和改进一个正在运行的系统的各个组成部分,更不用说减小因管理和改进带来的影响。

这些问题都是Java可以解决的,并且很多项目也都通过使用上文提到的一些定制技术实现了这一点,但是这些方法都比较复杂。为了避开基础功能的不足,我们异常纠结。如果Java对模块化有明确的支持,那么你就可以不再被这些问题困扰,专注于你真正想做的——全力开发应用程序的功能。

欢迎进入OSGi服务平台。OSGi服务平台是专门针对Java对模块化支持不足的情况,由OSGi联盟定义的一个行业标准。作为其对模块化支持的延续,为了清晰地分离接口和实现,OSGi服务平台引入了一个面向服务的编程模型,被某些人称作“VM中的SOA”。本章将概述OSGi服务平台以及利用它如何使用基于接口的开发模型,创建模块化的、可管理的应用。

学习完本章后,你将了解OSGi技术在众多Java技术中所扮演的角色,了解为什么Java和其他与Java相关的技术没有OSGi技术所提供的具体功能。

1.1 OSGi的定义和目标

“OSGi是什么?”这个问题非常难回答。最简单的答案是:OSGi 是Java平台的一个模块化层。当然,接下来的问题是:“模块化是什么?”这里使用模块化或多或少是从传统计算机科学意义上来说的。在计算机科学里,软件应用程序的代码被分割为表示独立内容的逻辑单元,如图1-1所示。如果软件是模块化的,你可以简化开发,并且通过强化逻辑模块的界限来提高可维护性。第2章讨论更多模块化的细节。

图1-1 模块化是指将一个大系统从逻辑上分解为较小的互相协作的部分

模块化不是新概念,早在20世纪70年代就开始流行了。OSGi技术正在遍地开花,例如,它已经被作为Eclipse IDE和GlassFish应用服务器的运行环境。为什么现在它越来越流行呢?为了更好地理解OSGi为什么越来越重要,有必要先了解一些Java在创建模块化程序时的不足。了解了这些不足,你就能明白OSGi技术为何如此重要,以及它如何帮助你。

1.1.1 Java模块化的不足

Java 以面向对象的方式提供了某种程度的模块化,但它从未考虑支持粗粒度的模块化编程。尽管因为某些Java不想去解决的问题而批评它有失公允,但是Java的成功确实给那些需要更好的模块化支持的开发者带来了困难。 Java已经发展成为构建不同领域的各类应用程序的平台,范围从移动电话到企业应用。大多数应用都需要更广泛的模块化支持,或者至少应从模块化中受益。下面就来看一下Java模块化的不足。

低层代码的可见性控制

虽然Java提供了很多控制可见性的访问修饰符(例如public、protected、private和包级私有),但这些都是为了解决低层面向对象封装,而不是逻辑系统划分。Java用包来划分代码。如果需要代码在多个包之间可见,那么包内的代码须声明为public(或在继承时声明为protected)。有时应用程序的逻辑结构需要特定的代码分属于不同的包,但是这也意味着任何包间依赖必须声明为public,从而让代码都可见。通常这样会暴露具体的实现细节,使后续的升级更加困难,因为用户可能已经依赖于未公开的API。

代码清单1-1以 “Hello world!”程序为例进行说明:一个包提供公开接口,另一个包提供该接口的私有实现,第三个包编写主类。

代码清单1-1 Java面向对象封装的限制

这段代码的作者可能打算只允许第三方程序通过Greeting接口(1)与该程序交互。他们在Java文档、技术教程、博客或者邮件中可能都会提及这个要求,但却无法阻止第三方使用公有构造函数(2)构造一个新的GreetingImpl,正如(3)所完成的逻辑。

你或许会说,构造函数本不应该是公有的,也不需要把程序分割到多个包中。对于这个小例子来说,这当然是没问题的。但是,在实际应用中,类级的可见性加包在保证API的一致性时略显拙劣。如果私有实现细节可以被第三方开发者访问,那么当进行升级时,你除了考虑公有接口之外,还需要关注私有实现的变化。

这个问题的根源在于,虽然Java包表面上是通过包嵌套而使其具有逻辑关系,但其实不然。人们初学Java时通常有一个误解,认为父子关系的包被赋予了特殊的可见性。其实存在嵌套关系的两个包与两个没有嵌套关系的包是相同的。包嵌套主要是为了避免命名冲突,而仅为逻辑代码划分提供了部分支持。

所有这些表明在Java语言中,你通常必须要面对下面两种选择。

  1. 为了避免暴露非公有API而把不相关的类打包在一起,以致损害程序的逻辑结构。
  2. 为了保持程序逻辑结构而使用多个包,代价就是暴露非公有API,被不同包中的类访问。

两种选择都不尽如人意。

易错的类路径概念

Java平台也会妨碍好的模块化实践,罪魁祸首正是类路径(class path)。为什么类路径会为模块化带来困难呢?最大的原因是类路径隐藏了代码版本、依赖和一致性等特性。应用程序一般由各种版本的库和组件组成。类路径不关心代码版本——只返回找到的第一个版本。即使关注代码版本,但还是无法明确描述依赖关系。建立类路径的过程是烦琐易错的,你只是不停地添加类库,直到虚拟机不再报出找不到类的错误。

图1-2表明当多个JAR文件提供指定类集的时候 “类路径困境”问题会经常发生。即使每个JAR文件已经按照一个工作单元完成编译,但当在执行时合并时,Java 类路径并不关心组件的逻辑划分。这就会导致难以预料的错误,例如:一个JAR文件的类与另一个JAR文件中不兼容的类交互时会出现NoSuchMethodError异常。

图1-2 按照在类路径中出现的顺序来合并多个包含重叠类或包的JAR文件,而没有考虑包的一致性

在由独立开发的组件构成的大型应用中,依赖同一组件不同版本的情况并不少见,例如日志或者XML解析。而类路径会强制选择某个可能并不合适的版本。更糟的是,如果类路径中有同一个包的多个版本,不论是有意或是偶然,都会被Java当做是不同的包,并按照出现的顺序隐式地融合起来。

综上所述,类路径的方式不包含任何形式的一致性检查。无论获得的是系统管理员授权使用的哪些可用类,可能都只是接近你的预期。

部署和管理支持上的不足

Java还缺少对应用部署和管理的支持。在Java中存在对多个版本的依赖时,没有简单的方法来正确部署这些代码并执行。在部署之后更新应用和组件也会面临同样问题。

考虑一下希望支持动态插件机制的常见需求。唯一能实现这个合理需求的方法就是使用类加载器,但是这种方法是低级的,且容易出错。类加载器并不是应用开发者的常用工具,但是现今的许多系统却必须使用它。一个合理定义的Java模块层能够通过明确模块定义和提高代码划分的抽象级别,提供对这些特性的支持。

在更好地理解Java模块化不足的基础上,我们可以思考一下OSGi是否是你的项目的理想解决方案。

1.1.2 OSGi能帮助你吗?

除了最简单的应用程序外,几乎所有的应用程序都能够获益于OSGi提供的模块化特征,所以如果你正对是否应该了解OSGi犹豫不决,那么答案非常可能是肯定的。仍然不相信吗?在以下这些你可能遇到过的常见情况下,OSGi恰恰能够给你提供一些帮助。

当启动程序时,由于类路径不正确而导致ClassNotFoundException异常。OSGi可以帮助你先确保代码满足依赖关系,然后才允许执行代码。

由于类路径上一个依赖库的错误版本而导致程序执行时错误。OSGi会在要求的版本和其他约束条件方面对依赖集进行一致性检查。 当模块间共享类时导致类型不一致,更加具体地说,出现了像foo instanceof Foo ==false这样令人生畏的问题。使用OSGi,你就不必担心由于层次化的类加载模式隐含的限制。 将一个程序打包成逻辑上独立的JAR文件,并且只部署那些某个安装所需要的部分。这大致上阐述了OSGi的目的。 将一个程序打包成逻辑上独立的JAR文件,声明哪些代码可以被其他JAR文件访问,并且强调可见性。OSGi为JAR文件提供了新一级的代码可见性,这样你就可以指定哪些对外界是可见的,而哪些是不可见的。 为程序定义一个插件式的扩展机制。OSGi模块化特别适合提供强大的扩展性机制,包括支持执行时的动态性。 正如你所见,这些场景覆盖了很多用例,我们无法一一列举。OSGi简单而非侵入式的本质使得你用得越多,就发现使用它的方式越多。上文已经展示了标准Java 类路径的一些不足,现在我们该向你介绍OSGi了。

1.2 OSGi架构概览

OSGi服务平台由两部分组成:OSGi框架和OSGi标准服务(如图1-3所示)。OSGi框架是实现并提供OSGi功能的运行环境,OSGi标准服务定义了很多用于执行常见任务(如日志和首选项)的可重用API。

图1-3 OSGi服务平台规范分为两部分:OSGi框架和标准服务

OSGi框架和标准服务的规范由OSGi联盟(www.osgi.org)管理。OSGi联盟成立于1999年3月,是一个行业支持的非营利性组织。目前的OSGi框架规范是第4个版本,该版本是稳定的。基于该规范的技术已经应用于一系列大规模的行业应用,包括(但不限于)汽车、移动设备、桌面应用以及近来的企业应用服务器。

注意,OSGi曾经是Open Services Gateway Initiative(开放服务网关协议)的首字母缩写。该缩 写词突出了技术的传承,但是已经过时。第3版规范发布以后,OSGi联盟正式废弃了这个缩写,现在OSGi只是该技术的商标。

本书大部分内容都将讨论OSGi框架、OSGi框架的功能以及如何使用这些功能。因为OSGi框架提供了大量的标准服务,我们将在恰当的时候只讨论最相关和最有用的服务。对于未涉及的服务,你可以从OSGi规范中获得更多的信息。现在,我们将通过介绍OSGi框架的主要特点来继续介绍OSGi。

1.2.1 OSGi框架

OSGi框架在创建基于OSGi的应用时起着核心作用,因为它是应用的执行环境。OSGi联盟在OSGi框架规范中定义了框架的正确行为,这样就可以基于一个定义清晰的API进行编程。该规范也使核心框架可以有多种实现方式,从而可让你自由选择。此外,还有一些知名的开源项目,比如Apache Felix(http://felix.apache.org/)、Eclipse Equinox(www.eclipse.org/equinox/),以及Knopflerfish(www.knopflerfish.org/)。这最终会给你带来好处,因为你不需要受限于特定的供应商,而只需根据规范中定义的行为编程即可。这种感觉令人欣慰,就好比你知道进入世界上任何地方的麦当劳都可以买到同样的饭。 OSGi技术开始被应用到各个地方。你或许不知道,使用IDE做Java开发时,很可能你就已经用过OSGi了。Equinox OSGi框架实现是Eclipse IDE的底层运行时环境。此外,如果你使用GlassFish v3应用服务器,其实你也在使用OSGi,因为Apache Felix OSGi框架实现是它的运行时环境。多种应用场景表明OSGi框架具有很高的价值和灵活性,而这个框架是依据OSGi规范中定义的三个概念层设计的(如图1-4所示)。

  • 模块层——关注于打包和共享代码。
  • 生命周期层——关注于提供执行时模块管理和对底层OSGi框架的访问。
  • 服务层——关注于模块,特别是模块内的组件间的交互和通信。

    图1-4 OSGi分层架构

像常见的分层架构一样,OSGi的每一层都依赖于它的下层。因此,你可以只使用OSGi的下层而不用它的上层,但反之则不然。接下来的三章将会详细地讨论这三层,在这里我们将概述每一层。 模块层 模块层定义了OSGi模块的概念,并将之称为一个bundle。bundle是一个包含元数据(关于数据的数据)的JAR文件,由类文件和相关资源组成,如图1-5所示。bundle通常并不是打包到一个JAR文件中的整个应用程序;相反,它们是构成一个特定应用程序的多个逻辑模块。bundle比标准的JAR文件更强大,因为你可以明确地声明哪些包对外可见(即导出包)。从这个意义上说,bundle扩展了Java的普通访问修饰符(public、private和protected)。

图1-5 bundle中包括代码、资源和元数据

相比标准JAR文件,bundle的另一个重要优势是你可以明确声明依赖哪些外部包(即导入包)。明确声明bundle的导入包和导出包的主要好处是,OSGi框架可以自动地管理和验证它们的一致性;这个过程称为bundle解析,包括使导出包与导入包相匹配。bundle解析确保bundle版本和其他方面约束的一致性,我们将在第2章详细介绍。

生命周期层

生命周期层定义了在OSGi框架中是如何动态安装和管理来的。这好比你建造一座房子,模块层是基础和结构,而生命周期层是供电线路,它使得房子里所有的东西运转起来。

生命周期层的存在有两个目的。一方面,在应用程序的外部,生命周期层精确地定义了bundle生命周期的操作(安装、更新、启动、停止和卸载)。这些生命周期的操作使得你可以用一种定义明确的方式动态地提供、管理和改进你的应用程序。这意味着可以安全地在框架中安装和卸载bundle,而不需要重启应用进程。

另一方面,在应用程序的内部,生命周期层定义了bundle如何访问它们的执行环境。执行环境为bundle提供了一种与OSGi框架交互的方式和执行时的一些便利。生命周期层整体的方法非常强大,支持创建可从外部(和远程)管理的应用程序,或者完全自我管理的应用程序(或者两者的任意组合)。

服务层

最后,服务层支持和促成了一个灵活的应用编程模型。该模型包含了一些因面向服务的计算而流行起来的概念(尽管面向服务的计算是在这些概念已经成为OSGi框架的一部分后才开始流行起来的)。主要的概念涉及面向服务的发布、查找和绑定交互模式:服务提供者将服务发布到服务注册中心,然后服务客户端通过搜索服务注册中心,查找可供使用的服务(如图1-6所示)。当今,这种面向服务的架构(SOA)大部分都与Web 服务有关;但是OSGi服务则属于VM的一部分,因此有些人把它称为VM中的SOA。

图1-6 面向服务的交互模式。

服务提供者将服务发布到注册中心,然后服务请求者可以从注册中心那里看到可供使用的服务。

OSGi服务层是符合直觉的,因为它提倡一种基于接口的开发方式,该方式是一种公认的优秀编程实践。确切地说,它提倡接口与实现之间的分离。OSGi服务是Java接口,表示服务提供者和服务客户端之间的一种概念上的合约。服务层于是很轻量,因为服务提供者只是一些通过直接的方法调用来访问的Java对象。另外,服务层通过基于服务的动态性(服务可以在任何时刻出现或消失)来扩展生命周期层基于bundle的动态性。结果是产生了一种支持模块化和灵活性的编程模型,避免了过去僵化又脆弱的方式。

这听起来还算不错,那如何将这三层组合起来,如何使用它们,如何在它们之上创建应用程序呢?在接下来的两章将利用一些小的示例来说明如何将这些层组合起来。

1.2.2 将它们结合起来

OSGi框架由多层组成,但如何在应用程序开发中使用这些层呢?为了说明得更清楚,我们先介绍以下创建OSGi应用程序的通用方法。

  1. 设计应用,将它分解为一些服务接口(普通的基于接口的编程)和这些接口的客户端。
  2. 使用你选定的工具和方法来实现服务提供者和客户端组件。
  3. 将服务提供者和客户端组件打包为独立的JAR文件(通常要这样做),然后用合适的OSGi元数据扩展每个JAR文件。
  4. 启动OSGi框架。
  5. 安装和启动所有来自步骤3的JAR文件。

如果你已经采用了基于接口的方式,那么就会对OSGi的方式感到熟悉。主要的不同是如何找到你的接口实现(也就是服务)。通常,可以实例化实现,传递引用以初始化客户端。在OSGi中,服务会将它们自身发布到服务注册中心,然后你的客户端从服务注册中心寻找可使用的服务。那些bundle安装和启动之后,应用将正常地启动和执行,但却具有几个优点。应用底层的OSGi框架提供更严格的模块化和一致性检查,并且它的动态本质会展现多种可能性。

如果你不想或者不能使用基于接口的方法来开发,也不要因此而焦虑。OSGi框架的前两层仍然提供了很多的功能;事实上,OSGi框架的大部分功能都位于这两层中,所以请继续往下看。不如来看一些代码吧。

1.3 Hello, world!

由于OSGi的功能分为之前所述的三层(模块层、生命周期层和服务层),因此我们将展示三个不同的“Hello, world!”示例来阐明每一层的功能。

1.3.1 模块层示例

模块层并不涉及代码的开发,而是关注于将你的代码打包成bundle。在开发过程中,你需要了解那些和代码相关的问题,但总的来说,准备模块层代码需要向项目生成的JAR文件中添加打包元数据。例如,假设你想共享下面这个类。

在构建的过程中,需要编译源代码并将所产生的类文件放入JAR文件中。为了使用OSGi模块层的功能,必须在JAR文件中的META-INF/MANIFEST.MF文件中增加一些元数据信息,如代码清单1-2所示。

代码清单1-2 增加元数据信息

第一行指明了OSGi元数据的语法版本。第二行指定一个可读的bundle名称,此名称并未严格限制必须设置。第三行和第四行分别是bundle的符号名和版本号标识符。最后一行指明与其他bundle共享的包。

在本例中,大多数元数据都用于标识bundle。重要的部分是Export-Package声明,因为通过该声明可以扩展普通JAR文件的功能,你可以明确声明JAR文件中哪些包对使用者是可见的。本例中只有org.foo.hello包的内容是对外可见的;即使JAR文件内还包括其他的包,这些包对外也将是不可见的。这意味着,你在运行应用程序时,其他模块将不会有意或无意地依赖于你的模块中没有明确公开的包。

使用其他模块所提供的共享代码,也要通过在JAR文件中添加元数据来实现。此时,你需要使用Import-Package语句明确声明客户端JAR文件中的代码所需要的外部包。下面是代码片段:

本例中,最后一行指定了所依赖的一个外部包。 欲看该实例的运行效果,请到本书配套源码中的chapter01/greeting-example/modularity/目录下,输入ant进行构建,并使用java –jar main.jar命令来运行该实例。尽管这个实例很简单,但它说明了基于已有的JAR文件构建OSGi bundle是一个合理的非侵入式过程。另外,还有一些工具可以帮助你生成bundle的元数据,我们将在附录A中进行说明;然而,实际开发中,除了平常用来创建JAR文件的工具外,我们并不需要什么特殊的工具来创建bundle。第2章将会针对OSGi模块化的有趣细节进行深入讨论。

1.3.2 生命周期层示例

在上一小节中,你可以看到利用非侵入的方法向已有的JAR文件中添加元数据可以发挥OSGi功能上的优势。这种简单的方法基本上满足了大部分库的重用需求,但有时你需要或是想要进一步地满足一些特殊需求,或是想要应用OSGi的一些更高级特性。生命周期层将会带你走入更深的OSGi世界。

也许你想要创建一个模块来执行一些初始化任务,例如启动一个后台线程或者初始化一个驱动程序;在生命周期层可以做到这一点。bundle中可以将一个指定的类声明为激活器(activator),作为该bundle进行自身生命周期管理的钩子。虽然第3章将对bundle的整个生命周期进行讨论,但首先还是通过一个简单的例子来初步了解一下bundle的生命周期。代码清单1-3将之前的Greeting类扩展为一个单例实例的类。

代码清单1-3 扩展的greeting实现

代码清单1-4实现了一个bundle的激活器,在bundle启动时,用来初始化Greeting类的单例,bundle停止时,用以清除该单例。使用者现在可以使用预先配置好的单例,而不再需要创建自己的实例。

bundle的激活器类必须实现一个简单的OSGi接口,本例中该接口由start()和stop()两个方法组成。在执行过程中,bundle启动时OSGi框架将构造该类的一个实例,并调用start()方法,bundle停止时框架将调用stop()方法。(这里所说的有关bundle的启动和停止,将在第3章详细地说明。)由于bundle处于运行状态期间,框架所使用的是该bundle的同一个激活器实例,因此可以在start()方法和stop()方法之间共享其成员变量。

代码清单1-4 greeting实现的OSGi bundle激活器

你也许想要知道在start()方法和stop()方法中的BundleContext类型的参数有什么作用。该参数是bundle在运行过程中访问OSGi框架的桥梁。正因为有了该上下文对象,模块才可以使用OSGi模块层、生命周期层和服务层的所有功能。总之,对于大多数bundle来说,该对象都相当重要,我们将在稍后讨论生命周期层时再对该对象进行详细介绍。上述例子的重点在于,bundle可以通过简单的方法来控制它们的生命周期层,并且可以访问底层OSGi框架。

当然,只创建一个bundle激活器实现类是不够的,你还要将这件事情通知给OSGi框架。幸好这很简单。如果你是在把一个已有的JAR文件转化成模块,那么必须向原有工程中添加激活器的实现类,因此,该实现类也应包含在JAR文件中。如果你是从头开始创建一个bundle,那么需要编译该激活器实现类,并且将编译结果放到JAR文件中。你还要在JAR文件的清单文件中添加额外的元数据,告诉OSGi框架bundle激活器类的位置。在本节的例子中,要在JAR清单文件中加入如下元数据: 注意,你还需要导入org.osgi.framework包,因为bundle 激活器依赖于该包。欲看该实例的运行效果,请到本书配套源码中的chapter01/greeting-example/lifecycle/目录下,输入ant进行构建,并使用java –jar main.jar命令来运行该实例。

我们已经介绍了如何使用模块层,利用已有的JAR文件创建OSGi bundle,以及通过使bundle在其生命周期可见,来使用框架的功能。本节的最后一个例子展示了由OSGi提倡的面向服务的编程方法。

1.3.3 服务层示例

如果你在开发中使用面向接口的开发方式,那么你肯定会习惯OSGi面向服务的开发方式。下面用Greeting接口的例子来说明:

对于任何给定的Greeting接口的实现,当sayHello()方法被调用时,将会显示问候语。通常,服务表示服务提供者与未来客户之间的一种契约;如同规范一样,契约的语义常常用独立的可读文档来描述。前面的服务接口示例表示所有Greeting实现的语法契约。契约的概念是必须的,这样能够确保所有客户在使用Greeting服务时都能得到期望的功能。

客户端并不了解提供Greeting服务的具体实现细节。例如,一个实现可能会以文本方式打印问候语,而另一个实现可能在一个GUI对话框中显示问候语。代码清单1-5描述了一个简单的基于文本的实现。

代码清单1-5 Greeting接口的实现

你可能会这样想:服务接口或代码清单1-5中并没有任何东西表明你正在定义一个OSGi服务啊。确实是这样的。这就是上文说如果你使用过面向接口的开发方式,那么肯定会习惯OSGi面向服务的开发方法的原因,你的大部分代码都将与原来的代码相同。开发方法将有两处微小的不同:一是如何使服务的实例在应用程序的其他部分可用;二是如何使应用程序的其他部分发现这个可用的服务。

所有服务的实现最终都被打包到一个bundle中,且该bundle必须能够在其生命周期可见,以使其能够注册成服务。这意味着你需要为示例服务创建一个bundle激活器,如代码清单1-6所示。

代码清单1-6 用OSGi bundle激活器完成服务注册

这时,在start()方法中使用OSGi提供的bundle上下文,将Greeting的实现在服务注册中心注册为服务,而不是将其存储为一个单例。需要提供的第一个参数是服务实现的接口名;第二个参数是实际的服务实例;第三个参数是服务属性。在stop()方法中,你能够在bundle停止前将服务的实现注销;但是在实际使用过程中不需要这样做。当bundle停止时,OSGi框架会自动注销所有该bundle已注册的服务。

你已经知道如何注册服务,但是如何发现这个服务呢?代码清单1-7是一个简单的客户端程序,但是它没有处理服务缺失以及潜在的竞态条件的机制。第4章将会讨论一种更健壮的访问服务的方法。

代码清单1-7 用 OSGi bundle激活器完成服务发现

注意,访问OSGi中的服务需要两步。第一,从服务注册表中检索间接的服务引用 。第二,使用这个间接引用去访问服务对象的实例 。服务的引用能够被安全地赋值给成员变量,但是持有一个服务对象实例的引用通常不是一个好方法,因为服务可能动态地注销,这将导致持有服务的过时引用阻止垃圾回收器回收卸载的bundle。

服务的实现和客户端都应该打包进不同的bundle JAR文件中。每个bundle的元数据声明了相应的激活器,但是服务实现是导出org.foo.hello包,而客户端则将其导入。注意,客户端bundle的元数据仅需要声明将Greeting接口包导入即可,它没有直接依赖服务的实现。这就使在不重启客户端bundle的情况下,动态的交换服务实现包变得更容易。欲看该实例的运行效果,请到本书配套源码中的chapter01/greeting-example/service/目录下,输入ant进行构建,并使用java –jar main.jar命令来运行该实例

我们已经看了一些例子,相信你已经更好地理解了OSGi的各层是如何搭建在前一层基础上的。构建应用程序时,OSGi的每一层都能提供一些额外的功能,因为OSGi技术足够灵活,使你能够根据具体的需求来加入这些功能。如果在你的项目中仅仅想要更好的模块化,那么使用模块层。如果想要初始化模块以及与模块层交互,那么使用模块层和生命周期层。如果想使用动态的、面向接口的开发方式,那么使用所有三层。一切由你自己选择。

1.3.4 场景设置

在接下来的三章中,我们将使用一个简单的绘图程序,来帮助介绍OSGi框架中每一层的概念,绘图程序的用户界面如图1-7所示。介绍该绘图程序的目的并不是要介绍它的功能,而是要演示一些常见问题和最佳实践。

图1-7 简单绘图程序的用户界面

从功能的角度来看,该绘图程序仅能允许用户绘制不同的图形,例如圆形、正方形、三角形。这些图形会被填充预定义的颜色。可绘制的图形以按钮的形式显示在主窗口的工具栏上。为了绘制图形,用户需先从工具栏中选择相应的图形,然后在画布的任意地方点击即可。在画布中点击多次重复绘制同样的图形。用户可以通过拖动将图形移动到新的位置。这听起来非常简单。在介绍执行时的动态性时,通过一个图形程序来演示这些概念的真正价值就会显现出来。

我们已经大致介绍完了OSGi框架,在准备探究细节之前,将通过讨论与OSGi相似和相关的技术来了解它的来龙去脉。尽管没有Java技术与OSGi完全吻合,但技术总有相通相似处,在深入理解OSGi之前了解它们之间的关联性是很有必要的。

1.4 OSGi的相关技术

提及OSGi时通常也会提到很多其他技术,但在Java世界中OSGi具有其非常独特的位置。近些年来,没有哪项技术能解决OSGi所能解决的所有问题,但会有重叠、补充或者分支。尽管不大可能介绍OSGi与每项可想到的技术有何关联,但我们仍然会大致按照时间顺序介绍一些与OSGi紧密相关的技术。通过阅读本章内容,你应该能够有一个清晰的判断:OSGi是否可以取代你所熟悉的一些技术,或者是对它们的补充?

1.4.1 Java EE

Java Enterprise Edition(Java EE,也就是之前的J2EE)的历史可以追溯到1997年。最初,Java EE和OSGi分别面向计算领域的两极(前者面向企业应用市场而后者面向嵌入式应用市场)。直到过去几年,OSGi技术才真正开始扎根于企业应用领域。

总的来看,Java EE API栈与OSGi并没有直接的关系。在Java EE领域,企业JavaBeans(EJB)规范也许和OSGi最为接近,EJB定义了一个组件模型和包的组织形式。但是EJB中的组件模型侧重于为企业应用提供一种标准的实现方式,这些企业应用必须定期处理持久化、事务及安全等问题。EJB在部署描述及包的组织形式方面相对简单,对组件的完整生命周期也没有表述,同时也不支持清晰明确的模块化概念。

OSGi已经进入Java EE领域,OSGi作为Java EE现有技术的基础为其提供了一个非常完备的模块层。因为这两个领域相互忽略了很久,将已经存在的Java EE概念向OSGi转移时将面临一些挑战,主要是两者采用了不同的类加载机制。但是,目前仍然取得了很大进展。现在OSGi在所有主流应用服务器中都扮演了重要角色,例如IBM的WebSphere,Red Hat的JBoss,Oracle的GlassFish,ObjectWeb的JOnAS和Apache的Geronimo。

1.4.2 Jini

Jini是一项经常被忽视的Java技术,在概念上Jini绝对称得上是OSGi的同胞兄弟。Jini要解决的问题与OSGi一样,都植根于拥有大量互联设备的网络环境。

Sun对Jini的研发始于1998年,目标是通过一种易扩展的动态服务组的方式来管理网络互联环境。Jini引入了服务提供者、服务消费者以及服务查找注册的概念。所有这些听起来都与OSGi相似,而区别在于Jini面向分布式系统。在典型的Jini场景中,消费者使用一种远程过程调用机制(类似RMI),通过某种形式的代理连接到客户端。服务查找注册本身也是一个可以远程访问的联合服务。Jini设定的场景是多个VM进程之间实现远程访问,而OSGi设定的场景是所有的一切都发生在单个VM进程中。然而与OSGi形成鲜明对比的是,Jini没有定义任何模块化机制,并且依赖于RMI的执行时代码加载特性。开源项目Newton是一个例子,它把OSGi和Jini整合到一个框架之中。

1.4.3 NetBeans

作为面向Java的IDE及应用程序平台,NetBeans在模块化设计方面有着悠久的历史。Sun公司于1999年收购NetBeans并且一直对其进行改进。

NetBeans平台与OSGi有很多的相似之处。NetBeans定义了一个非常成熟的模块层,同时也提倡基于接口的编程方式,并且使用了查找模式,该模式类似OSGi中的服务注册。由于OSGi专注于嵌入式设备和动态化机制,而NetBeans平台起初仅是服务于IDE的一个上层实现。最终该平台演化为一个独立的工具,但是NetBeans专注于成为一个完整的GUI应用平台,这包括对文件系统、窗口系统以及其他更多系统的抽象。NetBeans和OSGi很难相提并论,即便二者如今已不相上下,这或许是因为OSGi更加专注。

1.4.4 JMX

JMX(Java Management Extension)由JCP(Java Community Process)于2000年作为JSR003发布。早期JMX被认为是可以和OSGi相提并论的技术。JMX这门技术用于管理和监控远程的应用程序、系统对象和设备。基于该目的,JMX定义了一个服务器和一个组件模型。

但在真正意义上JMX还不能和OSGi相比拟,它仅仅是OSGi的一个补充,因为JMX可用来管理和监控OSGi框架以及其所包含的bundles和服务。为何在一开始将JMX与OSGi放在一起进行比较?大概有三个原因:JMX组件模型非常通用,所以一般用它来构建应用程序。JMX规范中定义了一套机制用于将动态代码加载到服务器中;某些早期的使用者推动JMX朝这个方向发展(其中JBoss是一个主要的推动者)。通过采纳并扩展JMX,使其成为JBoss应用服务器中的模块层。(从JBoss 5开始取消了JMX),如今对于JMX来说,它不会(也不应该)再和一个模块化系统相混淆了。

1.4.5 轻量级容器

2003年左右,轻量级或者控制反转(IoC)容器开始出现,如Pico Container、Spring和Apache Avalon。这一系列IoC容器背后的主要思想是,通过优先使用接口避免使用具体类型的方式简化组件的配置和组装。与Ioc容器联合使用的还有依赖注入技术,该技术使得组件依赖于接口类型,接口的实现被注入到组件的实例中。OSGi服务进一步改进了类似的基于接口的方式,但它采用了一种服务定位器(service-locator)模式,并以此来打破组件之间对具体实现的依赖,这与Apache Avalon类似。

与此同时,Service Binder项目为OSGi组件提供了依赖注入框架。由此不难理解为何会出现对二者的比较。无论如何,OSGi对基于接口的服务以及服务定位器模式的使用远超前于当前趋势,没有哪一项技术能够像OSGi那样提供一种完备的动态模块层。现在IoC厂商正经历着重大转变,将他们的基础架构向OSGi框架迁移,比如VMware(之前的SpringSource)在OSGi Blueprint规范(将在第12章讨论)上所做的工作。

1.4.6 Java 业务集成

JBI (Java Business Integration)是由JCP开发的,并于2005年发布,旨在构建标准的SOA平台。基于该平台可构建企业应用集成(EAI)和B2B(Business-to-Business)集成方案。

在JBI中,如果将插件组件集成到JBI框架里,那么这些插件组件就可以提供服务或者使用服务。与OSGi类似,这些组件并不直接与服务交互,取而代之的是使用标准的基于WSDL(Web Services Description Language)Web服务描述语言的消息实现间接通信。

JBI使用基于JMX的方法管理组件的安装过程和生命周期,并为所有组件定义了包的封装结构。因为和OSGi架构具有内在的相似性,JBI很容易被误解为与OSGi的作用相同。事实恰恰相反,JBI具有极其简单的模块化机制,主要解决的是将基本组件集成到框架的问题。不难看出,对于JBI来说,使用OSGi更加成熟的模块化会更有意义,这也最终导致了Sun公司的Fuji项目和Apache的ServiceMix项目的出现。

1.4.7 JSR 277

2005年,Sun发布了为Java定义模块化系统的JSR 277 (“Java模块化系统”),希望通过JSR 277为Java平台定义一种模块化框架、包的封装结构、仓库系统等。从OSGi联盟的角度来说,这是一个重新发明轮子的经典案例,因为一切都是从头开始,构建工作并没有以从OSGi获得的经验为基础。

2006年,众多OSGi的支持者急切要求引入JSR 291(标题为“Java的动态组件支持”),这是为了将OSGi技术适当地引入JCP标准化;其目的有两个:建立两个社区之间联系的纽带,同时也确保JSR 277中考虑对OSGi技术的集成。JSR 291完成的速度非常快,主要是因为它基于OSGi R4规范,并导致R4.1规范的发布。在这期间,OSGi技术继续保持上涨的势头。而直到2008年,JSR 277的进展仍然非常缓慢,直到被无限期地搁置。

1.4.8 JSR 294

2006年,JSR 294(标题为“改进的Java编程语言的模块化支持)是作为JSR 277的分支被引入的。作为JSR 294的重要目标,为了实现模块化,Java语言本身需要做出相应的改变。JSR 294的最初想法是将超级包(superpackage)的概念引入Java语言——一个包包含多个包。

超级包的规范陷入了细节的泥潭中,直到后来要将一个模块访问修饰符关键字添加到语言中而被拆解。简化的处理方式最终也导致了JSR 294被彻底抛弃,并在2007年被重新合并到JSR 277。2008年,当JSR 277被搁置的可能性越来越大后,JSR 294又被重新提出,用于声明一个模块级的访问修饰符。

由于JSR 277被搁置,Sun公司引入了一个内部项目,称为Project Jigsaw,用于将JDK模块化。在Sun被Oracle被收购后,Jigsaw仍然在不断演进。

1.4.9 SCA

SCA(Service Component Architecture)是于2004年成立的一个行业协作组织,并在2007年催生了最终的规范。SCA定义了一个与OSGi类似的面向服务的组件模型。在该模型中,组件提供并请求服务。该组件模型相对来说更加高级,因为它为完全递归组件模型定义了复合组件(composite component)。复合组件是由其他组件组成的组件。

SCA旨在成为一个用于声明式组合组件的组件模型,在实现的过程中,使用了多种技术,如Java、 BPEL (业务处理执行语言)、EJB和C++,另外还通过多种绑定方式实现了集成,如SOAP/HTTP、 JMS (Java Message Service)、JCA(Java EE Connector Architecture),以及IIOP(Internet Inter-Orb Protocol)。SCA明确定义了一个标准的包结构,但其并非像OSGi那样提供了一个复杂的组件层。SCA规范允许使用其他包结构,这样一来就可以在基于Java的SCA实现中使用OSGi进行包封装,并将其作为模块层。Apache Tuscany和Newton是使用OSGi实现SCA的例子。除此以外,bundle可以被用于实现SCA组件的类型,而SCA也可作为一种机制使用,以提供对OSGi服务的远程访问。

1.4.10 .NET

虽然微软的.NET技术(发布于2002年)与Java并没有直接的关系,但还是值得一提。.NET在很大程度上受到了Java的启发,并在此基础上做过许多改进,这类似于OSGi对Java的改进。不仅仅是将Java作为榜样,微软也从处理DLL的痛苦历史中吸取了教训。最终的结果就是,.NET中使用了具有模块化性质的集合(assembly)概念,类似于OSGi中的bundle。所有的.NET代码都会被打包到集合中,并采用DLL或EXE文件的形式。集合提供了对其所包含代码的封装机制;internal访问修饰符表明代码的可见性局限于集合内部,而非外部可见。集合还包含了元数据,用于描述对其他集合的依赖关系,但整个模型的灵活性远不及OSGi。因为集合之间的依赖性取决于具体的集合版本,所以OSGi中提供者的可替换性是无法实现的。

在执行的过程中,集合被载入应用域,并只能在卸载整个应用域时被卸载。这就导致难以实现OSGi的高度动态和轻量级特性。因为当多个集合被载入到同一个应用域后,只能在同一时刻被卸载。虽然将集合载入到不同的域中也是可以的,但是跨域的通信必须通过进程间通信协作实现,与此同时,集合之间的类型共享也是非常复杂的。目前已开展了一些研究工作,目的是为.NET平台构建类似于OSGi的环境,但.NET和Java平台生来不同,这就导致二者并没有太多共性。尽管如此,在该领域中,.NET对于标准Java的促进作用还是有目共睹的。

1.5 小结

本章是本书其他内容的基础,介绍了如下内容。

  1. Java平台是非常好的应用开发平台,但它对模块化的支持很大程度上受限于细粒度的面向对象机制而不是粗粒度的模块化特征,而后者却是工程管理中需要的。
  2. 为了弥补Java在模块化上的不足,OSGi服务平台通过OSGi框架创建了一种强大灵活的解决方案。
  3. OSGi采用声明、基于元数据的方式,充分利用了OSGi复杂的模块化功能,提供了一种非侵入的使用方法,这种方法不修改或少修改代码就可以完成对项目的封装。
  4. OSGi框架为了简化管理,定义了一套可控的动态模块化生命周期。
  5. OSGi遵循良好的设计原则,提供了基于接口的编程方式,从而可以分离接口与实现。

在对Java的局限性和OSGi能力有了高层次的认识后,第2章通过深入分析模块层的细节开始我们的技术探险之旅。这是OSGi世界中其他一切的根基。


罗俊杰 2013-11-29 12:05

回复ll_123: 确实如此。调试时也方便不少,模块化做好,实现和接口分离,实现上做修改时不影响其他所有模块。

顶(0) 踩(0) 回复

ll_123 2013-11-28 16:44

做过大项目的的兄弟们可能会明白模块化的重要性。多个模块相互调用和数据相互访问的时候。模块化的优势就体现出来了。直接暴露实现类给别人,先不说代码的安全性,单说调用接口的人看着一堆方法都会觉得这定义接口的人水平实在是一般。再说小项目,可能你在一个两个小项目中体验不出来。当你有十个二十个小项目部署在同一web容器上的时候(我说的是假设),当你想重启其中一个项目的时候你不得不从其所有项目,因为没有模块化。这个时候模块化的优势就体现出来了,项目开发的越久,就越能体现模块化的好处。以上属于个人观点,大家随意喷。

顶(0) 踩(0) 回复

泪雨迷情 2013-11-25 10:04

不错,值得一看

顶(0) 踩(0) 回复

admott 2013-10-12 10:16

回复yanbin: 对于你问题的回答:<br>1. 你的项目很小,全是一个人做,所以没有接口隔离的困扰;<br>2. 你的项目生命周期都很短,发了一个版本估计就没第二个版本了,所以没有因为迭代而接口困扰;<br>3. 你的项目都是静态的,编译成啥样子就是啥样子,所以import 肯定会在写代码的时候就能解决掉;<br>4. 你的项目都没有部署,直接把整个appserv 和 app 扔给客户,所以没有动态import的困扰。<br><br>结论:综上所述,在微型项目中,你的理解是正确的,OSGi 应用在微型项目上确实有点鸡肋。

顶(0) 踩(0) 回复

yanbin 2013-10-11 17:12

可能由于水平有限,对文中所提的 java两个问题请教下。<br>1、接口的访问级别。我觉得首先如果是同一个project内部,访问接口能看到实现,问题其实不是很大。如果是远程的访问。只是会提供接口API的jar,不会有实现存在。我觉得访问的级别够用。还这没有在项目中因这个困扰<br>2、类路径问题, java开发程序的时候,类路径是由import指定的,在指定的过程中一般都是开发着知道具体的类路径使用。就算引错了类,在调用类方法也很容易报错,编译器就会提示错误。一般不会在起服务的时候才发现NoSuchMethodError。<br>个人觉得这两个在项目过程中影响很小,如果OSGI 为了解决这两个问题 着实很鸡肋。<br>但是据我了解OSGI的功能 远远不止解决这种问题。希望文中的论证能更贴近现实项目中OSGI能解决的问题,做更有力的论证。

顶(0) 踩(0) 回复

罗俊杰 2013-09-05 12:52

回复程序猿: 全部在这http://osgi.com.cn/article/tagged/4002938

顶(0) 踩(0) 回复

程序猿 2013-09-05 11:06

期待下一章

顶(0) 踩(0) 回复

松仁战玉米 2013-07-04 15:19

支持一个。

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