GlassFish OSGi-JavaEE (一): GlassFish 与企业级 OSGi 开发

阅读数:4423 2013 年 7 月 17 日

话题:语言 & 开发架构

前言

欢迎进入 GlassFish OSGi-JavaEE 专题!自从 GlassFish v3 开始,一个新的特性被加入到 GlassFish 中,那就是 GlassFish OSGi-JavaEE。本专题将分为九个部分向大家介绍 GlassFish OSGi-Java EE 相关的知识:

Part1: 对 GlassFish OSGi-JavaEE 做简单的介绍并简要叙述企业级的 OSGi 开发的现状。

Part2: 理解 GlassFish OSGi/WEB 容器并开发和部署一个 WEB 应用程序 Bundle 到 GlassFish 中。

Part3: 理解 GlassFish OSGi/EJB 容器并开发和部署一个 EJB 应用程序 Bundle 到 GlassFish 中。

Part4: 理解 GlassFish OSGi/CDI(上下文依赖注入) 规范以及 GlassFish OSGi/CDI 的实现,同时,展示如何使用 GlassFish OSGi/CDI 来简化企业级 OSGi 的开发。

Part5,Part6: 集成 EAR 和 OSGi,其中,Part5 会通过集成 Apache Aries Application 特性来拥抱 EAR 和 OSGi;Part6 会通过一个真实的案例来展示如何使用 EJB Bundle 来桥接 EAR 和 OSGi 以达到相同的目的。

Part7: 深入解读 GlassFish、HK2 和 OSGi 三者的关系, 同时,对于社区经常提到的问题阐述一个自己的观点。(“HK2 在未来将会 Dead 吗?”)

Part8: 深入理解 GlassFish 内核 (模块的配置、加载以及启动),同时,以一个案例展示如何扩展 GlassFish 来加入更多自定义模块。

Part9: 贡献 GlassFish OSGi 以及 OSGi-JavaEE 的流程。

本专题将假定读者拥有 GlassFish 和 OSGi 的基本知识,限于篇幅原因,我将不会专门介绍 GlassFish 和 OSGi 的基本知识。如果读者刚刚接触 GlassFish,建议大家参考以下链接:

如果读者并不了解 OSGi,推荐大家阅读以下的书籍:

  1. Neil Bartlett’s “OSGi in Practice”
  2. Richard S. Hall 等 “OSGi In Action”

如果用一句话来定义”什么是 GlassFish OSGi-JavaEE”的话,那么我们可以这样说:

GlassFish OSGi-JavaEE 就是企业级 OSGi 的 GlassFish 实现。

从 2005 年 GlassFish 1.0 到现在 4.0 的发布,GlassFish 已经走过了 9 个年头,在这 9 年中,GlassFish 经历了 JavaEE5 到 JavaEE7 的进化,经历了 Sun 被 Oracle 的收购,经历了架构上的重大变更……在我看来,最为重要的改变是 GlassFish 3.0 的内核和设计理念的重大变更,甚至说是一次革命! 3.0 之前,GlassFish 是一个不可分割或者说是一个整体化 (monolithic) 的庞然大物,内核与组件以及各个组件之间紧耦合,缺乏足够灵活的扩展性,启动性能不高。3.0 时代,情况发生了根本性的转变,内核依托 HK2 和 OSGi,各个模块完全采用 OSGi 设计理念,GlassFish 变得更加轻量级和模块化,内核与组件以及各个组件之间实现了松耦合,有着更加良好的可扩展性,启动性能有了质的提高,更为重要的是大大提高了可维护性。同时,更多有特点的模块出现了, GlassFish OSGi-JavaEE 就是其中之一。而这一切都要归功于 OSGi!

另一方面, 放眼当前世界上其他主流的开源 JavaEE 应用服务器,如 JBOSS 7(现在已经更名为 Wildfly), Apache Geronimo 3 等均采用模块化的设计理念(尽管 JBOSS 7 没有直接采用 OSGi 作为内核),力图使应用服务器瘦身,更加具有可维护性和可扩展性。

我们已经看到模块化的设计理念在 JavaEE 应用服务器领域大获成功,OSGi 作为模块化设计理念中最具代表性的产物,功不可没!

本专题并不专门讲述 OSGi 的基本知识以及如何采用 OSGi 来设计 JavaEE 应用服务器,相反,我们探讨如何使用 OSGi 来开发企业级 Java 应用。但是,当提到 OSGi 与企业级 Java 时,我们势必将会抛出一个问题:业级Java需要使用OSGi?

为了回答这个问题,我们需要进一步解答以下的两个重要的问题:

  1. 企业级 Java 开发有哪些不足?
  2. 借助 OSGi 能够解决这些不足吗?

