用 OSGi 应用程序开发和工作的最佳实践

Comments

简介

OSGi 模块性提供了标准机制来以 Java 应用程序应对共同挑战。在 2007 年,OSGi Alliance Enterprise Expert Group (EEG) 成立,以一个业务 Java 编程模型的形式向业务应用程序开发人员引入 OSGi 基础设施。OSGi 应用程序和 IBM WebSphere Application Server 企业级服务质量共同为模块化 Web 应用程序提供最完整和最健壮的业务服务器。您可以使用 WebSphere Application Server Feature Pack for OSGi Applications and JPA 2.0 来部署和管理 Web 应用程序,作为一组版本 OSGi bundle。您也可以配置一个或多个 bundle 存储库,作为供应技术设施部分,来承载多个应用程序使用的公共 bundle 和简化使用这些公共 bundle 的应用程序部署。WebSphere Application Server V7 Feature Pack for SCA V1.0.1.5 升级版添加了对由异构资产组成的 OSGi 应用程序的支持,以支持面向服务体系结构(SOA)概念。(见 参考资料

对于任何新技术都有一些该做的和不该做的建议,对架构师、开发人员和部署人员来说这也称为最佳实践。OSGi 技术已经使用了十多年,并且在那时出现了许多最佳实践。本文介绍了为 OSGi Applications feature of WebSphere Application Server 编写 OSGi 应用程序和集成 Service Component Architecture (SCA) 相关的主要最佳实践。其中一些是常用 OSGi 最佳实践,一些是专用于 WebSphere Application Server 中提供支持的;为了清晰起见,属于后者的最佳实践已指明。

最佳实践

本文介绍以下最佳实践:

  1. 使用 Blueprint
  2. 使用 Blueprint 来启用基于服务的供应
  3. 使用 Blueprint 来启用 SCA 集成
  4. 版本是可控的
  5. 从实现中分离 API
  6. 共享服务而不是实现
  7. 好的 bundle 就像构造良好的类:松耦合、高聚合
  8. 避免包和 Require-Bundle 分离
  9. 列出 Application-Content 头中包含的内容
  10. 使用 WAB 而不是 WAR
  11. 必要时只使用 Use-Bundle
  12. 使用持久 bundle 来共享您的持久性单元
  13. 充分利用提供的组件模型
  14. 让容器担起重任

每个最佳实践在后续部分逐一详细介绍。

1. 使用 Blueprint

一般而言,使用 Blueprint 是一个最佳实践。Blueprint 提供的一些 Blueprint 支持一个简易的 POJO 开发,并支持测试模型、简易组件组装和基于开发标准的这一事实。在 WebSphere Application Server OSGi 应用程序中使用 Blueprint 是额外推荐的,因为它增强了对容器集成、Service Component Architecture (SCA) 集成和基于服务的供应的支持。

原因如下

Blueprint 是一个基于 Spring Framework 的简易组件装配模型,它是被 SGi Alliance 在 Enterprise OSGi R4 V4.2 规范中由 SpringSource 进行标准化的。 作为标准化的 Spring,它支持相同的依赖注入模式,使简单的 Java 组件能够不使用框架 API 而进行开发,易于进行单元测试。

为 WebSphere Application Server 开发企业级 OSGi 应用程序 一文列出了 WebSphere Application Server 支持 Blueprint 组件模型的原因,这是由客户需求所激发的。WebSphere Application Serve 中支持的 OSGi 应用程序,其中大多数依赖于 Blueprint 的使用来启用某一个功能,例如基于服务的配置和 SCA 集成。尽管通常情况下使用 Blueprint 是一个好主意,当开发 WebSphere Application Server OSGi 应用程序时,甚至还有更多的理由这样做。

2. 使用 Blueprint 启用基于服务的供应

(专用于WebSphere Application Server)

使用 Blueprint 开发和使用 OSGi 服务时,Blueprint 定义将被用来确保在应用程序配置到 WebSphere Application Server 时满足服务依赖性。

原因如下

一个 OSGi bundle 清单描述了一个 bundle 从另一个 bundle 导入的包以及导出给其他 bundle 使用的包。因此,对于给定一个具体 bundle,为了满足所有包导入(包括传递依赖),决定一组必须的 bundle 很有可能。一个最佳实践是将界面和实现分到不同的 bundle(见 将 API 从实现分离)。另一个最佳实践是使用 OSGi 服务实现依赖(见 共享服务而不是实现))。这两个最佳实践的结果就是配置包依赖项仅满足界面需求,而不能提供实现 bundle。WebSphere Application Server OSGi 应用程序特性通过使用 Blueprint 定义决定一个 bundle 提供和需要的服务来解决这一问题。然后,在应用程序部署期间使用该信息从应用程序归档文件(.eba)或内部 bundle 存储库(一个配置和管理 WebSphere Application Server 管理的 bundle 存储库)提供实现。

