深入理解OSGI:Java模块化之路

简介

Java可能是近20年来最成功的开发技术,因其具备通用性、高效性、平台移植性和安全性而成为不同硬件平台理想的开发工具。从笔记本电脑到数据中心,从游戏控制台到科学超级计算机,从手机到互联网,Java技术无处不在。

Java能够让程序员使用同一种语言为服务器、智能卡、移动电话和嵌入式设备开发程序,极大地提升了软件的研发效率。不过,仅靠统一的语言还不足以让软件业迅速提升至成熟的工业化阶段。不同软件系统、不同硬件设备下的程序都经常会有相同的业务需求和设备间交互通信的需求,例如很多设备都需要互联网接入的功能,如果通用于不同设备的网络标准件不存在,那就只能为每个设备都开发一个连接互联网的模块,这样效率和质量都难以保证。假如把开发中经常遇到的需求进行抽象,将它们统一规范起来作为标准件提供,任何设备都通过预定义好的协议和接口来使用这些标准件,那么构造一个大型程序的主要工作很可能就只是根据需求选择合适的模块,然后再写少量的黏合代码而已。

标准件是区别小手工作坊和大工业化最明显的标志。今天,个人计算机的硬件已经到达了工业化阶段,无论哪个公司生产的显示器、键盘、鼠标、内存和CPU,都遵循统一规范的接口工作。要获得不同功能、性能的计算机,只要选择适当的硬件模块进行组装即可。与此相对的,大部分计算机软件都还是从零开始进行编码开发的。软件业还远不如硬件成熟,但是软件工业化是一股不可逆转的潮流,实现这个目标的第一步就是要制定不同功能模块的标准,以及模块间的黏合及交互方式。Java业界内已经有了很多的技术规范,例如EJB、JTA、JDBC、JMS等,欠缺的是一个组织者或扮演黏合剂的角色,直到Java有了OSGi……

1.1 什么是OSGi

OSGi(Open Service Gateway Initiative,直译为“开放服务网关”)实际上是一个由OSGi联盟(OSGi Alliance,如图1-1所示)发起的以Java为技术平台的动态模块化规范。

http://assets.osgi.com.cn/article/7289372/图1-1.jpg

图1-1 OSGi联盟

OSGi联盟是由Sun Microsystems、IBM、Ericsson等公司于1999年3月成立的一个世界性的开放标准化组织,最初的名称为Connected Alliance,该组织成立的主要目的原本在于使服务提供商通过住宅网关为各种家庭智能设备提供服务。最初的OSGi规范也只是关注于嵌入式领域,前三个版本的OSGi规范主要满足诸如机顶盒、服务网关、手机等应用环境的模块化需求。从第四个版本开始,OSGi将主要关注点转向了Java SE和EE领域,并且在这些领域中获得了很大的发展,成为Java平台事实上的模块化规范。

随着OSGi技术的不断发展,OSGi联盟的成员数量已经由最开始的几个增长到目前超过100个,很多世界著名的IT企业都加入到OSGi的阵营之中,如Adobe、IBM、Oracle、SAP、RedHat和Siemens等。它们推出的许多产品都支持OSGi技术,甚至产品本身就使用了OSGi技术构建,例如IBM的WebSphere、Lotus和JAZZ,Oracle的GlassFish和Weblogic,RedHat的JBoss,Eclipse基金会的Eclipse IDE、Equinox及之下的众多子项目,Apache基金会的Karaf、Aries、Geronimo、Felix及之下的众多子项目等。这些IT巨头的踊跃参与,也从侧面证明了OSGi技术有着非常广阔的市场前景。

OSGi技术的影响同时也延伸到了Java社区,JSR–232提案的通过说明OSGi技术已经被Java ME领域所认可,而 JSR–291提案则奠定了OSGi技术在Java SE和Java EE领域标准模块化规范的地位(OSGi与Java模块化规范的历史将在1.1.2节会详细介绍)。

OSGi的诸多优秀特性,如动态性、模块化和可扩展能力逐渐被越来越多的开发者所认识和欣赏,越来越多的系统基于OSGi架构进行开发。在这些系统的开发过程中,又会向OSGi提出一个又一个新的需求,所以OSGi规范所包括的子规范与技术范畴也在不断发展、日益壮大,如图1-2所示。

http://assets.osgi.com.cn/article/7289372/图1-2.jpg

图1-2 OSGi及相关技术

今天,OSGi的已经不再是原来Open Service Gateway Initiative的字面意义能涵盖的了,OSGi联盟给出的最新OSGi定义是The Dynamic Module System for Java,即面向Java的动态模块化系统。

2012年7月,OSGi联盟发布了最新版的OSGi R5.0规范,这次发布的规范包括OSGi核心规范R5.0和OSGi企业级规范R5.0。在Java SE领域,Eclipse和NetBean两款集成开发工具的成功已经完全证明了OSGi在桌面领域是能担当重任的。最近两三年来,OSGi的发展方向主要集中在Java EE领域,在OSGi企业专家组(EEG)的努力下,OSGi的企业级规范R5.0版相比两年前发布的R4.2版又增加了许多新的内容,OSGi技术在服务端和企业级领域正迅速走向成熟。

1.1.1 OSGi规范的演进