业级Java开发有哪些不足

企业级 Java 是由构建在核心 Java 上的一系列库、API 以及框架松散构成的,并且这些库、API 以及框架为开发人员提供了一系列的企业服务,例如,分布式组件开发、事务、持久化访问数据库等。企业级 Java 已经取得了巨大的成功(无论是 JavaEE 本身还是 Spring 框架),但是,随着企业级 Java 应用程序的规模和复杂度的不断增加,这些应用程序已经开始变得更加得笨重和庞大,标准 Java 的一些固有的问题也越发变得严重,有时甚至是致命的。

classpath

众所周之,标准 Java 的 classpath 是扁平 (flat) 的结构,也就是说,是以线性顺序在指定的 classpath 中搜索类。例如,图 1 中,

图 1: 标准 Java 中扁平 (flat) 的 classpath 结构

类 A 有两个不同的版本,放在两个不同的 JAR 中,对于某些被部署的应用程序来说,也许它们想要使用版本 1 的类 A, 对于另一些被部署的应用程序来说,也许它们希望使用版本 2 的类 A。但是,扁平的 classpath 结构决定了很有可能不会加载版本 2 的类 A,因为版本 1 的类 A 将首先被发现和加载。

对于小的程序,也许我们能够迅速发现这个问题并且通过调整 jar 包在 classpath 中的顺序来解决。但是,当应用程序的规模和复杂性日益升级时,这个应用程序的 classpath 可能非常的大 (由几十个甚至几百个 JAR 构成),搜索 classpath 花的时间也会很大,一旦丢失了希望加载的某个类的话,很难在短时间内被发现。以下的一些场景进一步描述了这个问题。

classpath中缺失了运行的依

也许因为某种原因,当应用程序运行时,当前应用程序的 classpath 中缺失了某个依赖。那么,在最好的情形下,能够在运行的早期通过抛出 ClassNotFoundException 异常发现这样的缺失。但是,在最糟糕的情形下,运行的早期不会发现缺失的依赖(因为没有运行到相关的逻辑),直到数个星期(甚至几年)之后,通过抛出的 ClassNotFoundExcept 异常才能发现这样的缺失,但是,修补的成本可能会很大,如果你不够走运的话,这样的一个依赖缺失很可能会导致一个已经运行了很长时间的关键任务 (Mission-Critical) 程序(如股票交易,大型在线购物等)突然崩溃,而你却束手无策。

classpath中的版本冲突

也许有一种方式能够回避“依赖突然缺失”的问题,即包装 (wrap) 所有的依赖到这个应用程序之中 (例如: 对于 WAR 来说,我们将依赖放置到到 WEB-INF/lib 中)。我们暂且不考虑效率问题,因为对于每个应用程序都用到的一些共通的依赖,包装一份拷贝是一种资源的冗余,关键是这种包装的方式未必能够使应用程序正确地运行。想象一下这样一种场景: 如果运行在同一个 JVM 的多个应用程序公用某一项依赖的话,会是什么样的结果呢?

按照这种包装的方式,这个公用的依赖将会被每个应用程序都包装一份拷贝,当其中一个拷贝首先出现在 classpath 中并且被加载时, 其他的拷贝将被忽略。那么,如果这个公用依赖的版本一致,不会有什么问题。但是,当版本不一致时,一些应用程序将会运行正常,但是另一些应用程序很可能最终会出现” NoSuchMethodError”,因为它们所调用的方法根本不存在 (它们所希望依赖的版本被其他版本屏蔽了)。在一些更糟糕的情形下,没有显式的异常或错误抛出,程序看起来也在正常地运行,但是运行的行为是错误的,这并不容易被发现。

让我们从 JavaEE 的视角再看一下这样的包装方式,在一个 JavaEE 环境中,每个实例 (例如: GlassFish 的 Instance) 都可能驻留多个应用程序,为了解决“依赖突然缺失”的问题, JavaEE 规范建议包装每个应用程序的依赖到该应用程序之中,看起来这是一个理想的解决方案,但是,情况可能更加得糟糕,因为 JavaEE 应用服务器本身就可能依赖了很多开源的库,而应用程序也可能依赖了相同的这些库。这样的话,即便所有的应用程序都使用了相同版本的库,但因为 JavaEE 应用服务器使用了不同版本的库,就很可能导致这些应用程序运行异常,但是,这样的问题通常很难发现也很难调试。

复杂让事情看起来更糟糕

应用程序在日益变得复杂,问题也在不断地发生。BBC News 新闻站点上的一篇文章 [1] 提到:

The core of the problem is that the business software used by the institutions has become horrifically complex, according to Lev Lesokhin, strategy chief at New York-based software analysis firm Cast.