示例

图 1. 基于供应示例的服务
图 1. 基于供应示例的服务
图 1. 基于供应示例的服务

图 1 显示了一个 API bundle、一个提供服务的实现和一个使用来自实现 bundle 的客户端 bundle。客户端 bundle 和实现 bundle 在 API bundle 上都有一个包依赖项(package dependency),其中含有服务 Java 界面。客户端 bundle 是 OSGi 应用程序(在内容中列出)的一部分,API 和实现 bundle 是共享的,都是来自于内部 bundle 存储库。API bundle 将配置到 WebSphere Application Server 作为包依赖的结果,如果客户端 bundle 和实现 bundle 都是使用 Blueprint 实现的,那么实现 bundle 也将被配置。如果这两个 bundle 中有一个不使用 Blueprint,那么将不配置实现 bundle 。

如果,实现 bundle 使用 Blueprint,而客户端不使用,那么也提供应用程序,但是没有实现 bundle 。这是因为供应程序没有意识到客户端 bundle 缺失服务依赖。如果客户端 bundle 使用 Blueprint 但是实现 bundle 没有使用,那么应用程序将不能部署,因为供应程序不能满足客户端 bundle 服务依赖。

3. 使用 Blueprint 来启用 SCA 集成

(专用于 WebSphere Application Server)

开发集成其他技术的 OSGi 应用程序时,一个最佳实践(实际上是必不可少的)是使用 Blueprint 来定义 OSGi 应用程序的服务实现。

原因如下

在 WebSphere Application Server 中,SCA 用于聚合 OSGi 应用程序和其他应用程序类型(例如,Java EE)。它也用来通过各种传输和协议(例如 JMS、Web services、Atom)公开 OSGi 应用程序服务,也可使服务依赖通过这些传输和协议进行调用。要做到这一点,SCA 需要一个由 OSGi 应用程序提供的服务描述和应用程序需要的服务。SCA 重新使用 Blueprint XML 中描述的信息来推出 SCA 所需的信息。因此将 OSGi 应用程序公开到 SCA 时,必须使用 Blueprint。如果一个 OSGi 应用程序包含使用 Blueprint 时没有定义的服务,那么必须创建向 Blueprint 描述这些服务的 Blueprint 虚包,然后将请求转发给非 Blueprint 服务实现。

图 2. Blueprint 支持 SCA 集成
图 2.  Blueprint 支持 SCA 集成

图 2 显示了一个 OSGi 应用程序和一个 bundle,提供了一个由 SCA 从应用程序外部调用的服务。这个调用来自另一个组件(似乎是另一个 OSGi 应用程序或一个 Java EE 应用程序),或者来自一个特定的绑定,例如一个 Web 服务或 JMS 调用。同一个 bundle 也需要一个由 SCA 提供的外部应用程序服务。这可能再一次调用另一个 SCA 组件,或在一个特定的绑定上进行一次调用。在图 2 的第一个图表中,Bundle A 是使用 Blueprint 实现的。SCA 使用 Blueprint 服务定义来了解如何分辨和调用目标 OSGi 服务。SCA 不使用 Blueprint 服务参考定义,因为在应用程序清单(用于定义一个 OSGi 应用程序的构件)中有充足信息来确定什么样的服务是有效目标。在第二个图表中,服务和参考资料在 Bundle B 中使用其他组件模型实现,例如,声明服务(DB)。SCA 不能理解 DS,因此这是无效的。在第 3 个图表中,一个 Blueprint 虚包 bundle(Bundle C )用于向 SCA 描述服务,然后将调用转发到 Bundle B 中的 DS 服务实现。

4. 版本是可控制的

为 bundle 和包提供语义版本来启用 API 客户端和提供商之间的松耦合。

原因如下

在 OSGi 中,包和 bundle 是版本可控制的,如果没有指定版本,则默认为 0。包和 bundle 应该是从语义上进行版本库控制的,便于客户端进行自我保护,以防 API 更改而导致中断。

语义版本控制(semantic versioning)定义了一个版本控制模式,使其可以向客户端传递版本兼容信息。语义版本控制 使用主版本号.次版本号.微版本号(major.minor.micro)的编号模式。主版本号、次版本号和微版本号都是自然数(0,1,2,等等)。

  • 在主版本中的改变表示一个二进制不兼容 API 改成客户端 API;这就是说,在版本 2 中,API 中进行的客户端编译需要在版本 3 中重编译才能生效。一个二进制不兼容改变的示例是删除一个接口或方法。
  • 在次版本中的改变表示 API 已被增强,但是现有客户端不需要被重新编译。这类改变的一个例子就是添加一个接口或一个方法。
  • 在微版本中的改变表示已进行了一个改变但是不需要改变 API。这类改变的示例是一个删除 NullPointerException 的 bug 修复。