OSGi在R4版之前都处于初级阶段,规范的主要关注点是在移动和嵌入式设备上的Java模块化应用。在这个初级阶段中有一些很成功的案例,比如BMW(宝马)汽车使用OSGi架构实现的多媒体设备控制程序(内部是西门子VDO系统),要使用不同型号的电子设备,只更换对应程序模块便取得了很好的效果。但是初级阶段的OSGi在Java其他主流应用领域(企业级、互联网、服务端、桌面端等)的影响力还比较有限,因此下面简要介绍这部分的历史。OSGi规范在初级阶段一共发布了三个版本:

  • OSGi Release 1 (R1):2000年5月发布。
  • OSGi Release 2 (R2):2001年10月发布。
  • OSGi Release 3 (R3):2003年3月发布。

从OSGi R4版开始,OSGi的目标就从“在移动和嵌入式设备上的Java模块化应用”发展为“Java模块化应用”,去掉了“在移动和嵌入式设备上的”这个限定语,这意味着OSGi开始脱离Java ME的约束,向Java其他领域进军。同时也意味着OSGi需要考虑如何去迁移遗留的异构系统、如何去支持大规模开发等非嵌入式领域的问题了。因此,OSGi R4版规范的复杂度相应地高出R3版许多。笔者可以给出两个最直观的数据:规范文档的页数从R3的450页增加到900页,规范中定义的外部接口(统计API中public方法)数量从R3的661个增加到1432个。

OSGi R4版本分为两个部分, 2005年10月发布的核心规范(包含服务纲要规范)和2006年9月发布的移动设备规范(移动设备规范已停止发展,目前最新的OSGi移动设备规范依然是这个版本)。从更具体的角度来讲,OSGi R4解决了R3的许多遗留问题和限制,以下列举了部分在核心规范中比较关键的改进:

  • 在OSGi R3版本中,模块导出的Package是全局唯一的,不允许同一个Package存在多个版本。这点限制放在资源受限的嵌入式环境中一般不会有问题,但是放在整个Java领域就不妥了,因为引用不同版本的第三方包对于规模稍大一点的程序来说是很常见的事情。
  • OSGi R3中的模块缺乏对模块本身的扩展机制,所有的资源、代码都必须在模块中是静态存在的,无法运行时动态添加。在OSGi R4中,出现了Fragment Bundle的概念。
  • OSGi R3的Package导入和导出无论是版本、可见性和可选择性都很粗糙,例如在导入时指明一个Package的版本,语义就只能是导入不小于这个版本的Package,而对于要明确具体版本范围(如[2.5,3.0))的需求就不适用;又如在导出Package时,一个Package中的所有类要么全部导出,要么全部隐藏。在OSGi R4中改进了version参数,也为导入导出加入了许多子参数来方便精确过滤范围。

除了在核心规范中对R3版的改进外,许多目前非常常用的、在服务纲要规范中的OSGi服务也是在R4版才开始出现的。这些服务对提升开发人员的工作效率及系统的鲁棒性有很大帮助,例如R4版首次出现的声明式服务就是对R3版之前的程序化服务模型的重大改进。 尽管从OSGi R3到OSGi R4发生了很大的变化,R4版规范依然保持了很好的向后兼容性,绝大部分能运行于OSGi R3的模块都可以不经修改地迁移到OSGi R4之中。

2007年5月发布的OSGi R4.1是一个修正版性质的规范,只是核心规范发生了很小的变化,服务纲要规范和移动设备规范并没有跟随发布R4.1版,整个R4.1版没有新增任何服务。OSGi R4.1版本的推出,最重要的任务是适配JSR-291提案,让JSR-291提案顺利通过JCP的投票,成为整个Java业界标准的一部分。

在OSGi R4.1版本中,值得一提的改进是处理了Bundle延迟初始化的问题,增加了Bundle-ActivationPolicy标识来指明Bundle的启动策略。在此之前,OSGi实现框架只能通过自己的非规范的标识来完成类似的事情,例如Equinox的私有的Eclipse-LazyStart标识。

2009年9月,OSGi R4.2版核心规范发布;在次年3月,还发布了OSGi R4.2企业级规范。OSGi R4.2是一个包含了许多重要改进的版本。首先,随着OSGi实现框架的数量逐渐增多,OSGi R4.2开始着手解决OSGi框架自身的启动问题,提供了操作OSGi框架的统一API。在此之前,启动Felix、Equinox、Knopflerfish或其他OSGi框架,必须使用完全不同的私有API来实现,这点不利于程序在不同OSGi上实现平滑迁移。另外,OSGi R4.2还向开发者提供了影响OSGi框架运作的能力,如Bundle Tracker、Service Hooks这些工具的出现,让由非OSGi实现框架的开发人员去实现OSGi系统级的模块成为可能。

在具体服务方面,OSGi R4.2的主旋律是企业级服务的改进。许多企业级服务在这个版本中首次出现,例如远程服务规范和Blueprint容器(提供依赖注入和反转控制的容器,类似于Spring的功能)规范。说到企业级服务,OSGi R4.2专门独立发布企业级服务规范的一个重要任务就是解决OSGi与Java EE服务之间关系的问题。Java EE体系中的许多重要服务如JNDI、JPA和JDBC等在企业级开发中都是不可或缺的,因此在OSGi R4.2中相应定义了JNDI、JPA和JDBC等服务规范,将Java EE的服务引入到OSGi容器中来。