He says developers are good at building new functions, but bad at ensuring nothing goes wrong when the new software is added to the existing mix.

“Modern computer systems are so complicated you would need to perform more tests than there are stars in the sky to be 100% sure there were no problems in the system,” he explains.

简单地说,2012 年银行系统中发现了许多严重的问题,这些问题导致了支付处理中的损失和其他问题。这篇文章预测了当应用程序变得复杂时,这样的损失将会在未来变得更为普遍。

就复杂性而言,有两层的含义:

a. 应用程序的逻辑变得更为复杂

b. 应用程序的结构变得更为复杂,模块更为庞大

无论是 a 还是 b,这样的应用程序有很多都是由耦合性很高的代码构成的,用 Holly Cummins 和 Timothy Ward[2] 的话来说,这些都是混乱 (spaghetti) 的代码。下图演示了一个具有混乱代码构成的应用程序:

图 2: 一个带有很少结构化的并且高度相互关联的混乱的应用程序。实线代表着既是编译期的依赖也是运行期的依赖,而虚线仅仅代表运行期的依赖。摘自: “Enterprise OSGi In Action”第一章

从图 2 中,不难发现,对于每个对象,很难准确地发现它的每一个依赖以及传递性依赖,更不要说发现这些依赖的版本了。很可能在某个类中,有很多的依赖 (这些依赖包括了显式的依赖,也包括了许多隐式的依赖),对于小规模的应用程序来说,你可能很容易地发现这些依赖,但是,对于一个复杂性很高的应用程序来说,代码很庞大也很难读,那么,想要清晰地识别依赖并不容易。

对于这些具有混乱代码的工程,当未来试图替换一个依赖的版本时,很有可能造成程序运行不正确,更严重地,导致程序运行崩溃。另外,当试图为程序增加一个新的功能时,因为不能清晰地把握程序的每个部分的依赖关系,只能寄希望于做更多的测试,而这些测试中也许有一些根本没有必要,如果你打个盹想少测试一些,那么就有可能招致新的问题,这也就像 BBC News 的那篇文章所反映的那样。

进一步来说,出现混乱代码的根本原因其实就是 JAR 本身缺乏显式的依赖说明。一个复杂的企业应用可能由多个 JAR 构成,或者它本身就会被打包成一个 JAR,这些 JAR 不仅依赖标准 Java 库还依赖了其他的一些 JAR。因此,如果要部署这样的企业应用的话,必须也要部署它依赖的其他 JAR。例如:Apache JClouds 依赖 Google Guice 和 Google Guava 等,如果使用 JClouds 的应用程序的 classpath 之中没有 Guice 和 Guava 的话,那么这个应用程序将不可能正常工作。

但是,我们如何知道这些依赖和传递依赖呢?如果 Apache JClouds 有很好的文档说明,列举了这些依赖,那么这不会有什么问题,当然,如果你是一个 Maven 专家的话,通过 Apache JClouds 的工程 Pom 文件,你也能识别出这些依赖。但是,许多库并没有很好的文档说明,而且你可能也不是 Maven 专家或者这些库根本就不是用 Maven 这些好的依赖构建工具构建的,那么这些依赖对你来说就是隐式的,当试图使用这些库时,你很可能会遇到 ClassNotFoundException。

正如 Neil Bartlett 在” No Solution for Complexity?”[3] 中提到的那样,我们需要一种方式在大型系统的不同部分创建”防火墙”,当在受到”防火墙”保护的每个部分的外部增加新功能时,能够确保这些受到保护的部分不会被攻击和破坏。这样的话,我们就能够精确地知道系统的任何改变所涉及的范围,然后仅仅针对这些范围做测试,而不是全部。

另一方面,应用程序的逻辑变得更为复杂,今天的企业应用程序通常要支持并发访问、远程访问、持久化数据、事务操作、组件化和分布式 (为了考虑伸缩性和负载均衡等)……毫无疑问, JavaEE 已经提供了一系列事实的标准去满足这些需求,并且获得了巨大的成功。

但是,正如 Holly Cummins 和 Timothy Ward[2] 提到的,我们的企业级应用程序带有 Web 前端、持久化组件、事务组件等,并且运行在多个服务器上,所有的这些组件如何粘合到一起也许并不为团队中每个成员知晓,他们可能仅仅关注自己所编写的那部分。

另外,随着企业级应用程序划分成越来越多的系统,它们会运行在不同的服务器上,这意味着每个系统运行的 classpath、可用的依赖以及应用程序服务器本身的技术实现都可能大相径庭,对于这些互相关联的系统,最好是避免指定它们的依赖来自哪里以及系统的 classpath 是如何构造的。否则,对其中一个系统做改变很可能对其他系统有很大的影响。