API 以这种方式控制版本,使客户端可以将 API 版本限制到其可用的范围。当导入一个包或需要一个 bundle 时,客户端可以要求他们想要的版本。例如(1,2)允许客户端使用版本 1 的包或 bundle 进行工作,而不能使用版本 2 的包或 bundle。这是因为客户明白,将来的变更可能是一种使用主版本的二进制兼容变更。

如果语义版本控制用于 bundle 和包,那么在运行时可能立刻会有 API 的多个版本,且和 bundle 同时使用。

到目前为止,最佳实践已经介绍了关于 API 客户端的兼容性。当遵守以下 从实现中分离 API 最佳实践时,API 的实现将在一个独立的 bundle 中,也可从版本控制中获益。对于一个实现者(implementor),主版本或次版本中的改变表示一个二进制不兼容改变,例如,它们可能需要实现一个新接口或方法。(使用语义版本控制的额外优势和注意事项,见 OSGi Alliance Semantic Versioning Technical Whitepaper。)

示例

图 3 显示了一个 API 的两个提供商,其中 API 是同一版本。在 OSGi 中,这两个提供商被认为是相同的。客户端(左边)和实现者(右边)都表达了一种依赖,将匹配各自 API 提供商。

图 3. 客户端和实现者可以任意使用两个相同版本的包
图 3. 客户端和实现者可以任意使用两个相同版本的包
图 3. 客户端和实现者可以任意使用两个相同版本的包

图 4 显示了不同版本中的两个 API 提供商。也显示了次版本中的一个改变如何分别影响客户端和实现者。既然这样,您可以提供 API 的1.0 版本和 1.1 版本。客户 A 可以使用任何一个 API 提供程序;客户 B 使用新 API 方法,可使用最高版本 1.1。Implementation A 使用 bundle API A 提供的 API,而 Implementation B 只使用 bundle API B 提供的 API 。

服务注册表将对请求者可见的服务限制在执行与包绑定的 API 版本的那些请求者范围内,知道这一点很重要。

图 4. 一个次要 API 版本改变如何不同程度地影响客户端和实现
图 4. 一个次要 API 版本改变如何不同程度地影响客户端和实
图 4. 一个次要 API 版本改变如何不同程度地影响客户端和实

图 5 从客户和实现者角度显示了一个不兼容的 API 改变。既然这样,Implementation A 和 Client A 被绑定到 API A,Implementation B 和 Client B 绑定到 API B。

图 5. 一个主要 API 版本改变如何同样影响客户端和实现
图5.  一个主要 API 版本改变如何同样影响客户端和实
图5. 一个主要 API 版本改变如何同样影响客户端和实

5. 从实现中分离 API

构建 OSGi bundle,一个最佳实践是将任何 API 类从实现类放入单独的 bundle。

原因如下

从实现类分离 API 类比较灵活。一个单独的 API bundle 使一个客户端可以使用任何实现提供商;此外,也可使多个提供商同时使用。如果没有这个分离,客户端 bundle 需要重启来连接一个新提供程序 bundle。

该最佳实践也可以减少 API bundle 包依赖性,因而减少不同 API 之间的循环依赖性。当两个 API 实现内部彼此使用 API 时,这很容易发生。

一旦 API 类放入一个独立的 bundle,确保没有实现包被导出是很重要的,API 实现是使用公共 API 作为 OSGi 服务发布的。

示例

图 6 中的场景展示了一个客户端 bundle,从一个没有分离这些包的提供商 bundle 导入 API 类和实现类,这个提供商 bundle 也导出实现类和 API 类。这是一个糟糕的实践,因为没有分离,也因为实现类被导出且不能作为服务公开。

图6. 设计糟糕的提供商 bundle ,其中 API 类和实现类是在同一 bundle 中
图 6. 设计糟糕的提供商 bundle ,其中 API 类和实现类是在同一 bundle 中
图 6. 设计糟糕的提供商 bundle ,其中 API 类和实现类是在同一 bundle 中

另一个场景如图 7 所示,其中显示了一个客户端,从一个 bundle 中导入 API 包,从另一个中导入实现包。尽管 API 包和实现包已被分离,提供商仍然导出实现包,而不是作为服务公开该类。

图 7. 设计糟糕的提供商 bundle,其中 API 类和实现类已被分离
图 7.  设计糟糕的提供商 bundle,其中 API 类和实现类已被分离
图 7. 设计糟糕的提供商 bundle,其中 API 类和实现类已被分离