除此之外,OSGi R4.2还制定了Web Applications规范,使OSGi中包含Web页面的模块可以使用标准WAR格式来打包(打包后的产品为以.wab为扩展名的JAR格式文件),允许将这些模块直接安装到支持OSGi和Web Applications规范的应用服务器之中。

2011年4月和2012年3月,分别发布了OSGi R4.3的核心规范和服务纲要规范。在这个版本中,OSGi的API接口终于开始使用已经有8年历史的、从Java SE 5开始提供的泛型。OSGi对Java平台版本迟缓响应,很大程度是因为顾及到嵌入式环境的虚拟机版本,很多设备还没有升级到Java 1.5,同时还顾及其中存在的遗留系统。OSGi R4.3有了这样的变化,也从侧面说明OSGi的重心已经开始向服务端应用等领域偏移了。

OSGi R4.3的另一个重要改进是在核心规范中添加了Bundle Wiring API子规范,该规范引入了Capabilities和Requirements的概念。在此之前,OSGi中的依赖单元要么是某个Package,要么是整个Bundle(分别使用Import-Package和Require-Bundle标识来描述),这种粒度的依赖单元能够满足代码上的依赖需要,却无法描述某些非代码的依赖特性,例如说明一个功能要依赖某个Java虚拟机版本、依赖某种架构的操作系统、依赖某些资源文件或依赖一定数量的CPU或内存等。以前虽然可以使用Bundle-RequiredExecutionEnvironment标识来描述部分执行环境的特性(如指明JDK版本是Java SE 6),但是有一些特性不是在执行环境中天然存在的,是由某个Bundle安装后带来的,有了Require-Capability和Provide-Capability标识之后,才可以精确描述这类依赖关系。

继在OSGi R4.2中引入Service Hooks之后,OSGi R4.3大幅增加了OSGi的Hooks挂接点数量,新增了Weaving Hook、Resolver Hook、 Bundle Hook、Weaving Hook和Service EventListener Hook。

Weaving Hook让用户模块可以获得在其他类加载时动态植入增强的能力。Resolver Hook和Bundle Hook代替了以前的OSGi框架嵌套和组合模块(Composite Bundle)的功能,让用户可以创建虚拟的模块集合,使不同集合之间的模块互不可见(这点在OSGi R5中提供了更完美的解决方案)。Service EventListener Hook让用户可以插手服务事件分派的过程。

2012年7月,OSGi R5发布(同时发布了核心规范和企业级规范的OSGi R5版本),这是目前最新的OSGi规范版本。OSGi R5的一个主要目标是建立一套基于OSGi的模块仓库系统(为下一步的OSGi in Cloud做准备)。Apache Maven已经建成了类似的仓库系统,它的中央仓库中保存了Java业界中许多项目的依赖信息和JAR包。其实OSGi在这个领域本应有着得天独厚的优势,模块的元数据信息在某种意义上就是依赖描述的信息,但迟迟未在规范上踏出这一步。在OSGi R5出现之前,就已经有了Equinox P2、OSGi Bundle Repository(OBR)等技术出现,在OSGi R5版规范中提出的Subsystem Service子规范和Repository Service子规范终于把这些技术统一起来。

OSGi R5还对许多之前发布的子规范进行了更新和功能增强,例如JMX Management Model规范开始支持Bundle Wiring API了,Configuration Admin在这个版本中能够支持多个Bundle共享同一个配置对象,声明式服务规范开始支持注解(这些注解用于供BndTools这类工具自动生成XML配置文件之用,实际上OSGi运行期还是没有使用泛型之外的Java SE 5后的语法特性)。

1.1.2 Java模块化规范之争

经过近20年的发展,Java语言已成为今日世界上最成功、使用的开发者人数最多的语言之一,Java世界中无数商业的或开源的组织、技术和产品共同构成了一个无比庞大的生态系统。

与大多数开发人员的普遍认知不同,Java的生态系统和演进路线并不是由Sun Microsystems公司来决策和管理的。虽然Sun公司拥有“JavaTM”这个商标的所有权,并且拥有Java中使用最广泛的HotSpot虚拟机和Sun JDK,但它并不能直接制定Java世界中的规则、确定Java技术的发展走向。

1998年,在Sun公司的推动下成立了JCP(Java Community Process)组织,这个组织负责制定Java的技术标准,发布技术标准的参考实现(RI)和用于检验标准的技术兼容包(TCK)。目前,除了Sun(目前是Oracle继承了Sun的席位)公司外,还有Google、IBM、Motorola、Nokia、Sybase、SAP等数以百计的公司、组织和独立个人参与了JCP组织,共同监督和维护Java标准的制定。这些参与者中的16位代表组成了JCP执行委员会(JCP Executive Committees,一共有两个这样的委员会,分别对应Java SE/EE方向和Java ME方向),对提交给JCP的提案进行投票表决。