因此,JavaEE 甚至比标准 Java 更需要为它的应用程序模块化。

借助OSGi复这问题

当前,OSGi 很好地解决了上述的这些不足。简单地说,OSGi 使用核心 Java 中的 ClassLoader 并扩展 JAR 清单 (manifest) 来创建一个比核心 Java 更具有模块化的系统。

OSGi 是一个大的话题,如果要清晰地理解它,需要更多的篇幅去介绍,如果你是第一次接触 OSGi, 建议首先阅读 [4], 这是我见到过的最好的 OSGi 书籍之一。

InfoQ 曾经推出过一个系列来讨论模块化和 OSGi 相关的知识:模块化 Java 简介模块化 Java:静态模块化模块化 Java:动态模块化以及模块化 Java:声明式模块化。为了兼顾一些读者 (也许他们的时间有限),我们仅介绍 OSGi 的几个重要概念,想了解更多信息的读者可以参考以上提及的文献和文章。

理解OSGi基本概念

OSGi BundleBundle的版本

OSGi Bundle 是由标准 JAR 以及在 JAR 清单中加上一些额外的 OSGi 元数据所构成。

OSGi 运行时常常被称作”OSGi 框架”,用来管理 OSGi Bundle 的生命周期并构建各个 Bundle 之间的依赖关系。在 OSGi 运行时的外面,Bundle 和标准 JAR 没有两样,但是,在 OSGi 运行时之中,情况则完全不同,一个 Bundle 中的类能够使用另一个 Bundle 中类,但是,这里有个前提,那就是另一个 Bundle 要显式地允许它的类被访问,否则,OSGi 运行时将阻止对这个 Bundle 中类的访问。这个访问规则非常类似 Java 语言中的可见性修饰符 (public,private,protect 以及包级私有)。

OSGi 运行时会通过依赖解析,将每个 Bundle 关联起来,形成一个依赖的图,

图 3: OSGi 让各个模块之间的依赖更加清晰。

摘自: “Enterprise OSGi In Action”第一章

相比较图 2 混乱的结构,从图 3 中,我们能够清晰地识别各个模块之间的依赖,这一切都要归功于 OSGi。

另一方面,最重要的一点是通过 Bundle 的包导入和导出机制,你不必再担心或猜测在应用程序运行时会丢失哪些依赖,因为,一旦真的丢失了这些依赖,OSGi 运行时会检测出丢失的依赖,然后抛出异常,根据异常你能够准确地分析出丢失了哪些依赖以及这些依赖的版本。

当然,为了完全消除 classpath 地狱中的版本冲突,OSGi Bundle 需要版本化。使用 OSGi 能够让具有不同版本的库 (在 OSGi 环境中,我们称之为模块或 Bundle) 以及导出的包共存,同时,对于依赖不同版本的库和包的模块来说,它们能够选择最适合的依赖库。如果依赖的库没有版本化,那么就没有办法知道旧库和新库之间的差异,我们会再次陷入 classpath 地狱。

限于篇幅,关于 OSGi Bundle 元数据和版本化,更多的内容请读者看一下 [2] 和 [4]。

OSGi动态性和生命周期管理

动态性对于软件工程并不是新的概念,但是,它是 OSGi 的基础和核心。你也许会说,通过反射、动态代理以及 URLClassLoader,也能够实现动态性,那么,OSGi 为什么会为动态性建立一个新的模型呢?

简单地说,通过以上的方式来实现动态性势必要利用 Java 底层的 API,而 OSGi 试图为开发人员提供一种更加方便和友好的方式来达到这一目标。

Bundle的生命周期

Bundle 不像普通的 JAR 那样安静地呆在 CLASSPATH 中,Bundle 能够根据需要启动和停止,一旦 Bundle 启动了,那意味着它的所有导入依赖都被满足了。Bundle 的启动和停止过程就像一个状态机一样,通过下图,我们能够清晰地发现 Bundle 的生命周期中的各个阶段:

图 4: Bundle 的生命周期中的各个阶段。

摘自: “Enterprise OSGi In Action”第一章

Bundle 能够在已安装 (Installed)、已解析 (Resolved)、正在启动 (Starting)、已启动 (Active)、正在停止 (Stopping) 以及已卸载 (Uninstalled) 这 6 个状态中进行迁移。

正在启动 (Starting) 和正在停止 (Stopping) 更多的是一个暂态,例如,当启动完成之后,Bundle 将进入已启动状态。Bundle 处在解析状态的条件是它已经被安装,并且它的所有依赖都被解析或者说被满足。当一个 Bundle 被卸载时,它不再能够提供包给任何新的 Bundle。

