GlassFish OSGi-JavaEE Part1: GlassFish与企业级OSGi开发

 由  Tang Yong 发布

本文在InfoQ发布,经作者同意发布到OSGi社区。

[作者]

汤泳,高级工程师,硕士,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

LinkedIn : http://www.linkedin.com/pub/tang-yong/21/62b/809

Blog: http://osgizone.typepad.com/

[前言]
欢迎进入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,建议大家参考以下链接: https://glassfish.java.net/getstarted.html

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

1) Neil Bartlett’s “OSGi in Practice”

http://njbartlett.name/files/osgibookpreview20091217.pdf

2) Richard S. Hall等 “OSGi In Action”

http://www.manning.com/hall/

GlassFish OSGi-JavaEE Part1: GlassFish与企业级OSGi开发

如果用一句话来定义”什么是 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相关的知识:Modular Java: What is it?),Modular Java: Static Modularity(该文的译文为:模块化Java:静态模块化),Modular Java: Dynamic Modularity)以及Modular Java: Declarative Modularity,我们仅介绍OSGi的几个重要概念,想了解更多信息的读者可以参考以上提及的文献和文章。

理解OSGi基本概念

OSGi Bundle和Bundle的版本

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的生命周期中的各个阶段:

图5: 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]给出了清晰的解释。

为什么OSGi和JavaEE不能很好地结合?

框架和类加载

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

图6: 一个典型的JavaEE应用服务器的类加载体系 摘自: Neil Bartlett’s “OSGi in Practice”[7]

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

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

这个问题通过线程上下文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之间的鸿沟已经变得越来越小了。

企业级OSGi与JavaEE的集成

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。

GlassFish与OSGi的关系

前面已经提到,自从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开启了OSGi和JavaEE的双向交互。一方面,由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相关的各个模块的位置关系。

图7: 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” http://www.bbc.co.uk/news/technology-21280943

[2]: ”Enterprise OSGi in Action” http://www.manning.com/cummins/

[3]: ” No Solution for Complexity?” http://njbartlett.name/2013/02/04/no-solution-for-complexity.html

[4]: “OSGi In Action” http://www.manning.com/hall/

[5]: “Apache Felix 框架配置属性” http://felix.apache.org/site/apache-felix-framework-configuration-properties.html

[6]: “Inside Class Loaders” http://www.onjava.com/pub/a/onjava/2003/11/12/classloader.html

[7]: Neil Bartlett’s “OSGi in Practice” http://njbartlett.name/files/osgibookpreview20091217.pdf

[8]: “Find a way out of the ClassLoader maze” http://www.javaworld.com/javaworld/javaqa/2003-06/01-qa-0606-load.html

[9]: Neil Bartlett’s “The Dreaded Thread Context Class Loader” http://njbartlett.name/2012/10/23/dreaded-thread-context-classloader.html

[10]: Neil Bartlett’s “OSGi Readiness — Loading Classes” http://njbartlett.name/2010/08/30/osgi-readiness-loading-classes.html

http://docs.oracle.com/javase/6/docs/api/java/util/ServiceLoader.html

[12]: “How can I get a list of all the implementations of an interface programmatically in Java?” http://stackoverflow.com/questions/347248/how-can-i-get-a-list-of-all-the-implementations-of-an-interface-programmatically

[13]: “Creating Extensible Applications With the Java Platform” http://www.oracle.com/technetwork/articles/javase/extensible-137159.html

[14]: “OSGi Enterprise Release 5 Specification” http://www.osgi.org/Download/File?url=/download/r5/osgi.enterprise-5.0.0.pdf

[15]: “OSGi Early Draft” http://www.osgi.org/Download/File?url=/download/osgi-early-draft-2013-03.pdf

[16]: “OSGi Application Development using GlassFish Server” https://glassfish.java.net/public/GF-OSGi-Features.pdf


Tang Yong 2013-09-23 22:53

回复admott: 是的,文章本身定位在GlassFish OSGi-JavaEE,重点在以GlassFish为例介绍企业级OSGi,就移植性看也许不太合适,关于JTA这块,我将反馈给社区,甚至和OSGi EEG的一些成员进一步讨论,总之,非常感谢你的comment!

顶(0) 踩(0) 回复

admott 2013-09-23 22:02

回复Tang Yong: 你的JPA例子,到目前仅能在glassfish内跑,其他容器都无法在persistenunit 中直接使用 JTA,仅能使用RESOURCE_LOCAL。其他appserv 的做法只能通过blueprint 对 persistenceunit 显式注入jta。对于4.2 ent 标准而言,jpa部分也明确了OSGi容器下只支持 resrouce_local,而jta 则是其他容器(如EE容器)的事情。

顶(0) 踩(0) 回复

Tang Yong 2013-09-23 11:45