JCP允许任何组织或个人对Java的演进路线提出建议,这些建议在审查立项后会以JSR(Java Specification Requests)的形式形成正式的规范提案文档;在经过专家组审核和执行委员会投票通过之后,这些JSR文档就将成为正式的Java技术规范。J2EE、EJB、JSP、JDBC、JMX、JVMS等规范都由对应的JSR文档“孵化”而来,甚至连JCP本身的组织和运作方式也是由特定的JSR文档进行定义的。

以上介绍的内容虽然与OSGi没有直接关系,但是它们是读者了解后面介绍的OSGi与Java之间关系的必要背景知识。

OSGi源于JSR-8(Open Services Gateway Specification,开放服务网关规范),这是一份由Sun发起并主导(共同发起的还有IBM、Ericsson和Sybase等公司)、在1999年3月提交到JCP的规范提案。这份规范定义了不同设备之间服务互相依赖和调用的交互接口。1999年,Java 2刚发布不久,互联网也刚刚兴起,“支持各种移动设备、嵌入式设备、智能家电”这个最初建立Java语言的目标,对于Java来说依然是最重要领域之一。

不过,JSR-8提案很快(1999年5月,即提交之后的2个月)就被发起者撤回。撤回并不是因为这份JSR不够资格成为Java规范发布,主要是发起者希望另外建立一个独立于JCP的组织来发展运作这份规范,让更多不适合参与到JCP的设备厂商能够参与OSGi的规范制定。因此,1999年独立于JCP的OSGi联盟成立,并于2000年发布了OSGi规范的第一个版本:OSGi R1.0。

在OSGi的前三个版本中,OSGi主要领域依然维系在移动和嵌入式设备之上,在这三个版本的发展中Sun公司起了很大的推动作用,这段时间可谓是OSGi和Sun的蜜月期。比如Sun的JES(Java Embedded Server)就是当时使用最广泛的OSGi R2的实现。从OSGi R4开始,OSGi开始尝试跨越移动和嵌入式领域的限制,进入Java SE/EE领域,与此同时,OSGi联盟的各个成员在发展和商业选择上也产生了分歧,各自(主要是IBM和Sun)都在争夺OSGi联盟的主导权。在这个过程中各厂商是如何争夺规范控制权的我们不得而知,总之最终的结果是Sun公司于2006年离开了当年它一手主导建立的OSGi联盟。OSGi规范分为面向Java主流领域的OSGi R4核心规范和依然专门面向嵌入式和移动领域的OSGi移动设备规范(即JSR-232 Mobile Operational Management,OSGi R4 Mobile与JSR-232的内容是完全一致的)。

在今天看来,Sun的离开恰恰证明了当时它在Java模块化方向上的错误。OSGi R4的目标平台转变为Java SE/EE,进军桌面、服务端和互联网的举措获得了很大的成功,关于这点不得不提到在IBM支持下Eclipse基金会对OSGi快速流行所做出的贡献。自Eclipse 3.0 M4版本开始,这款著名的集成开发工具被重构为完全基于OSGi架构实现的产品,支持Eclipse运行的底层框架Equinox成为OSGi R4.x使用最广泛的实现框架。伴随着Eclipse IDE的流行,OSGi迅速在Java ME以外的领域站稳脚跟。许多人(包括笔者)都是从Eclipse开始关注OSGi的,IBM的很多后续产品,如WebSphere、JAZZ等都继续支持OSGi或直接基于OSGi来构建,其他公司也迅速跟进。

OSGi R4的迅速流行带来一个强烈的信号:Java SE/EE支持模块化已经成为一股不可逆转的潮流,支持模块化的呼声已经强大到令Sun公司不能再忽视的程度。Sun公司期望能借助JCP的力量重新争夺Java模块化规范的控制权。

2006年10月,由Sun公司提交的JSR-277规范提案(Java Module System,Java模块化系统)发布了第一个早期预览版(Early Draft Review);2007年年底,Sun携带着JSR-277重新加入OSGi联盟。

JSR-277是一个全静态的模块化规范,它在模块化和依赖描述方面与OSGi有着相似的能力,在构建模块仓库(Repository)上对比当时的OSGi规范占有一定优势(OSGi这方面的弱点在2012年7月OSGi R5发布并拥有了Subsystem Service和Repository Service之后已经被填补)。但是JSR-277是完全静态的,没有任何关于动态化的考虑,这样模块就无法在运行时安装、更新和卸载,因此也就不存在类空间一致性、类和类加载器卸载等问题。这点相对于OSGi的动态模块化来说是极大的退步。另外JSR-277引入新的“.jam”格式文件作为模块分发格式也被大家所诟病。OSGi技术专家、OSGi联盟主席Peter Kriens在OSGi联盟的官方博客上发表文章批评道:“JSR-277的目标如同儿戏,只能相当于OSGi在8年前的技术水平”。

在JCP中争夺模块化规范控制权,对于Sun来说会比在OSGi联盟中更为有利。Sun在JCP拥有很大的影响力,它是JCP的发起者,担任JCP的主席,在执委会中拥有无须选举的无限期执委会投票权(其他15个执委席位三年选举一次),Sun肯定希望能永远保持在JCP中的领导地位,但是JCP的其他成员都希望能够在Java的规范和发展路线上拥有更大的话语权。这样,不可避免会产生一些利益冲突。Sun力挺JSR-277,希望用它代替OSGi成为Java模块化标准,IBM则将OSGi R4.1提交到JCP成为JSR-291(Dynamic Component Support for Java SE ,Java SE动态组件支持)来与JSR-277对抗,这样,在OSGi联盟中的规范之争的战场又重新回到JCP之中。