关于 OSGi 的其他一些概念如服务与服务注册表、ClassLoading 等,限于篇幅,没有办法一一介绍,详细的内容也请读者参考 [2] 和 [4]。

回到我们的问题, 也许你会问,既然很多应用服务器厂商已经采用了 OSGi 作为它们的内核,是否我们可以简单地将企业级 Java 和 OSGi 相结合?

很不幸,答案是否定的,因为一些原因,JavaEE 编程模型与 OSGi 并不兼容,关于这一点,Holly Cummins 和 Timothy Ward[2] 给出了清晰的解释。

OSGiJavaEE不能很好地合?

框架和类加载

典型的 JavaEE 应用服务器会驻留多个企业级 Java 应用程序,为了在这些应用程序之间提供某种层次的隔离,JavaEE 应用服务器会建立一个严格的类加载体系,通过这个类加载体系,应用程序之间相互隔离 (这里的隔离指的是应用程序之间不能互相访问各自的资源),应用程序与应用服务器之间也相互隔离。这个类加载体系基于标准 Java 的 ClassLoader 体系 [6]。以下是一个典型的 JavaEE 应用服务器的类加载体系,

图 5: 一个典型的 JavaEE 应用服务器的类加载体系

摘自: Neil Bartlett’s “OSGi in Practice”[7]

如图 5 所示,每一个 ClassLoader 仅仅能够加载它自己和它祖先定义的类,它不可能加载到同等层次的其他 ClassLoader 定义的类。因此,对于一些希望被 EJB 和 WEB 共享的类来说,它们必须要放置到更高的层次 (例如, EAR ClassLoader),另外,如果有一些类希望被所有应用程序共享,那么,它们必须要放置到 Application ClassLoader 中,通常,这是通过配置应用服务器本身来达到的。

另一方面,从图 5 我们能够发现应用程序的类不可能很容易地被应用服务器本身的类加载,这似乎不是什么大的问题,但是应用服务器中的一些容器为应用程序提供了插入点或者说”钩子”,通过回调应用程序的代码来完成一些共通的逻辑。因此,应用服务器必须要访问应用程序的类。

这个问题通过线程上下文ClassLoader(Thread Context ClassLoader)[8] 能够回避。通过正确地设置线程上下文 ClassLoader,框架能够检索这个 ClassLoader,然后访问应用程序以及它的模块中的类。但是,这也意味着,

  • 线程上下文 ClassLoader 必须在框架检索它之前在其他地方正确地被设置,但是,在 OSGi 中,线程上下文 ClassLoader 通常并不会被设置。
  • 使用线程上下文 ClassLoader 完全违反了系统的模块化,不能保证由线程上下文 ClassLoader 加载的类会匹配你的类空间。关于这一点,可以想象一下,如果线程上下文 ClassLoader 加载了一个类 A 的旧版本,而你的类空间希望匹配类 A 的新版本,那么,这种不匹配将导致大的问题。

我们提到,在 OSGi 中,线程上下文 ClassLoader 常常很少被设置,但是,凡事都不是绝对的,例如,GlassFish OSGi-JavaEE 的 OSGi/WEB 模块就配置了线程上下文 ClassLoader 来加载一些应用程序 Bundle 的类。

关于线程上下文 ClassLoader 的讨论和使用,我建议读者看一看 Neil Bartlett’s “The Dreaded Thread Context Class Loader”[9] 以及” OSGi Readiness — Loading Classes”[10],这两篇文章给出了一些精辟的观点。

META-INF (META-INF/services)ServiceLoader模式

从 JDK6 开始, 出现了一种能够发现给定接口的所有实现的模式, 称之为 ServiceLoader[11](注意: 普通的反射无法发现给定接口的所有实现 [12])。

在这种模式下,任何 JAR 都能够注册一个给定接口的实现并且将实现的名称放置在这个 JAR 的 META-INF/services 目录下的一个文件中,该文件的名字要根据所实现的接口来命名。然后,通过 ServiceLoader 的 load 方法来获取 classpath 中的所有接口的实现。例如,我们有一个接口 JAR(AInf.jar),其中定义了一个接口 AInf,

public interface AInf
{
long getCount();
…
}

我们再定义一个 AInf 的实现 JAR(AImpl.jar),其中定义了一个实现类 AImpl,

package sample.serviceLoader.internal
public Class AImpl implements AInf
{
…
}

然后,Java 客户端通过 ServiceLoader 获取 AInf 的所有实现,

ServiceLoader loader = ServiceLoader.load(AInf.class);
AInf service = loader.iterator().next();