最后一个场景如图 8 所示,显示了最佳实践。提供商 bundle 已经分离了实现包和 API 包,此外,实现类也被作为 OSGi 服务公开。

图 8. 设计良好的提供商 bundle,其中 API 类和实现类已被分离
图 8. 设计良好的提供商 bundle,其中 API 类和实现类已被分离
图 8. 设计良好的提供商 bundle,其中 API 类和实现类已被分离

6. 共享服务而是不实现

开发 OSGi bundle 时,一个最佳实践是使用 OSGi 服务注册表来实现工厂模式,并用其他 bundle 构造导出类。除了 从实现中分离 API 最佳实践之外,还实现了一个客户端、API 和 API 实现的松耦合。

原因如下

分离 API 和实现的实践从消耗资源的客户端抽象了多个实现。这使得一个供应商的 API 实现可以被另一个替换,且不需要变更使用 API 的客户端。

想要行之有效,对于 API 和实现的分离,客户端需要一个机制来获取一个实现实例,而无需知道将要使用哪个实现。在非 OSGi 系统中,这通常是由一个 API 类中的静态工厂方法来完成的。静态工厂方法从类路径上的实现中选择要实例化哪个实现。由于平面类空间的所有这些实现都是可视的。用于决定哪个类应该被实例化的算法是特定于 API 的。

这个机制在 OSGi 中不能正常工作,因为含有 API 类的 bundle 不能看到任何含有 API 实现的 bundle。

OSGi 以 OSGi 服务注册表的形式提供一个更为整洁的解决方案。Bundle 含有以下 API 实现之一:

  • 在 OSGi 服务注册表中注册一个 API 接口实例。这可以产生这种效果,即所有客户端 bundle 仅使用一个实现实例。
  • 在 OSGi 服务注册表中注册一个 OSGi ServiceFactory 接口。ServiceFactory 接口在请求一个服务实例时为 OSGi 所用。在这种情况下,ServiceFactory 可以选择实例来返回到每个请求客户端 bundle。例如,可以向所有的请求客户端 bundle 返回同一个实例,或可以返回不同的实例,或两者兼而有之。

客户端是完全从实现中分离出来了,正如在静态工厂方法模式中那样。此外:

  • API 是完全从所有实现中分离出来了,正如 OSGi 服务层与实现合作应对实例创建。
  • 用于决定哪个实现将要返回的算法是贯穿所有 API 的而不是特定于某一个 API,就像在非 OSGi 系统中使用的静态工厂方法模式那样。

OSGi 服务注册表是一个共享服务的动态机制,其中服务可以在运行时自由来往。在服务可用时,Blueprint 通过将一个请求服务注入客户端 bundle 的 bean 中,帮助客户处理这种动态性。服务不可用时,Blueprint 可以注入另一个实现(如果有一个已注册的)。

7. 好的 bundle 就像构造良好的类:松耦合、高聚合

编写 bundle 时,将功能限制到一个特定的任务。将一个 bundlle 执行的任务数量降到最低,尝试尽量少的依赖。

原因如下

编写一个 OSGi 应用程序时,总想向一个已有 bundle 添加更多的功能,而不是去编写一个新的 bundle 。然而,这很快就会产生一个大且聚合性低的 bundle,通常会导致 bundle 导入和导出大量的包。使用 Require-Bundle 头部隐藏大量包导入并不是一个好的实践,因为这只不过是在 bundle 之间添加紧耦合。随着 bundle 依赖图的增长,必须下载和安装的 bundle 数量不断增多,通常成指数增长。这称之为 “hairball 效应” 。一个设计不佳的 bundles 需要在您的应用程序中下载并安装数十甚至上百兆,而仅仅能访问一到两个简单的功能,而一个高聚合的 bundle 只有极少的依赖性,一个很小的依赖树,而且更容易在其他应用程序中重用。需要注意该最佳实践为面向类设计对象镜像最佳实践,这是因为同样的理由。一个 OSGi bundle 应该是封装良好、高度聚合和松耦合的 — 就像一个格式良好的类 — 防止其依赖特定版本的其他 bundle 或类,并增加重用机会。

示例

一个设计不佳的示例是一个登录实现,使用户可以登录文件系统、通过 JPA 登录数据库,或登录一个消息队列(图 9)。为了使用文件系统 logger,一个应用程序需要加载消息 API、JAP API,等等。因此,应用程序含有 logger 实现类,但并不使用。一个比较好的解决方案是登录接口和各个实现相互分离(图 10)。通过这种方式,应用程序可以选择包含一个它使用的登录 bundle ,而不包括额外实现类和任何进一步实现,这些都出现在登录 bundle 中。