这两个JSR竞争的结果是JSR-277没有得到通过,尽管Sun曾经做出了一些让步,比如承诺JSR-277可以引用和操作OSGi的遗留模块,它最终还是没有得到足够的支持,被废止在早期预览版阶段。另一方面,在对JSR-291进行表决时,虽然Sun明确投了反对票,但是JSR-291仍然在JCP执委会最终投票中通过。这样,OSGi终于确立了Java唯一的模块化规范的地位。不过,事情并没有结束,JSR-291得到通过并不意味着OSGi规范立刻就会成为现实。

虽然OSGi R4.1在2007年5月通过投票之后就应该是正式的Java规范,但是Sun依然在JSR-316(Java Platform, Enterprise Edition 6 Specification,Java企业版规范第6版,这个规范提案在2007年7月提交给JCP,2009年12月发布最终版,提交时JSR-277与JSR-291之争已尘埃落定)的规范目标中明确写道:“为了更好地支持这个平台(指Java EE 6)在扩展性方面的目标,应该有一个更加宽泛的模块化概念。这项工作正由‘JSR-277 Java模块系统’来实现,它的目标平台是Java SE 7。我们预期Java EE 7将建立在这项技术的基础上,因此我们将推迟任何可能与将来的版本冲突的技术规范”。在这段话中所谓的“可能与将来的版本冲突的技术规范”毫无疑问就是JSR-291了。Sun(当时已经被Oracle收购了)这种一意孤行地坚持自己的JSR-277而无视JSR-291的行为招来了许多非议。但是非议无法解决问题,Sun仍然实质性地控制着JDK的发展,最终结果就如JSR-316中的那句话所表达的那样,整个Java SE 6期间没有任何模块化相关的改进以JDK功能的形式进入到Java平台中。Sun对待OSGi的态度不应解读为Sun反对Java模块化,相反,这是Sun极为重视Java模块化的体现,它一定要把Java模块化的主导权抓在手中。尽管Sun的做法拖延了Java模块化的进程,但模块化依然是不可避免地向前发展了,到Java SE 7中又如何呢?

讲到Java SE 7的模块化,我们不得不再多介绍一个规范提案:JSR-294(Improved Modularity Support in the Java Programming Language,Java语言的模块化改进)。如此之多(提交的时间很集中,并且是并行发展的,在JSR-294提交时JSR-277并没有被废止)的JSR来解决重复的问题在JCP历史上也是极为罕见的。这个规范提案的提交者也是Sun公司,它试图在Java语言和Java虚拟机层面上对模块化进行支持,直接修改JLS(Java语言规范)和JVMS(Java虚拟机规范),加入module关键字和超级包(Super Package)等概念。JSR-294的模块化实现思想与OSGi的差异是非常大的,通俗地讲,OSGi是在Java平台之上建立的模块化,而JSR-294是直接在Java平台之内建立的模块化。

从纯粹技术角度来看,Java模块化如果真能通过直接修改Java语法、Class文件格式和Java虚拟机来实现,我们相信这种实现方案的性能、完善程度肯定能够超越OSGi,仅从技术角度看,这种改进方式无疑是Java模块化的最佳结果。不过,修改Java语法在JCP中历来都是“慎重而敏感的话题”,增加新的语言特性相对缓慢,这点也是社区管理的劣势。对比一下微软一家单独掌控的C#语言,就能看出明显差距。

话题再回到JSR-294,很遗憾,这个规范提案的结果与JSR-277一样,也被废弃在早期预览版阶段。Sun似乎早就预料到这个结果,它在提交JSR-294的同时,就在OpenJDK中启动了Jigsaw项目的孵化进程。Jigsaw项目最开始的目标是作为JSR-294的参考实现(RI),但是该项目的开发过程却是在jigsaw-dev邮件列表上进行的,该邮件列表游离于JSR-294专家组的邮件列表之外。目前的种种迹象表明,Sun决定让Jigsaw项目采取“SunJDK专有的方式”来实现Java语言模块化,JSR-294没有得到通过,也就意味着Jigsaw项目是Sun私有的,使用了Jigsaw的Java程序无法运行于其他公司提供的JDK之上。因此,即使Jigsaw本身的设计再好,只要无法做到“一次编译,到处运行”的模块化,就必然是对Java语言最重要一块基石的巨大损害。

很难相信Sun最后会以私有化的方式强行推出Jigsaw,这是非常不明智的。最后的结果肯定还要重新激活JSR-294,或者再提交另外一个JSR使之在JCP上通过,在此之前,Jigsaw只能无限期拖延下去。目前Jigsaw已经拖得足够长了,最初它是作为Java SE 7的特性进行设计的,后来因Java 7进度压力被推迟到Java 8之中,在2012年7月,在Jigsaw项目的主页上宣布它将进一步被延迟到Java 9中发布。这样导致的结果是,即使后续一切顺利,用户也要到2015年9月才能见到Jigsaw,那时已经是OSGi出现的16年之后了;再等到Java 9被应用到主流的生产环境中,Jigsaw就显得更加遥遥无期了。如果系统要兼容Java 2至Java 8平台,OSGi还是唯一的选择。Jigsaw不得不正视OSGi事实上的模块化规范的地位,建立了一个名为的Penrose子项目让Jigsaw可以与OSGi互相操作。

