“OSGi是什么?”这个问题非常难回答。最简单的答案是:OSGi 是Java平台的一个模块化层。当然,接下来的问题是:“模块化是什么?”这里使用模块化或多或少是从传统计算机科学意义上来说的。在计算机科学里,软件应用程序的代码被分割为表示独立内容的逻辑单元,如图1-1所示。如果软件是模块化的,你可以简化开发,并且通过强化逻辑模块的界限来提高可维护性。第2章讨论更多模块化的细节。
图1-1 模块化是指将一个大系统从逻辑上分解为较小的互相协作的部分
模块化不是新概念,早在20世纪70年代就开始流行了。OSGi技术正在遍地开花,例如,它已经被作为Eclipse IDE和GlassFish应用服务器的运行环境。为什么现在它越来越流行呢?为了更好地理解OSGi为什么越来越重要,有必要先了解一些Java在创建模块化程序时的不足。了解了这些不足,你就能明白OSGi技术为何如此重要,以及它如何帮助你。
Java 以面向对象的方式提供了某种程度的模块化,但它从未考虑支持粗粒度的模块化编程。尽管因为某些Java不想去解决的问题而批评它有失公允,但是Java的成功确实给那些需要更好的模块化支持的开发者带来了困难。
Java已经发展成为构建不同领域的各类应用程序的平台,范围从移动电话到企业应用。大多数应用都需要更广泛的模块化支持,或者至少应从模块化中受益。下面就来看一下Java模块化的不足。
低层代码的可见性控制
虽然Java提供了很多控制可见性的访问修饰符(例如public
、protected
、private
和包级私有),但这些都是为了解决低层面向对象封装,而不是逻辑系统划分。Java用包来划分代码。如果需要代码在多个包之间可见,那么包内的代码须声明为public
(或在继承时声明为protected
)。有时应用程序的逻辑结构需要特定的代码分属于不同的包,但是这也意味着任何包间依赖必须声明为public
,从而让代码都可见。通常这样会暴露具体的实现细节,使后续的升级更加困难,因为用户可能已经依赖于未公开的API。
代码清单1-1以 “Hello world!”程序为例进行说明:一个包提供公开接口,另一个包提供该接口的私有实现,第三个包编写主类。
代码清单1-1 Java面向对象封装的限制
package org.foo.hello; Greeting.javapublic interface Greeting { //?简单接口 void sayHello();}------------------------------------------------------------------------------package org.foo.hello.impl; GreetingImpl.javaimport org.foo.hello.Greeting;public class GreetingImpl implements Greeting { final String m_name; public GreetingImpl(String name) { //?接口实现 m_name = name; } public void sayHello() { System.out.println("Hello, " + m_name + "!"); }}------------------------------------------------------------------------------package org.foo.hello.main; Main.javaimport org.foo.hello.Greeting;import org.foo.hello.impl.GreetingImpl;public class Main { public static void main(String[] args) { Greeting greet = new GreetingImpl("Hello World"); //?主函数 greet.sayHello(); }}
这段代码的作者可能打算只允许第三方程序通过Greeting
接口?与该程序交互。他们在Java文档、技术教程、博客或者邮件中可能都会提及这个要求,但却无法阻止第三方使用公有构造函数?构造一个新的GreetingImpl
,正如?所完成的逻辑。
你或许会说,构造函数本不应该是公有的,也不需要把程序分割到多个包中。对于这个小例子来说,这当然是没问题的。但是,在实际应用中,类级的可见性加包在保证API的一致性时略显拙劣。如果私有实现细节可以被第三方开发者访问,那么当进行升级时,你除了考虑公有接口之外,还需要关注私有实现的变化。
这个问题的根源在于,虽然Java包表面上是通过包嵌套而使其具有逻辑关系,但其实不然。人们初学Java时通常有一个误解,认为父子关系的包被赋予了特殊的可见性。其实存在嵌套关系的两个包与两个没有嵌套关系的包是相同的。包嵌套主要是为了避免命名冲突,而仅为逻辑代码划分提供了部分支持。
所有这些表明在Java语言中,你通常必须要面对下面两种选择。
为了避免暴露非公有API而把不相关的类打包在一起,以致损害程序的逻辑结构。
为了保持程序逻辑结构而使用多个包,代价就是暴露非公有API,被不同包中的类访问。
两种选择都不尽如人意。
易错的类路径概念
Java平台也会妨碍好的模块化实践,罪魁祸首正是类路径(class path)。为什么类路径会为模块化带来困难呢?最大的原因是类路径隐藏了代码版本、依赖和一致性等特性。应用程序一般由各种版本的库和组件组成。类路径不关心代码版本——只返回找到的第一个版本。即使关注代码版本,但还是无法明确描述依赖关系。建立类路径的过程是烦琐易错的,你只是不停地添加类库,直到虚拟机不再报出找不到类的错误。
图1-2表明当多个JAR文件提供指定类集的时候 “类路径困境”问题会经常发生。即使每个JAR文件已经按照一个工作单元完成编译,但当在执行时合并时,Java 类路径并不关心组件的逻辑划分。这就会导致难以预料的错误,例如:一个JAR文件的类与另一个JAR文件中不兼容的类交互时会出现NoSuchMethodError
异常。
图1-2 按照在类路径中出现的顺序来合并多个包含重叠类或包的JAR文件,而没有考虑包的一致性
在由独立开发的组件构成的大型应用中,依赖同一组件不同版本的情况并不少见,例如日志或者XML解析。而类路径会强制选择某个可能并不合适的版本。更糟的是,如果类路径中有同一个包的多个版本,不论是有意或是偶然,都会被Java当做是不同的包,并按照出现的顺序隐式地融合起来。
综上所述,类路径的方式不包含任何形式的一致性检查。无论获得的是系统管理员授权使用的哪些可用类,可能都只是接近你的预期。
部署和管理支持上的不足
Java还缺少对应用部署和管理的支持。在Java中存在对多个版本的依赖时,没有简单的方法来正确部署这些代码并执行。在部署之后更新应用和组件也会面临同样问题。
考虑一下希望支持动态插件机制的常见需求。唯一能实现这个合理需求的方法就是使用类加载器,但是这种方法是低级的,且容易出错。类加载器并不是应用开发者的常用工具,但是现今的许多系统却必须使用它。一个合理定义的Java模块层能够通过明确模块定义和提高代码划分的抽象级别,提供对这些特性的支持。
在更好地理解Java模块化不足的基础上,我们可以思考一下OSGi是否是你的项目的理想解决方案。
除了最简单的应用程序外,几乎所有的应用程序都能够获益于OSGi提供的模块化特征,所以如果你正对是否应该了解OSGi犹豫不决,那么答案非常可能是肯定的。仍然不相信吗?在以下这些你可能遇到过的常见情况下,OSGi恰恰能够给你提供一些帮助。
当启动程序时,由于类路径不正确而导致ClassNotFoundException
异常。OSGi可以帮助你先确保代码满足依赖关系,然后才允许执行代码。
由于类路径上一个依赖库的错误版本而导致程序执行时错误。OSGi会在要求的版本和其他约束条件方面对依赖集进行一致性检查。
当模块间共享类时导致类型不一致,更加具体地说,出现了像foo instanceof Foo ==false
这样令人生畏的问题。使用OSGi,你就不必担心由于层次化的类加载模式隐含的限制。
将一个程序打包成逻辑上独立的JAR文件,并且只部署那些某个安装所需要的部分。这大致上阐述了OSGi的目的。
将一个程序打包成逻辑上独立的JAR文件,声明哪些代码可以被其他JAR文件访问,并且强调可见性。OSGi为JAR文件提供了新一级的代码可见性,这样你就可以指定哪些对外界是可见的,而哪些是不可见的。
为程序定义一个插件式的扩展机制。OSGi模块化特别适合提供强大的扩展性机制,包括支持执行时的动态性。
正如你所见,这些场景覆盖了很多用例,我们无法一一列举。OSGi简单而非侵入式的本质使得你用得越多,就发现使用它的方式越多。上文已经展示了标准Java 类路径的一些不足,现在我们该向你介绍OSGi了。
联系客服