这种方式对于标准 Java 来说已经做得很好了,但是,正如 [13] 中提到的那样,它有一定的局限性,最明显的是当运行时希望加入新的接口实现时,ServiceLoader 模式就不能发挥作用了,也就是说,ServiceLoader 不具有动态的可扩展性。

回到 OSGi 的话题,当希望将这样的代码移植到 OSGi 环境中时,问题将变得更多。

  • ServiceLoader 模式利用了线程上下文 ClassLoader 去发现 META-INF/services 目录下的所有的资源。关于线程上下文 ClassLoader,我们在上面已经提到,在 OSGi 的环境中,通常不会定义线程上下文 ClassLoader,所以有可能造成 ServiceLoader 模式失效。
  • OSGi 环境下,Bundle 之间的联系是通过” Import-Package”和”Export-Package”实现的,对于一个给定的导入,只能有一个确定的导出,因此,不可能通过 ServiceLoader 模式来获取接口的多个实现。
  • 初始化一个接口的实现一般需要访问实现类的内部细节,例如,我们的 AImpl 类定义在 *..internal 包中,对于 OSGi 来说,Bundle 一般都不会导出内部的细节 (因为这会打破 Bundle 的封装性),因此,ServiceLoader 模式也将失效。即便能够导出内部的细节,将客户端绑定到具体的实现,这也会违反松耦合的准则。
  • Bundle 有动态的生命周期,意味着实现 Bundle 很可能悄无声息的离开 OSGi 运行时,但是,ServiceLoader 模式并没有提供一种事件机制来通知客户端。

JavaEE没有实现动态

由于标准 Java 的种种不足以及 JavaEE 应用服务器的类加载体系,JavaEE

并没有实现动态的执行环境,这意味着当你的 WEB 应用程序或者 EJB 模块被部

署并且执行时,你不可能再添加新的 Servlet/JSP 或者更新 EJB 模块。应用程序

所能发挥的范围被绑定在了部署时。

幸运的是,随着业级OSGi的出现,OSGi 和 JavaEE 之间的鸿沟已经变得越来越小了。

业级OSGiJavaEE的集成

2007 年 6 月,OSGi 联盟 (OSGi Alliance) 发布了 OSGi Service Platform Release 4.1,在这个 Release 中,首次加入了面向企业级 OSGi 的规范,到 2013 年,OSGi 企业专家组 (EEG) 已经发布了 OSGi Enterprise Release 5 的最终 Draft,在这个 Release 5 中,加入了更多的面向企业 OSGi 的服务规范。

我们必须要指出,如果企业级 OSGi 不能提供更多在 JavaEE 中也可用的服务,那么企业级 OSGi 将变得毫无用处,因为,对于 JavaEE 来说,已经积累了非常庞大的开发群体,社区已经变得非常成熟,如果企业级 OSGi 提供了完全不同于 JavaEE 的规范或做法,那么将对 JavaEE 社区的开发群体毫无帮助,也不可能得到更多人的认同。

关于这一点,也正是驱动企业级 OSGi 不断向前发展的根本所在。

企业级 OSGi 的规范很多,覆盖了许多与 JavaEE 集成的服务。例如,

  • WEB 应用程序规范

    一个 OSGi 版本的 WEB 应用程序规范 (chapter 128 in the OSGi Enterprise Release 5 Specification[14]),这个规范的目的是为了部署一个既存的和新的 WEB 应用程序到运行在 OSGi 框架中的 Servlet 容器里,而部署的模型应该类似于 JavaEE 环境中 WEB 应用程序的部署。

    这个规范定义了“Web Application Bundle(WAB)”, 它是一个 Bundle 且与 JavaEE 中 WAR 执行同样的角色。WAB 使用了 OSGi 的生命周期以及 OSGi 的类 / 资源加载规则而没有使用标准 JavaEE 环境的加载规则。WAB 是常规的 Bundle,因此能够使用 OSGi 框架中的所有特性。

    一个传统的 WAR 也能够作为 WAB 进行安装,这是通过清单重写来完成的。关于这一点,GlassFish 4.0 已经实现了这个特性。

  • JPA 服务规范

    Java 持久化 API 是 JavaEE 中一个重要的规范。JPA 提供了一个对象关系映射 (ORM) 模型,这个模型通过持久化描述符来配置。企业级 OSGi 的 JPA 服务规范 (chapter 127 in the OSGi Enterprise Release 5 Specification[14]) 定义了持久化单元如何能够被发布到 OSGi 框架中、客户端 Bundle 如何发现这些持久化单元,以及通过 OSGi JDBC 规范如何能够发现数据库驱动等。