目前OSGi是Java世界中唯一的模块化规范,从纯技术角度看,它未必是最先进的模块化技术,从学习使用来看,它也不是使用最简单方便的模块化技术。但是从整个Java业界整体来看,OSGi确实是过去、现在乃至未来至少5年内可预见的最有生命力、最标准、使用范围最广泛的Java模块化技术。随着Java应用规模的日益庞大,越来越多的大型系统使用OSGi架构进行建设,因此,OSGi是具有广阔发展前景和使用、学习价值的。

1.2 为什么使用OSGi

没有什么技术是万能的,任何一门技术都有它的适用场景和最佳实践方法。OSGi不只是一门技术,更多的是一种做系统架构的工具和方法论,如果在不适用的场景中使用OSGi,或者在适用的场景中不恰当地使用OSGi,都会使整个系统产生架构级的缺陷。因此,了解什么时候该用OSGi是与学会如何使用OSGi同样重要的事情。

每个系统遇到的业务环境都是不一样的,笔者不希望以经验式的陈述去回答“什么时候该用OSGi”或 “为什么要使用OSGi”这样的问题,而试图通过几个问题的讨论和利弊权衡,让读者自己去思考为什么这些场景适用OSGi。如果读者是第一次通过本书接触OSGi,那么可能对某些讨论的内容会感到困惑,笔者建议尽可能在阅读完全书或者在准备真正在项目中使用OSGi的时候,再回过头读一遍本节的内容。

1.2.1 OSGi能让软件开发变得更容易吗

不可否认,OSGi的入门门槛在Java众多技术中算是比较高的,相对陡峭的学习曲线会为第一次使用OSGi开发系统的开发人员带来额外的复杂度。

OSGi规范由数十个子规范组成,包含了上千个不同用途的API接口。OSGi规范显得这样庞杂的主要原因是实现“模块化”本身需要解决的问题就非常多。模块化并不仅仅是把系统拆分成不同的块而已—这是JAR包就能做的事情,真正的模块化必须考虑到模块中类的导出、隐藏、依赖、版本管理、生命周期变化和模块间交互等一系列的问题。

鉴于OSGi本身就具有较高的复杂度,“引入OSGi就能让软件开发变得更容易”无论如何是说不通的,小型系统使用OSGi可能导致开发成本更高。但是这句话又不是完全错误的,随着系统不断发展,在代码量和开发人员都达到一定规模之后,OSGi带来的额外成本就不是主要的关注点了,这时候的主要矛盾是软件规模扩大与复杂度随之膨胀间的矛盾。如图1-3所示,代码量越大、涉及人员越多的系统,软件复杂度就会越高,两者成正比关系。这个观点从宏观角度看是正确的,具体到某个系统,良好的架构和设计可以有效减缓这个比率。基于OSGi架构的效率优势在这时候才能体现出来:模块化推动架构师设计出能在一定范围内自治的代码,可以使开发人员只了解当前模块的知识就能高效编码,也有利于代码出现问题时隔断连锁反应。OSGi的依赖描述和约束能力,强制开发人员必须遵循架构约束,这些让开发人员“不自由”的限制,在系统规模变大后会成为开发效率的强大推动力。

http://assets.osgi.com.cn/article/7289372/图1-3.jpg

图1-3 引入OSGi对软件复杂度的影响

可以用一个更具体的场景来论述上面的观点,解析OSGi架构如何在开发效率上发挥优势。有经验的架构师会有这样的感受:设计一个具有“自约束能力”的系统架构非常不容易。最常见的情况是设计人员设想得很美好,开发人员在实现时做出来的产品却不是那样。大部分软件公司是通过“开发过程”、“编码规范”、“测试驱动”,甚至“人员熟练度”来保证开发人员实现的代码符合设计人员的意图。这样即使在开发阶段做到符合设计需求,也很难保证日后维护人员能够继续贯彻原有的设计思想;随着开发的时间越来越长,系统最终实现的样子可能和原有的设计产生越来越大的偏差。在软件工程中,将这种现象称为“架构腐化”。架构的“自约束能力”就是指限定不同开发人员在实现功能的时候,实现方式都是一致的,最好只有唯一一条遵循设计意愿的路可走,别的方法无法达到目的。更通俗地说就是,尽可能使程序员不写出烂代码。

举个最浅显的例子,如果有开发人员在Web层中使用DAO直接操作数据库,或者在DAL层直接从HttpSession对象中取上下文信息,这样的代码也许能逃过测试人员的黑盒测试,但是显然是不符合软件开发基本理论的。前者可能因绕过Service层中的事务配置而出现数据安全问题;后者限制了这样的DAO就只能从Web访问,无法重用和进行单元测试。如果项目中出现这样的代码,笔者认为首要责任在架构师,因为架构师没有把各层的依赖分清,如果Web层只依赖Service层的JAR包,那么程序员就无法访问到DAO,如果DAL层没有依赖Servlet API的JAR包,那么程序员就不可能访问HttpSession对象,这就是一种架构缺乏自约束能力的表现。