图 9. 一个不良设计系统,其中一个 bundle 提供一个 API 的多个实现
图 9.  一个不良设计系统,其中一个 bundle 提供一个 API 的多个实现
图 9. 一个不良设计系统,其中一个 bundle 提供一个 API 的多个实现
图 10. 一个设计良好的系统,其中一个 API 的每个实现都由一个独立的 bundle 提供
图 10. 一个设计良好的系统,其中一个 API 的每个实现都由一个独立的 bundle 提供
图 10. 一个设计良好的系统,其中一个 API 的每个实现都由一个独立的 bundle 提供

8. 避免拆分包和 Require-Bundle

开发 OSGi bundle 时,一个最佳实践是将一个包的所有类放在一个 bundle 中。将一个包分成多个 bundle 会导致使用 Require-Bundle,从而损害使用这些 bundle 的系统的可扩展和维护程度。

原因如下

模块化系统由松耦合、高聚合模块组成,每个模块执行一个相关的逻辑功能,它们之间的界限有明确的定义。该设计目标将生成易于维护和扩展的系统。OSGi bundle 是包上的模块化。

OSGi 通过每个 bundle 声明其提供松耦合的模块,这在 OSGi 术语中称为 bundle 。这是满足运行时 bundle 提供的包的。一个 bundle 不能直接依存于一个提供相关性的 bundle;至于哪个 bundle 提供相关性由 OSGi 运行时决定,根据部署到框架的 bundle 而定。结果是提供一个特殊包的 bundle 能够被提供相同包的不同 bundle 所取代,而不改变依赖那个包的 bundle。

当在同版本中一个包被两个 bundle 导出时,将出现一个拆分,每个 bundle 提供的类集合都不相同。类可能被复制,或者更常见的是,一部分包在一个 bundle 中,而另一部分在另一个 bundle 中。

图 11. 拆分包的用户需要使用 Require-Bundle 头部
图 11.  拆分包的用户需要使用 Require-Bundle 头部
图 11. 拆分包的用户需要使用 Require-Bundle 头部
图 12. 从一个 bundle 中导出的包保持高 bundle 聚合
图 12. 从一个 bundle 中导出的包保持高 bundle 聚合
图 12. 从一个 bundle 中导出的包保持高 bundle 聚合

在运行时,OSGi 将用另一个 bundle 的导出满足 Import-Package 头部指定的 bundle 的依赖性。即使这不只一个 bundle 导出包,也只有一个用于满足这种依赖性。Bundles 是 “连接” 在一起的。仅存于其他导出相同包的 bundle 中的类(导入 bundle 的包没有被连接在一起)对于导入 bundle 的类加载器是不可见的。如果导入 bundle 在运行时试图使用那些类其中之一,将抛出一个 ClassNotFoundException。因为在 OSGi 中的导入/导出 bundle 在每个包基础上只提供一种方法将一个 bundle 连接到另一个 bundle,另一个机制则需要将一个 bundle 连接到多个 bundle, 在这种拆分包的情况下工作,这可以通过使用 Require-Bundle 头部和指定输出所需包的 bundle 符号名来完成。

然而,这加强了 bundle 之间的耦合:

  • 客户端现在依赖两个(或更多)bundle 提供包。
  • 所有通过提供 bundle 导出的包现在对客户端是可见的,并且当客户端 bundle 开发一段时间以后,它将开始依赖其他包。
  • 提供包的 bundle 不再被另一个包替换,只是使用了一个不同的符号名,对所有需要他的客户端 bundle 没有更改。

将包分离成多个 bundle 从而强迫使用 Require-Bundle 头部使应用程序更紧密的耦合,维护和扩展的费用也因此更为昂贵。如果您有一个包可以分成两个 bundle,而您有不需要这两部分有高聚合性,那么您应该使用两个不同的包。

9. 在 Application-Content 头部列出包含的内容

(专用于 WebSphere Application Server)

在 Enterprise Bundle Archive (EBA) 文件中包含的 Bundle 应该被列在 EBA 的 META-INF/APPLICATION.MF 的 Application-Content 头部。相关 bundle 最好存储在 bundle 存储库中,它们可用于所有的应用程序。

原因如下

在 EBA 中的 bundle 能影响应用程序的配置方法,但是只要配置正在运行,那么 bundle 对其他的应用程序是不可见的。如果 EBA 含有一个或多个依赖项 ,而在 Application-Content 中没有列出,那么这些最终可能加载到一个或多个的服务器共享 bundle 空间,作为配置的结果。这会引起潜在的问题,因为其他应用程序最终被连接到这些依赖,即使它们不依靠这些依赖来配置。在运行时这将导致意外的改变,而且很难检测出。