除了上述两个服务规范之外,还有许多有用的服务规范,如,Java 事务服务规范 (chapter 123 in the OSGi Enterprise Release 5 Specification[14]) 以及 Blueprint 容器规范 (chapter 121 in the OSGi Enterprise Release 5 Specification[14]),后者是企业级 OSGi 中最重要的规范之一,连同 Declarative Services 规范 (chapter 112 in the OSGi Enterprise Release 5 Specification[14]) 和 RFC 193 CDI 集成规范 [15],均被视为 OSGi 中的依赖注入规范,我认为依赖注入规范是企业级 OSGi 最吸引人的几个规范之一。另外,Service Loader Mediator 规范 (chapter 133 in the OSGi Enterprise Release 5 Specification[14]) 的引入正是为了解决我们在上面提到的 ServiceLoader 模式的一些问题。

限于本专题的篇幅,不能一一提到每个服务规范,感兴趣的读者可以详细阅读 OSGi Enterprise Release 5 Specification[14]。

接下来,让我们看看企业级 OSGi 在 GlassFish 中的实现状况。

GlassFish中的业级OSGi

首先, 我们将再次回顾一下 GlassFish 与 OSGi 的关系。其次,我们将看一下 GlassFish 中的企业级 OSGi。

GlassFishOSGi

前面已经提到,自从 GlassFish 3.0 开始,GlassFish 的内核已经完全采用 OSGi,并且内核由一系列 OSGi Bundle 实现。当启动 Glassfish 时,首先启动 OSGi 运行时 (默认地,OSGi 运行时是 Apache Felix),然后,GlassFish 通过扩展 OSGi 框架的配置机制 (对于 Apache Felix,文档 [5] 很好地说明了框架的配置属性),安装并且启动内核 Bundle。由于 GlassFish 是一个实现了 JavaEE 规范的应用服务器,因此,实现 JavaEE 规范的各个容器或者说组件都被包装成了 OSGi Bundle。那么,当启动 GlassFish 时,这些容器 Bundle 也将被安装到 OSGi 运行时中。

理解GlassFish OSGi-JavaEE

GlassFish 不仅仅实现了最新的 JavaEE 规范,而且也暴露 JavaEE 组件模型和 API 给 OSGi 应用程序 Bundle,换句话说,OSGi 开发人员现在也能够使用 JavaEE 组件模型 (例如: JSF、JSP、EJB、CDI、JPA、JTA 等)。这正是企业级 OSGi 所希望达成的: 与 JavaEE 模型相集成。这对于从事企业级 OSGi 开发人员来说是非常重要的,因为他们现在既能够使用 JavaEE 的强大而成熟的特性,又能够达到 OSGi 模块化以及面向服务所带来的好处。

在 GlassFish 中,JavaEE 平台的服务 (例如: 事务服务、HTTP 服务、JMS 服务等) 都被视为 OSGi 服务,因此,OSGi 应用程序 Bundle 能够通过 OSGi 服务注册表去获取这些服务。

因此,我们给 GlassFish OSGi-JavaEE 做一个准确的定义:

“GlassFish启了OSGiJavaEE的双向交互。一方面,由OSGi框架管理的OSGi激活由JavaEE容器管理的JavaEE件。另一方面,由JavaEE容器管理的JavaEE件能激活由OSGi框架管理的OSGi

应用程序开发人员能够声明性地导出 EJB 作为 OSGi 服务,不必写任何 OSGi 服务导出代码。这样就允许任何纯的 OSGi 组件 (没有运行在 JavaEE 上下文中) 去发现这个 EJB,然后去激活它的业务方法等。类似的,JavaEE 组件能够定位由非 JavaEE OSGi Bundle 提供的 OSGi 服务,然后使用这些服务。另外,我们在未来专题中将看到,GlassFish 扩展了上下文依赖注入 (CDI) 使得 JavaEE 组件以类型安全的方式使用动态的 OSGi 服务更加得便利。以下是 GlassFish 中 OSGi-JavaEE 相关的各个模块的位置关系。

图 6: GlassFish 中 OSGi-JavaEE 相关的容器的位置关系

摘自: OSGi Application Development using GlassFish Server[16]

在 GlassFish OSGi-JavaEE 中,基于 OSGi Bundle 所使用的特性,它们被划分成两类,

① 普通 (plain vanilla) 的 OSGi Bundle

这些 Bundle 不包含任何 JavaEE 组件也不使用任何 JavaEE 特性。

② 使用 JavaEE 的 OSGi Bundle(也称作合成应用程序 Bundle)

这些 Bundle 包含 JavaEE 组件。因此,这样的 Bundle 不仅仅是一个 OSGi Bundle,而且是一个 JavaEE 制品(artifact)。