大概没有哪个架构师会犯上面例子那样幼稚的错误。但是,实际情况也远比例子中的复杂,甚至有一些问题是Java语言本身的缺陷带来的,例如,依赖了一个JAR包就意味着能够访问这个JAR包中的一切类和资源,因为JAR包中的内容没有Public、Private和Protected之分,无法限制用户能访问什么、不能访问什么。更复杂的情况是在引入了同一个JAR包的不同版本时怎么办?如果依赖包需要动态变化怎么办?使用OSGi一个很重要的目的就是弥补Java中资源精细划分的缺陷,加强架构的自约束能力。

虽然OSGi起源于精小软件占多数的嵌入式领域,但是在Java SE/EE领域中,对于越庞大的系统,使用OSGi进行模块化拆分就越能发挥出优势。在商业上已经有一些使用OSGi控制软件复杂度增长、延缓架构腐化速度的成功案例,如Eclipse Marketplace,它已经拥有了上千个插件,插件的开发者来自全球各地,技术水平差异很大,插件实现的功能也各不相同,是OSGi让这些插件基本遵循了统一的架构约束,并且一般不会因为某个插件的缺陷影响整个Eclipse的质量。

1.2.2 OSGi能让系统变得更稳定吗

笔者遇到过许多由OSGi框架引发的问题,例如,最典型的ClassNotFoundException异常、类加载器死锁或者在动态环境下的OutOfMemoryError问题等,这些都是基于OSGi架构开发软件时很常见的。从这一方面看,使用OSGi确实会增加系统不稳定的风险,所以,在开发过程中团队中有一两个深入了解OSGi的成员是必要的。

不过,软件是否稳定不是只看开发阶段可能出现多少异常就能衡量的,软件的“稳定”应是多方面共同作用的结果。除了关注开发阶段是否稳定之外,还要关注是否能积累重用稳定的代码,问题出现时能否隔断连锁反应蔓延,缺陷是否容易修复等。在这些方面,OSGi就可以带来相当多的好处,例如:

  • OSGi会引导程序员开发出可积累可重用的软件。我们无法要求程序刚开发出来就是完全稳定的,但可以在开发过程中尽可能重用已稳定的代码来提升程序质量。大家知道,写日志可以使用Log4j,做ORM会引入Hibernate,Java中有许多经过长期实践检验的、被证实为稳定的开源项目,这些开源项目的共同特征是都经过良好的设计,能够很方便地在其他项目中使用。相对而言,在自己开发项目时很多人没有注意到要进行可积累的设计。一种典型现象是项目中出现一些“万能的包”,通常名字会是XXXCommons.jar、XXXUtils.jar等,这些包中存放了在项目中被多次调用的代码,但是这样的包不能叫做可重用包。当这些包越来越大、类越来越多、功能越来越强时,与这个项目的耦合就越紧密,一般也就无法用在其他项目中了。在OSGi环境下,“大杂烩”形式的模块是很难生存的,如果某个模块有非常多的依赖项,那么没有人愿意为了使用其中少量功能去承担这些间接依赖的代价。因此设计者必须把模块设计得粒度合理,精心挑选对外发布的接口和引入的依赖,把每个模块视为一个商业产品来对待,这样才能积累出可重用的模块,也利于提高程序稳定性。
  • 基于OSGi比较容易实现强鲁棒性的系统。普通汽车坏掉一个轮胎就会抛锚,但是飞机在飞行过程中即使坏了其中一个引擎,一般都还能保持正常飞行。对于软件系统来说,如果某一个模块出了问题,能够不波及其他功能的运作,这也是稳定性的一种体现。大多数系统都做不到在某部分出现问题时隔离缺陷带来的连锁反应。试想一下,在自己做过的项目中把Common Logging(或slf4j)的包拿掉,系统能只损失日志功能而其他部分正常运作吗?但是对于基于OSGi架构开发系统,在设计时自然会考虑到模块自治和动态化,当某部分不可用时如何处理是每时每刻都会考虑的问题,如果软件在开发阶段跟随着OSGi的设计原则来进行,自然而然会实现强鲁棒性的系统。
  • 在OSGi环境下可以做到动态修复缺陷。许多系统都有停机限制,要求7×24小时运行,对于这类系统,OSGi的动态化能力在出现问题时就非常有用,可以做到不停机地增加或禁止某项功能、更新某个模块,甚至建立一个统一更新的模块仓库,让系统在不中断运行的情况下做到自动更新升级。

1.2.1节和1.2.2节提出的两个问题可以总结为OSGi是否能提升开发效率和软件质量。OSGi在这两方面的作用与软件设计得是否合理关系非常密切,这时OSGi好比一个针对“设计”这个因素的放大杠杆,配合好的设计它会更加稳定、高效,而遇到坏的设计,反而会带来更多问题。

1.2.3 OSGi能让系统运行得更快吗

系统引入OSGi的目的可能有很多种,但一般不包括解决性能问题。如果硬要说OSGi对性能有什么好处,大概就是让那些有“系统洁癖”的用户可以组装出为自己定制的系统了。例如GlassFish v3.0服务器是基于OSGi架构的,它由200多个模块构成,如果不需要EJB或JMS这类功能,就可以把对应的模块移除掉,以获得一个更精简的服务器,节省一些内存。总体上讲,OSGi框架对系统性能是有一定损耗的,我们从执行和内存两方面来讨论。