回复admott: 就这个例子来说,我们正在谈论GlassFish OSGi-Java EE,对于其他容器或者平台,例如wildfly, karaf, 配置会不同,这是移植性的范畴。如果你说得更为具体的话,我会反馈给社区并且就用户场景与leader进行讨论,非常感谢回复!

顶(0) 踩(0) 回复

admott 2013-09-19 17:32

回复Tang Yong: 这个官方jpa例子不太好,JTA事务方面比较难以控制,也许只有glassfish才能接受这种配置,其他容器估计跑不动。

顶(0) 踩(0) 回复

Tang Yong 2013-08-26 14:10

回复Tang Yong: 漏了设置了:<br><br>java-config debug-options="-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=9009" system-classpath="" classpath-suffix=""

顶(0) 踩(0) 回复

Tang Yong 2013-08-26 13:59

回复eric.deng: 在启动glassfish时使用调试方式, asadmin start-domain --debug,默认地glassfish在端口9009上侦听远程debug,所以,打开eclipse,然后创建远程debug配置,使用attach方式到9009端口了,然后你可以调试你的程序了。还有一种是调试内核,将domain.xml中debug配置的pending设置为yes(如下),这样,在启动的时候等待你来attach它,同样的在eclipse中创建远程debug,使用attach方式,然后你就可以调试内核了。<br><br> <java-config debug-options="-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=9009" system-classpath="" classpath-suffix=""><br><br>我不太清楚你需要哪一种,根据上面的你可以自行选择哈。

顶(0) 踩(0) 回复

eric.deng 2013-08-26 10:43

回复罗俊杰: 嗯嗯

顶(0) 踩(0) 回复

罗俊杰 2013-08-26 10:36

回复eric.deng: 我没用过glassfish。但是如果有eclipse插件的话,debug模式下自然而言就能f5 f6调吧。@Tang Yong 可以指点一下:)

顶(0) 踩(0) 回复

eric.deng 2013-08-26 09:42

遇到一个问题:目前我的调试方法是用eclipse打包成bundle后,放入glassfish中进行调试,超级麻烦。<br>请问有经验的朋友:是否能在eclipse下面启动bundle,从而进行单步调试?

顶(0) 踩(0) 回复

Tang Yong 2013-08-14 18:10

回复eric.deng: 呵呵,不客气,有什么问题,再告诉我,记得把server.log文件给我好有助于分析。

顶(0) 踩(0) 回复

eric.deng 2013-08-14 17:56

回复Tang Yong: 谢谢指点,万分感谢啊!现在我就放弃传统的方法,改用注册服务方式。

顶(0) 踩(0) 回复

Tang Yong 2013-08-14 17:25

回复eric.deng: 推荐使用JPA,注册实体管理器工厂到OSGi服务注册表中,传统的方法可能会因为ClassLoader的问题而失败,关于OSGi/JPA我将在后面Part阐述。以下是一个Sample。<br><br>https://svn.java.net/svn/glassfish~svn/trunk/fighterfish/sample/uas/entities

顶(0) 踩(0) 回复

eric.deng 2013-08-14 17:05

请问glassfish中自己写一个bundle想连接oracle,是不是只能用服务的方式获得数据库的connection,如果用传统的获得connection的方法就会不成功?

顶(0) 踩(0) 回复

Tang Yong 2013-08-08 19:52

一条命令的切换似乎有点问题,还是设置环境变量然后再切换。<br><br>[Windows]<br>set GlassFish_Platform=Equinox<br>asadmin start-domain<br><br>[linux]<br>export GlassFish_Platform=Equinox<br>asadmin start-domain

顶(0) 踩(0) 回复

Tang Yong 2013-08-08 19:47

回复Ruici: 关于切换OSGi运行时,可以使用如下方法:<br>export GlassFish_Platform=Equinox<br>asadmin start-domain<br><br>或者一条命令: asadmin start-domain -DGlassFish_Platform=Equinox

顶(0) 踩(0) 回复

Tang Yong 2013-08-08 19:42

回复Ruici: 原因是相对于Equinox和Knopflerfish, Felix更加的轻量级,另外,Felix的leader Richard曾经贡献过GlassFish v3,切换完全可以,但是在GlassFish 4时, 切换到Equinox还有一些不稳定,Knopflerfish的在规范的一些功能的实现上和Felix有点不太一样,有些bug被发现,所以建议还是用Felix为好。

顶(0) 踩(0) 回复

Ruici 2013-08-08 19:33

glassfish在选用底层OSGi框架时选择的Apache Felix是基于什么考虑的呢?它可以被替换为Knopflerfish, Equinox吗?

顶(0) 踩(0) 回复

张卫滨 2013-08-06 11:59

回复罗俊杰: 好的 3ks

顶(0) 踩(0) 回复

罗俊杰 2013-08-06 11:46

回复张卫滨: 已经加上

顶(0) 踩(0) 回复

张卫滨 2013-08-06 11:32

请指明原文链接吧

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