除了包含的内容应该被列在 Application-Content 头部这个事实外,您也应该充分意识到包含内容的功能主要为了方便您的开发而提供的 — 不推荐用于生产环境中,这个由应用程序内容组成的 bundle 可以包含在 EAB 中,也可以包含在一个 bundle 存储库中。生产环境鼓励 — 并经常强调 — 应用程序从一个 bundle 库中获取所有 bundle。这使得管理适用于生产 bundle 存储库,作为应用程序代码的唯一来源。

对于 EBA 将自动生成 APPLICATION.MF,假如没有的话。Application-Content 将被解释为在 EAB 根目录下包含的每个 bundle。每个 bundle 的版本范围将被锁定到其精确版本,在这种情况下,没有 bundle 能被其他应用程序共享、或部署之后升级。不能访问集成开发环境的 IBM Rational® Application Developer(它提供更为直接的集成)的开发人员可能不会发现,它在没有 APPLICATION.MF 的情况下包装应用程序和导入一个含有升级内容的新应用程序资产是很方便的,而不用将每个新 bundle 安装到内部 bundle 存储库。当然,APPLICATION.MF 应该在开发周期结束之前创建。通常情况下,当您从开发通过测试移到生产环境中时,包含的内容不是很常见。在测试环境中,创建一个包含所有依赖项的 EBA 是很方便的,因此它可被安装而不用考虑目标目标服务器 bundle 存储库的状态。而对于生产环境中依据上述这些理由进行操作是不明智的。

10. 使用 WAB 而不是 WAR

开发企业级 OSGi 应用程序时,创建一个 Web 应用程序 bundle (WAB),不依赖 WAR 进行 WAB 转换。

原因如下

WebSphere Application Server 的 OSGi 应用程序功能自动将一个 WAR 文件转换成一个 OSGi bundle,就是所谓的 Web 应用程序 bundle。在刚开始学习 OSGi 时,这个功能是很有用的,但是自动转换阻碍了某个功能的使用。因而,在开发过程中,WAR 文件应该被转换成一个 bundle 部署到生产系统中。

使用 OSGi bundle 时,应用程序可能显示一系列支持的 bundle。这样一来,不需要重新部署整个应用程序单独的 bundle 也可以更新。该功能取决于模块所拥有的一个已知特性,并不是自动转换,其中特性是在部署时生成的。

当一个 WAR 被转换成一个 WAB 时,包导入生成不可控的版本。结果,包可能捡起所需版本的二进制不兼容版本。如果一个包的版本 2 和版本 3 都可用,不可能阻止 WAR 使用版本 3。如果WAR 需要版本 2,而对版本 3 是不兼容的,这将导致运行时错误。

11. 必要时只使用 Use-Bundle

(专用于 WebSphere Application Server)

创建一个 WebSphere Application Server OSGi 应用程序时,一个最佳实践是仅在共享场景 bundle 的一个特定子集中指定 Use-Bundle 应用程序清单头部。

原因如下

WebSphere Application Server Use-Bundle 应用程序清单头部提供一种方法,可以引导应用程序部署进程在导出相同包的其他 bundle 上执行一个 bundle。

在运行时,OSGi 应用程序彼此隔离,但是他们的依赖项是共享的。在 OSGi 应用程序的 Application-Content 头部指定的 bundle 运行在它们自己独立的 OSGi 框架中。依赖项运行在服务器的共享 bundle 空间。

在 OSGi 应用程序部署过程中依赖项被确定。在部署过程中,应用程序 bundle 导入的包匹配应用程序或配置的 bundle 存储库中的 bundle 导出的包。如果 bundle 存储库中需要一个 bundle,将它提供给服务器。由于包的版本约束,两个应用程序被部署和配置来运行是可能的,其中不同 bundle 在共享 bundle 空间中提供相同的包。如果两个应用程序需要使用类实例(来自所涉及的包)互相交互,这就变成了一个问题。如果是的话,那么 Use-Bundle 应用程序清单头部应该用于确保两个应用程序是连接到同一个 bundle 的。

指定 Use-Bundle 来实现应用程序之间的依赖项共享不是必须的。只需要在两个应用程序需要共享一个类的同一版本时,确保它们被连接到同一个提供的 bundle。

示例

例如,一个含有 Bundle X 的应用程序被部署,从 1.0 到 2.0(不包括 2.0)版本中导入一个 包 org.hal.a(图 13)。根据 OSGi 语义版本控制,这意味着 X 可以连接到任何导出 org.hal.a 的 bundle,而不会中断 API 更改,从用户角度使用 API。此时,只有一个 bundle API.A 导出 org.hal.a,在 1.0 版本中也是这样的。因此 Bundle X 被连接到 API.A。Bundle X 因此可以看得见 org.hal.a 版本 1.0 的实现。Implementation.A 是这类实现之一。