首先,OSGi是在Java虚拟机之上实现的,它没有要求虚拟机的支持,完全通过Java代码实现模块化,在执行上不可避免地会有一些损耗。例如,OSGi类加载的层次比普通Java应用要深很多,这意味着需要经过更多次的类加载委派才能找到所需的类。在两个互相依赖的模块间发生调用时,可能会由于类加载器互相锁定而产生死锁;要避免死锁的出现,有时候不得不选用有性能损失的串行化的加载策略。在服务层上,动态性(表现为服务可能随时不可用)决定了应用不能缓存服务对象,必须在每次使用前查找,这种对OSGi服务注册表的频繁访问也会带来一些开销。使用一些具体的OSGi服务,例如使用HTTP Service与直接部署在Web容器中的Servlet相比会由于请求的桥接和转发产生一些性能损耗。

其次,从内存用量来看,OSGi允许不同版本的Package同时存在,这是个优点,但是客观上会占用更多内存。例如,一个库可能需要 ASM 3.0,而同一应用程序使用的另一个库可能需要ASM 2.0,要解决这种问题,通常需要更改代码,而在OSGi中只需要付出一点Java方法区的内存即可解决。不过,如果对OSGi动态性使用不当,可能会因为不正确持有某个过期模块(被更新或卸载的模块)中一个类的实例,导致该类的类加载器无法被回收,进而导致该类加载器下所有类都无法被GC回收掉。

仅从性能角度来说,OSGi确实会让系统性能略微下降,但是这完全在可接受范围之内。使用OSGi开发时应该考虑到性能的影响,但不应当将其作为是否采用OSGi架构的主要决策依据。

1.2.4 OSGi能支撑企业级开发吗

不管关于“OSGi是否能支撑企业级开发”的讨论结果如何,一个必须正视的事实是OSGi对企业级开发的支撑能力正在迅速增强。从2007年OSGi联盟建立企业专家组以来,OSGi的发展方向已经逐渐调整到企业级应用领域。在IBM、Apache和Eclipse基金会等公司和组织推动下,企业级OSGi正在变得越来越成熟。

在企业级OSGi出现之前,企业级开发要么是走Java EE的重量级路线,要么是走SSH的轻量级路线。企业级OSGi被引入后并没有扮演一个“革命者”的角色,没有把Java EE或SSH中积累的东西推倒重来,OSGi更像是在扮演一个“组织者”的角色,把各种企业级技术变为它的模块和服务,使以前的企业级开发技术在OSGi中依然能够发挥作用。

OSGi企业级规范中定义了JDBC、JPA、JMX、JTA和JNDI等各种Java EE技术以及SCA、SDO这些非Java EE标准的企业级技术在OSGi环境中的应用方式,这些容器级的服务都可以映射为OSGi容器内部的服务来使用。并且到现在,企业级规范定义的内容已经不仅停留在规范文字中,已经有不少专注于OSGi企业级服务实现框架出现(例如Apache Aries)了。

另一方面,OSGi的Blueprint容器规范统一了Java大型程序中几乎都会用到的依赖注入(DI)方式,使基于Blueprint的OSGi模块可以在不同的DI框架中无缝迁移。这个规范得到Apache、SpringSource等组织的大力支持,目前这些组织已经发布了若干个Blueprint规范的实现容器(例如Apache Geronimo和Equinox Virgo,Virgo前身就是SpringSource捐献的Spring DM 2.0)。在最近两三年时间里,企业级OSGi成为Java社区技术发展的主要方向之一,其发展局面可以说是如火如荼。

不过,我们在使用企业级OSGi的时候也要意识到它还很年轻,其中很多先进的思想可能是遗留程序根本没有考虑过的,还有不少问题的解决都依赖于设计约束来实现。因此,如果是遗留系统的迁移,或者设计本来就做得不好,那么使用OSGi会遇到不少麻烦。以最常见的数据访问为例,如果以前遗留系统使用了ORM方式访问数据库,而迁移到OSGi时没有把实体类统一抽取到一个模块,那么ORM模块的依赖就很难配置了,这时不得不使用Equinox Buddy甚至DynamicImport-Package这类很不优雅的方式来解决。另一个问题是集群,OSGi拥有支持分布式的远程服务规范,而OSGi的动态性是针对单Java虚拟机实例而言的,因此要在集群环境下保持OSGi的动态性,就必须自己做一些工作才行。

1.3 本章小结

在本书中,笔者尝试阐述与OSGi相关的三个问题:什么是OSGi?为什么要使用OSGi?以及如何使用OSGi?在本章中,我们已经解答了前两个问题,本书后面的13章都将围绕“如何使用OSGi”这个问题来进行,通过研读OSGi核心规范、分析最常用的OSGi实现框架Equinox以及与读者一起通过实践来了解如何使用OSGi技术开发程序。

 

原网址:http://osgi.com.cn/article/7289372

                                                        本文由OSGi China提供      转载请注明来源

posted @ 2017-02-08 15:34  但行好事-莫问前程  阅读(30603)  评论(0编辑  收藏  举报