对于使用 GlassFish OSGi-JavaEE 开发的应用程序来说,它们能够使用如下两类 API,

  • OSGi API

    目前,GlassFish 4.0 带有一个符合 OSGi R4.2 的框架,因此所有 OSGi 4.2 的核心 API 都可用,进一步地,GlassFish 也带有许多一般性的 OSGi 服务,例如,

    • OSGi 配置 Admin 服务
    • OSGi 事件 Admin 服务
    • OSGi Declarative 服务

    GlassFish 使用了来自 Apache Felix 的这些服务的实现。

  • JavaEE API

    一个用户部署的 OSGi Bundle 不仅仅能够使用 OSGi API,而且也能够使用 JavaEE API。值得注意的一点是,GlassFish 4 是一个实现了 JavaEE 7 的应用服务器,因此,用户能够使用最新的 JavaEE API。但是,并非所有 JavaEE API 都对各种类型的 OSGi Bundle 可用,

    • 类别 A: 由 JavaEE 容器管理的组件,例如,EJB、CDI、Servlet、JSF 以及 JAX-RS 等。这些组件由容器管理,因此,它们对于普通 (Plain vanilla) 的 OSGi Bundle 不可用,因为普通的 OSGi Bundle 不受 JavaEE 运行时管理。一个 OSGi Bundle 必须成为合成应用程序 Bundle 才能够使用这些组件相关的 API。
    • 类别 B: 访问平台服务的 API,例如,JNDI、 JTA、 JDBC 以及 JMS 等,OSGi Bundle 的开发人员也能够使用这些 API。典型地,一个 Bundle 必须使用 JNDI 去访问服务和资源,但 GlassFish 实际上使这些平台服务变成了 OSGi 服务,因此,OSGi 应用程序开发人员能够使用 OSGi 服务 API 去访问这些平台服务。
    • 类别 C: 功能 API,像 JAXB、JAXP 以及 JPA 等。

      绝大多数这些 API 都有一个插入层从而允许应用程序插入不同的实现,典型地,这种可插入性是通过标准 Java 的 ServiceLoader 模式来完成的,我们前面提到过,ServiceLoader 模式是利用了线程上下文 ClassLoader 来发现接口的所有实现,因此,必须要设置正确的线程上下文 ClassLoader。但是,GlassFish 没有这样的限制,对于部署在 GlassFish 的 OSGi Bundle 来说,可以安全地使用这些功能 API。

在接下来的各个专题中,我们将详细地理解 GlassFish OSGi-JavaEE 的各个模块以及演示如何开发合成应用程序 Bundle 来更好地结合 JavaEE 和 OSGi。

在 Part2 中我们将首先看一下 GlassFish OSGi/WEB 模块,一方面带领大家深入理解 OSGi WEB 规范,另一方面学习如何部署一个 WEB 应用程序 Bundle 到 GlassFish 中。

参考

[1]: “ Why banks are likely to face more software glitches in 2013”

[2]: “Enterprise OSGi in Action”

[3]: ”No Solution for Complexity?”

[4]: “OSGi In Action”

[5]: “Apache Felix 框架配置属性”

[6]: “Inside Class Loaders”

[7]: Neil Bartlett’s “OSGi in Practice”

[8]: “Find a way out of the ClassLoader maze”

[9]: Neil Bartlett’s “The Dreaded Thread Context Class Loader”

[10]: Neil Bartlett’s “OSGi Readiness — Loading Classes”

[11]: “ServiceLoader”

[12]: “How can I get a list of all the implementations of an interface programmatically in Java?”

[13]: “Creating Extensible Applications With the Java Platform”

[14]: “OSGi Enterprise Release 5 Specification”

[15]: “OSGi Early Draft”

[16]: “OSGi Application Development using GlassFish Server”

作者简介

汤泳,高级工程师,硕士,2004 年毕业于南京理工大学计算机科学与技术系。现就职于南京富士通南大软件技术有限公司。 2013 年 2 月成为 GlassFish OSGi 以及 OSGi-JavaEE 模块的 Committer, 同时也是 OSGi Alliance 的 Supporter。除了长期贡献 GlassFish, 还积极活跃在多个 Apache 开源社区,如 Apache JClouds、Apache Karaf 以及 Apache Aries。

E-Mail: tangyong@cn.fujitsu.com 或者www.tangyong.gf@gmail.com


感谢池建强对本文的审校。

给 InfoQ 中文站投稿或者参与内容翻译工作,请邮件至editors@cn.infoq.com。也欢迎大家通过新浪微博(@InfoQ;)或者腾讯微博(@InfoQ;)关注我们,并与我们的编辑和其他读者朋友交流。