图 13. OSGi 将 Bundle X 连接到 API.A 来在可接收 Bundle X 的版本上提供 org.hal.a
图 13. OSGi 将 Bundle X 连接到 API.A 来在可接收 Bundle X 的版本上提供 org.hal.a
图 13. OSGi 将 Bundle X 连接到 API.A 来在可接收 Bundle X 的版本上提供 org.hal.a

然后,部署一个含有 Bundle Y 的应用程序(图 14)。Bundle Y 从 1.5 到 2.0 版本(但不包括 2.0)导入 org.hal.a 包。API.B 也被部署,在版本 1.5 中导出包 org.hal.a。Bundle Y 不能连接到 API.A ,因为它导入一个新的 org.hal.a 版本,但它 可以 被连接到 API.B。Implementation.B bundle 提供了一个 org.hal.a 1.5 版本的实现,从 1.5 到 1.6 版本(但不包括 1.6)导入。OSGi Alliance Semantic Versioning Technical Whitepaper 对这一原因进行详细的讨论。

图 14. OSGi 将 Bundle Y 连接到 API.B 来在可接收 Bundle Y 的版本上提供 org.hal.a
图14. OSGi 将 Bundle Y 连接到 API.B 来在可接收 Bundle Y 的版本上提供 org.hal.a
图14. OSGi 将 Bundle Y 连接到 API.B 来在可接收 Bundle Y 的版本上提供 org.hal.a

然后,部署第 3 个应用程序,其中含有 Bundle Z。Bundle Z 从与 Bundle X 相同的版本导入 org.hal.a 包:从 版本 1.0 到版本 2.0 但不包括 2.0(图 15)。Bundle Z 的编写者想要它使用 Implementation.A,但是由于 API.B 是在版本 1.5 上导出 org.hal.a 的(这处在 Bundle Z 的导入范围中),Bundle Z 能被连接到 API.B,随后只能看到 Implementation.B。

图 15. Bundle Z 被连接到 API.B,因此不能看到 Implementation.A
图 15.  Bundle Z 被连接到 API.B,因此不能看到 Implementation.A
图 15. Bundle Z 被连接到 API.B,因此不能看到 Implementation.A

为了确保 Bundle Z 连接到 API.A,Bundle Z 应该指定一个 Use-Bundle 应用程序清单头部:API.A,然后,Bundle Z 将获得对 Implementation.A 的可见性,而不是 Implementation.B。这就是作者想要的(图16)。

图 16. Bundle Z 被连接到 API.A,现在可以看到 Implementation.A
图 16. Bundle Z 被连接到 API.A,现在可以看到 Implementation.A
图 16. Bundle Z 被连接到 API.A,现在可以看到 Implementation.A

12. 使用持久 bundle 来共享您的持久性单元

在应用程序中使用 JPA 时,将 bundle 包装成一个 OSGi 持久性 bundle。如果您的客户不直接使用 EntityManagerFactory,在 OSGi 服务存储库中公开一个数据访问服务,而不是从持久性 bundle 导出实体类。

原因如下

WebSphere Application Server OSGi 应用程序特性支持符合OSGi JPA 服务规范的 OSGi 持久性 bundle。通过在一个持久性 bundle 中包装 JPA 管理的类和 persistence.xml,用易共享的 JPA 资源(在 WebSphere Application Server OSGi 运行时使用的)和不可管理的 JPA Service 实现创建一个可重用模块是有可能的。持久 bundle 也可被大量基于持久性客户端的 OSGi 共享,但不包括持久性客户端代码,这两个都是高度聚合和易于重用的。记得指定 Meta-Persistence bundle 清单头部,持久性描述符可以在包内给出任意路径。

如果一个持久性单元被包装在一个遗留 WAR 中,它应该被打开作为一个单独的 OSGi bundle。在 WAR 中的持久性单元由 Java EE 容器管理,并且在 OSGi 框架内不能共享。同样的,不能被 OSGi 应用程序中的其他 OSGi bundle 所用。从 WAR 中删除持久性单元不是可能的,记住它是不能通过 Blueprint 容器或 OSGi 服务存储库访问的。

从其他应用程序代码中分离持久性 bundle 的另一个原因由 前面的一个最佳实践 来解释。如果几个应用程序需要在一个数据库中访问数据,重用同一个实体映射是有必要的。这有助于防止数据库中的数据损坏,因为两个应用程序共享同一数据映射,而只有一个 bundle 需要针对未来的模式更改而更新。如果持久性单元不能包装在一个较大的 bundle 中,那么实体将不再易于多个应用程序共享,需要代码副本和维护成本。

13. 充分利用提供的组件模型

(专用于 WebSphere Application Server)

在 OSGi、WebSphere Application Server OSGi Applications 功能和 SCA 之间提供一系列不断增长的粗粒度组件模型:

  • 使用 Blueprint 在 OSGi bundle 中定义细粒度组件。
  • 将 OSGi 应用程序定义为 bundle 组合,这些 bundle 提供一个特定应用程序功能。
  • 使用 SCA 公开服务,从一个 OSGi 应用程序到另一个,并向常规 Java EE 应用程序提供服务。

原因如下

使用 Blueprint 来管理细粒度组件以及与其他 OSGi Bundle 的交互。保持您 bundle 的聚合性(通常很小)并使 您的依赖项保持松耦合性。高聚合和松耦合的原则继续适用,因为 bundle 被分组到业务 bundle 应用程序。

OSGi 应用程序可以认为同传统 Java EE 企业级应用程序大致相同。在组装应用程序时,记住 WebSphere Application Serverkeep 将 Application-Content 列出的 bundle 部署到自己的独立的框架。所有应用程序的依赖 bundle 被部署到一个共享 bundle 空间。因此鼓励将通用代码库和服务分解到共享的依赖 bundle 。这节省了内存,增加了可重用性,并有助于系统的管理。在一个给定 OSGi 应用程序 Application-Content 中列出的 bundle 仅由该应用程序中独有的持久性、业务和显示逻辑组成。

有两个方法可以将工作深入到 OSGi 应用程序,第一个是通过 HTTP 深入到 WAR,第二个是调用一个 Application-ExportService 头部列出的服务;这么做需要使用 SCA。

SCA 是最粗粒度的可用组件模型,它提供一个分布式编程模型并强迫使用值传递语义。使用 SCA 从一个 OSGi 应用程序向另一个公开服务,并在常规 Java EE 程序之间提供服务。Rational Application Developer 8 提供工具支持构建和集成 OSGi 应用程序作为 SAC 组件。

14. 让容器担起重任

(专用于 WebSphere Application Server)

使用容器服务来构建应用程序并提企业品质服务而不是编写代码来管理服务。

原因如下

应用程序程序员经常希望以一种健壮可信的方式解决复杂的问题,并希望信任业务服务,例如事务和托管的资源,来为他们提供他们想要的服务质量。然而,向容器提供这些服务的完全控制权通常有点勉强,这无疑有损灵活性并失去控制。结果是许多应用程序选择管理自己的资源和周期。

控制反转,特别是依赖注入,是极为强大的简化应用程序开发工具,但它们依赖一个事实:容器能负责周期和资源管理。向 Blueprint bean 中的所有方法应用声明事务需要一行 XML,然而在每个方法中需要多行代码来在应用程序中完成同样的结果。类似地,对于资源管理,使用一个 Blueprint 资源引用可以将资源直接注入应用程序,也可以使其可以使用安全证书进行配置,以至于资源没有必要在应用程序中提供。如果一个开发人员选择定位自己的资源,容器就没有机会验证应用程序,因此证书被存储在应用程序中,而且,如果证书过期应用程序必须被更改。

示例

bundle 提供一个基于 JPA 的数据访问服务,但是不使用任何容器服务。它必须手工管理事务、EntityManager 周期和服务存储库。这将需要大约 100 行代码。需要多个 try/finally 块整理资源。客户端必须使用 OSGi API 来查看服务,这需要更多的周期管理和 try/finally 块。由于不使用 Blueprint 注册和使用服务,应用程序也必须手工处理服务不可用的错误案例。这也不是自动基于服务配置的。

通过使用声明性事务、Blueprint 和 管理 JPA,应用程序在大小上有所减少,减少了数百行复杂的周期和错误管理代码。这也减少了应用程序潜在缺陷的数量。这个应用程序,以及将来所有使用数据访问服务的应用程序,都可以利用基于服务的依赖配置优势。

结束语

本文描述了构建 OSGi bundle 和 WebSphere Application Server OSGi 应用程序来实现 bundle 之间的高聚合和松耦合的最佳实践。通过采用这些最佳实践,您可以使用 Feature Pack for OSGi 应用程序和 JPA 2.0 来创建更加可维护和可扩展的应用程序,这些应用程序所用的是运行在 WebSphere Application Server V7 中的技术。


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=WebSphere, Web development
ArticleID=525067
ArticleTitle=用 OSGi 应用程序开发和工作的最佳实践
publish-date=09212010