Karaf-秘籍-全-

Karaf 秘籍(全)

原文:zh.annas-archive.org/md5/99d5ffa588c36025c2f9244e55e5c5c2

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

欢迎使用 Apache Karaf 烹饪书. 这本烹饪书是为了提供您在 Apache Karaf 实战中积累的最佳实践和经验教训而创建的。

本书中的食谱不应按顺序阅读。读者应根据需要挑选食谱。我们已经尽最大努力确保可以在适用的情况下参考基础知识。然而,我们确实期望我们的读者对 OSGi、Karaf 以及每个食谱关注的包有一定的背景知识。需要更多关于 Karaf 和 OSGi 背景信息的读者可能需要查找以下书籍:

  • 学习 Apache KarafJohan Edstrom, Jamie Goodyear, 和 Heath KeslerPackt Publishing

  • Instant OSGi StarterJohan Edstrom 和 Jamie GoodyearPackt Publishing

本书涵盖的内容

第一章, 为系统构建者准备的 Apache Karaf,涵盖了如何使 Apache Karaf 更适用于生产的食谱。探讨了改进日志记录、自定义命令、品牌化、管理和高可用性等主题。

第二章, 使用 Apache Camel 构建智能路由器,介绍了 Apache Camel 命令,然后讨论了如何使用 Plain Old Java Objects (POJO)、Blueprint、带有配置管理支持的 Blueprint 以及最终使用托管服务工厂来构建 Camel 路由器的食谱。

第三章, 使用 Apache ActiveMQ 部署消息代理,探讨了如何在嵌入式方式下使用 Apache ActiveMQ,并介绍了用于监控和与嵌入式 ActiveMQ 代理交互的不同命令。

第四章, 使用 Pax Web 托管 Web 服务器,解释了如何配置和使用 Apache Karaf 与 Pax Web。它从基本的 Http 服务开始,到完整的 Web 应用支持。

第五章, 使用 Apache CXF 托管 Web 服务,展示了如何在 Karaf 中设置 Apache CXF 端点,以支持 RESTful 和基于 WSDL 的 Web 服务。

第六章, 使用 Apache Karaf Cellar 分发集群容器,解释了 Cellar 的设置并介绍了其命令的使用。

第七章, 使用 Apache Aries 和 OpenJPA 提供持久层,探讨了如何将 Java 持久性和事务支持添加到您的 OSGi 环境中。

第八章,使用 Apache Cassandra 提供大数据集成层,展示了如何将 Cassandra 客户端包安装到 Karaf 中,设置建模数据,并构建项目以利用由 Cassandra 支持的持久化层。

第九章,使用 Apache Hadoop 提供大数据集成层,向您展示了如何将所有 Hadoop 依赖项集成到功能文件中,在管理资源的同时正确部署到 Karaf 中,并使用 HDFS 存储和检索数据。

第十章,使用 Pax Exam 测试 Apache Karaf,解释了如何使用 OSGi 进行集成测试,并展示了如何在集成测试中使用 Apache Karaf。

您需要为此书准备的内容

作者们非常注意将探索 Apache Karaf 所需的软件数量保持在最低。在尝试本书中包含的食谱之前,您需要获取以下内容:

  • Apache Karaf 3.0

  • Oracle Java SDK 1.7

  • Apache Maven 3.0

  • Git 客户端

  • 任何文本编辑器

本书面向对象

Apache Karaf 食谱集是为寻求在开发和部署应用程序时应用最佳实践的开发商和系统管理员而收集的食谱集。读者将发现许多与面向服务的架构和大数据管理相关的食谱,以及需要一些 OSGi 经验才能充分利用其潜力的几个库。

约定

在本书中,您将找到多种文本样式,用于区分不同类型的信息。以下是一些这些样式的示例及其含义的解释。

文本中的代码词汇、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 昵称如下所示:“我们将jmxRole附加到管理员组。”

代码块设置如下:

<resource>
 <directory>
  ${project.basedir}/src/main/resources
 </directory>
 <filtering>true</filtering>
 <includes>
  <include>**/*</include>
 </includes>
</resource>

当我们希望您注意代码块中的特定部分时,相关的行或项目将以粗体显示:

log4j.appender.out.append=true
log4j.appender.out.maxFileSize=10MB
log4j.appender.out.maxBackupIndex=100

任何命令行输入或输出都应如下编写:

karaf@root()> feature:repo-add mvn:com.packt/features-file/1.0.0-SNAPSHOT/xml/features

新术语重要词汇将以粗体显示。屏幕上显示的词汇,例如在菜单或对话框中,在文本中如下显示:“QueueSize列显示当前队列中等待消费的消息数量。”

注意

警告或重要提示将以如下框中显示。

小贴士

小技巧和窍门如下所示。

读者反馈

我们欢迎读者的反馈。请告诉我们您对本书的看法——您喜欢什么或可能不喜欢什么。读者反馈对我们开发您真正能从中获得最大价值的标题非常重要。

要向我们发送一般反馈,只需发送电子邮件到<feedback@packtpub.com>,并在邮件主题中提及书名。

如果您在某个主题上具有专业知识,并且您有兴趣撰写或为书籍做出贡献,请参阅我们的作者指南www.packtpub.com/authors

客户支持

现在您已经是 Packt 书籍的骄傲拥有者,我们有一些事情可以帮助您从您的购买中获得最大收益。

下载示例代码

您可以从www.packtpub.com的账户下载您购买的所有 Packt 书籍的示例代码文件。如果您在其他地方购买了这本书,您可以访问www.packtpub.com/support并注册,以便将文件直接通过电子邮件发送给您。

错误清单

尽管我们已经尽一切努力确保内容的准确性,但错误仍然可能发生。如果您在我们的某本书中发现错误——可能是文本或代码中的错误——如果您能向我们报告这一点,我们将不胜感激。通过这样做,您可以节省其他读者的挫败感,并帮助我们改进本书的后续版本。如果您发现任何错误清单,请通过访问www.packtpub.com/submit-errata,选择您的书籍,点击错误提交表单链接,并输入您的错误详细信息来报告它们。一旦您的错误得到验证,您的提交将被接受,错误将被上传到我们的网站,或添加到该标题的现有错误清单中,在“错误清单”部分下。您可以通过从www.packtpub.com/support选择您的标题来查看任何现有错误。

盗版

在互联网上盗版版权材料是一个跨所有媒体持续存在的问题。在 Packt,我们非常重视我们版权和许可证的保护。如果您在互联网上发现我们作品的任何非法副本,无论形式如何,请立即提供位置地址或网站名称,以便我们可以追究补救措施。

请通过<copyright@packtpub.com>与我们联系,并提供涉嫌盗版材料的链接。

我们感谢您在保护我们作者和我们为您提供有价值内容的能力方面的帮助。

问题

如果您在本书的任何方面遇到问题,可以通过<questions@packtpub.com>与我们联系,我们将尽力解决。

第一章。Apache Karaf 系统构建者指南

在本章中,我们将涵盖以下主题:

  • 在 Apache Karaf 中配置生产就绪的日志记录

  • 使用 Maven 存档创建我们自己的自定义 Karaf 命令

  • 标记 Apache Karaf 控制台

  • 将应用程序作为功能部署

  • 使用 JMX 监控和管理 Apache Karaf

  • 重新配置对 Apache Karaf 的 SSH 访问

  • 将 Apache Karaf 作为服务安装

  • 设置 Apache Karaf 以实现高可用性

简介

有经验的 Apache Karaf 用户会告诉你,开箱即用,Karaf 为你提供了部署应用程序所需的功能和工具。然而,为了构建一个生产就绪的环境,你可能需要调整一些设置。

本章中的食谱是为系统构建者准备的,这些人需要使他们的 Apache Karaf 实例达到生产就绪状态,并使其中的应用程序易于管理。

小贴士

Apache Karaf 和 OSGi 新手?

对 Apache Karaf 及其底层技术有深入了解兴趣的读者应参考 Packt Publishing 出版的《Instant OSGi Starter》,作者为 Jamie Goodyear 和 Johan Edstrom,以及《Learning Apache Karaf》,作者为 Jamie Goodyear、Johan Edstrom 和 Heath Kesler。

在 Apache Karaf 中配置生产就绪的日志记录

Apache Karaf 管理员首先要做的第一项任务之一是更改默认的日志配置,以更符合生产就绪的设置。

为了改进默认的日志配置,我们将执行以下任务:

  • 更新日志文件位置,使其位于数据文件夹之外。这有助于管理员在删除运行时数据时避免意外删除日志文件。

  • 增加日志文件大小。默认的 1 MB 大小对于大多数生产部署来说太小了。通常,我们会将其设置为 50 或 100 MB,具体取决于可用的磁盘空间。

  • 增加我们保留的日志文件数量。没有保留日志文件的正确数量。然而,当磁盘空间便宜且可用时,保留大量文件是一种首选的配置。

如何做到这一点…

配置 Karaf 的日志机制需要你编辑etc/org.ops4j.pax.logging.cfg文件。使用你喜欢的编辑器打开文件,并更改以下突出显示的代码条目:

# Root logger
log4j.rootLogger=INFO, out, osgi:*
log4j.throwableRenderer=org.apache.log4j.OsgiThrowableRenderer

# File appender
log4j.appender.out=org.apache.log4j.RollingFileAppender
log4j.appender.out.layout=org.apache.log4j.PatternLayout
log4j.appender.out.layout.ConversionPattern=%d{ISO8601} | %-5.5p | %-16.16t | %-32.32c{1} | %X{bundle.id} - %X{bundle.name} - %X{bundle.version} | %m%n
log4j.appender.out.file=${karaf.base}/log/karaf.log
log4j.appender.out.append=true
log4j.appender.out.maxFileSize=10MB
log4j.appender.out.maxBackupIndex=100

在前面的配置中,我们指示 Karaf 将日志写入基础安装目录中的日志文件夹,将日志文件大小增加到 10 MB,并将保留的日志文件数量增加到 100。

完成文件编辑后,保存更改。它们将很快生效。

小贴士

我们可以通过更改log4j.rootLogger条目从INFODEBUGWARNERRORTRACE来改变日志记录的详细程度。

它是如何工作的…

Karaf 的日志系统基于OPS4J Pax Logging,其中log4j库作为其后端。配置文件etc/org.ops4j.pax.logging.cfg用于定义附加器、日志级别等。让我们看看以下默认附加器配置以及我们将如何调整它以使其更符合生产就绪:

# Root logger
log4j.rootLogger=INFO, out, osgi:*
log4j.throwableRenderer=org.apache.log4j.OsgiThrowableRenderer

# File appender
#log4j.appender.out=org.apache.log4j.RollingFileAppender
#log4j.appender.out.layout=org.apache.log4j.PatternLayout
#log4j.appender.out.layout.ConversionPattern=%d{ISO8601} | %-5.5p | %-16.16t | %-32.32c{1} | %X{bundle.id} - %X{bundle.name} - %X{bundle.version} | %m%n
#log4j.appender.out.file=${karaf.data}/log/karaf.log
#log4j.appender.out.append=true
#log4j.appender.out.maxFileSize=1MB
#log4j.appender.out.maxBackupIndex=10

在前面的代码中,File appender 配置设置了默认的 Karaf 日志行为。初始配置设置了 RollingFileAppender 并构建了一个日志条目模式。其余选项指定了日志文件的位置、大小和保留的日志文件数量。

Karaf 监控位于 KARAF_HOME/etc 文件夹中的配置文件。当读取配置文件的更新时,日志服务会更新为新值。允许此行为的机制由文件安装(可在 felix.apache.org/site/apache-felix-file-install.html 获取)和 OSGi 配置管理服务提供。请查看以下图示:

如何工作…

如前图所示,当 KARAF_HOME/etc 目录中的文件被创建、删除或修改时,文件扫描器会检测到该事件。给定一个配置文件更改(Java 属性文件格式的更改),配置处理器将处理条目并更新 OSGi 配置管理服务。

小贴士

下载示例代码

您可以从您在 www.packtpub.com 的账户中下载您购买的所有 Packt 书籍的示例代码文件。如果您在其他地方购买了这本书,您可以访问 www.packtpub.com/support 并注册,以便将文件直接通过电子邮件发送给您。

还有更多…

要进一步提高日志记录,您可以向 log4j 库提供一个外部日志位置,以牺牲增加的网络流量为代价,将日志记录的 I/O 需求与基本系统分离。以下图示显示了此架构:

还有更多…

要实现此日志架构,您需要在 Karaf 运行的服务器上挂载外部卷。

相关链接

  • 使用 Maven 架构创建我们自己的自定义 Karaf 命令 的配方。

使用 Maven 架构创建我们自己的自定义 Karaf 命令

Karaf 控制台提供了许多有用的命令,用于与 OSGi 运行时交互和管理已部署的应用程序。作为系统构建者,您可能希望开发直接集成到 Karaf 中的自定义命令,以便您可以自动化任务或直接与应用程序交互。

自定义 Karaf 命令将作为控制台的一个完全集成的组件出现在您的容器中,如下截图所示:

使用 Maven 架构创建我们自己的自定义 Karaf 命令

前面的截图说明了我们的示例食谱命令接受一个选项标志和一个参数。让我们深入了解构建您自己的命令。

准备工作

此食谱的成分包括 Apache Karaf 分发套件、对 JDK、Maven 和源代码编辑器的访问。此食谱的示例代码可在 github.com/jgoodyear/ApacheKarafCookbook/tree/master/chapter1/chapter1-recipe2 找到。

如何操作…

  1. 第一步是生成模板命令项目。为了鼓励构建自定义命令,社区提供了以下 Maven 架构调用以生成 Karaf 命令项目:

    mvn archetype:generate \
     -DarchetypeGroupId=org.apache.karaf.archetypes \
     -DarchetypeArtifactId=karaf-command-archetype \
     -DarchetypeVersion=3.0.0 \
     -DgroupId=com.packt.chapter1 \
     -DartifactId=command \
    -Dversion=1.0.0-SNAPSHOT \
    -Dpackage=com.packt
    
    

    在先前的架构调用中,我们提供了 Maven 项目组和工件名称。该过程将要求您提供命令名称。Maven 然后为您生成命令的项目模板。

  2. 下一步是实施您自定义的代码。自定义命令模板项目将为您提供 Maven POM 文件、Blueprint 连接(在 src/main/resources/OSGI-INF/blueprint 目录中),以及自定义命令存根实现(在 src/main/java/ 目录中)。根据需要编辑这些文件以添加您的自定义操作。

  3. 最后一步是在 Karaf 中构建和部署自定义命令。我们通过 Maven 调用 mvn install 来构建我们的命令。在 Karaf 中部署它只需要发出一个格式良好的安装命令;为此,在 Karaf 控制台中调用 install –s mvn:groupId/artifactId。考虑以下调用:

    karaf@root()> install –s mvn:com.packt.chapter1/command
     Bundle ID: 88
    karaf@root()>
    
    

    先前的调用中,groupId 值为 com.packt.chapter1artifactId 值为 command

它是如何工作的…

Maven 架构生成您的自定义命令的 POM 构建文件、Java 代码和 Blueprint 文件。让我们看看这些关键组件。

生成的 POM 文件包含 Karaf 命令所需的全部基本依赖项,并设置了一个基本的 Maven Bundle 插件配置。编辑此文件以引入您的命令所需的额外库。确保相应地更新您的包的构建参数。当此项目构建时,将生成一个可以直接安装到 Karaf 中的包。

我们的自定义命令逻辑位于生成的 Java 源代码文件中,该文件将根据您提供的命令名称命名。生成的命令扩展了 Karaf 的 OSGICommandSupport 类,这为我们提供了访问底层命令会话和 OSGi 环境的权限。Command 注解装饰了我们的代码。这为运行时提供了范围、名称和描述。Karaf 提供了 ArgumentOption 注解来简化添加命令行参数和选项处理。

Blueprint 容器将我们的命令实现连接到 Karaf 控台中的可用命令。

小贴士

有关扩展 Karaf 控台的信息,请参阅 karaf.apache.org/manual/latest/developers-guide/extending.html

还有更多…

感谢 Apache Karaf 的 SSHD 服务和远程客户端,您可以使用自定义命令来提供对应用程序的外部命令和控制。只需将您的命令及其参数传递给远程客户端,并监控返回的结果。

参见

  • 品牌化 Apache Karaf 控制台配方

品牌化 Apache Karaf 控制台

Apache Karaf 被用作生产应用程序平台的运行环境。在这样的部署中,Karaf 通常会有自定义的品牌标识。

Karaf 社区已经将运行时的重命名简化为一项简单任务。让我们为这本书创建自己的版本。

准备工作

这个配方的原料包括 Apache Karaf 发行套件、对 JDK、Maven 和源代码编辑器的访问。这个配方的示例代码可在github.com/jgoodyear/ApacheKarafCookbook/tree/master/chapter1/chapter1-recipe3找到。

如何操作…

  1. 第一步是生成基于 Maven 的项目结构。对于这个配方,我们只需要创建 Maven POM 文件的基础版本,将其打包设置为bundle,并包含一个build部分。

  2. 下一步是在我们的 POM 文件的构建部分添加一个资源指令。在我们的 POM 文件中,我们添加了一个资源指令到我们的构建部分,如下面的代码所示:

    <resource>
      <directory>
        ${project.basedir}/src/main/resources
      </directory>
      <filtering>true</filtering>
      <includes>
        <include>**/*</include>
      </includes>
    </resource>
    

    我们在我们的构建部分添加一个资源指令,以指示 Maven 处理我们的resources文件夹的内容,过滤任何通配符,并将结果包含在生成的包中。

  3. 接下来,我们按照以下代码配置 Maven Bundle 插件:

    <configuration>
      <instructions>
        <Bundle-SymbolicName>
          ${project.artifactId}
        </Bundle-SymbolicName>
        <Import-Package>*</Import-Package>
        <Private-Package>!*</Private-Package>
        <Export-Package>
          org.apache.karaf.branding
        </Export-Package>
        <Spring-Context>
          *;publish-context:=false
        </Spring-Context>
      </instructions>
    </configuration>
    

    我们配置了 Maven Bundle 插件,将Bundle-SymbolicName导出为artifactId,并将Export-Package选项设置为org.apache.karaf.branding。将符号名称作为项目的artifactId变量是 Karaf 插件开发者中的一个常见约定。我们导出 Karaf 品牌包,以便 Karaf 运行时能够识别包含自定义品牌的包。

  4. 下一步是创建我们的自定义品牌资源文件。回到我们的项目,我们将在src/main/resource/org/apache/karaf/branding目录中创建一个branding.properties文件。这个.properties文件将包含 ASCII 和 Jansi 文本字符,组织成你的自定义外观。使用 Maven 资源过滤,你可以使用${variable}格式的变量替换,如下面的代码所示:

    ##
    welcome = \
    \u001B[33m\u001B[0m\n\
    \u001B[33m      _       ___  ____    ______  \u001B[0m\n\
    \u001B[33m     / \\    |_  ||_  _|  .' ___  | \u001B[0m\n\
    \u001B[33m    / _ \\     | |_/ /   / .'   \\_| \u001B[0m\n\
    \u001B[33m   / ___ \\    |  __'.   | |        \u001B[0m\n\
    \u001B[33m _/ /   \\ \\_ _| |  \\  \\_ \\ '.___.'\\ \u001B[0m\n\
    \u001B[33m|____| |____||____||____| '.____ .' \u001B[0m\n\
    \u001B[33m                                   \u001B[0m\n\
    \u001B[33m       Apache Karaf Cookbook       \u001B[0m\n\
    \u001B[33m Packt Publishing - http://www.packtpub.com\u001B[0m\n\
    \u001B[33m       (version ${project.version})\u001B[0m\n\
    \u001B[33m\u001B[0m\n\
    \u001B[33mHit '\u001B[1m<tab>\u001B[0m' for a list of available commands\u001B[0m\n\
    \u001B[33mand '\u001B[1m[cmd] --help\u001B0m' for help on a specific command.\u001B[0m\n\
    \u001B[33mHit '\u001B[1m<ctrl-d>\u001B[0m' or '\u001B[1mosgi:shutdown\u001B[0m' to shutdown\u001B[0m\n\
    \u001B[33m\u001B[0m\n\
    

    在前面的代码中,我们在branding.properties文件中使用 ASCII 字符和 Jansi 文本标记的组合来在 Karaf 中产生简单的文本效果,如下面的截图所示:

    ![如何操作…

  5. 最后一步是构建和部署我们的自定义品牌。我们通过 Maven 调用mvn install来构建我们的品牌。在构建我们的品牌包之后,我们将一个副本放在 Karaf 的KARAF_HOME/lib文件夹中,然后启动容器。在第一次启动时,你会看到我们的自定义品牌被显示出来。

它是如何工作的…

在第一次启动时,Apache Karaf 将检查其 lib 文件夹中的任何包,并将导出 org.apache.karaf.branding 包。在检测到该资源后,它将访问 branding.properties 文件的内容,并将其作为运行时启动程序的一部分显示。

更多内容...

Apache Karaf 社区维护一个可能也带有品牌标志的 Web 控制台。有关更多详细信息,请参阅 karaf.apache.org/index/subprojects/webconsole.html

将应用程序作为功能部署

管理仓库位置、包、配置和其他工件的总装和部署很快就会成为系统构建者的一个主要头痛问题。为了解决这个问题,Karaf 社区开发了 功能 的概念。以下图描述了功能的概念:

将应用程序作为功能部署

功能描述符是一个基于 XML 的文件,它描述了一组要一起安装到 Karaf 容器中的工件。在这个配方中,我们将学习如何创建一个功能,将其添加到 Karaf 中,然后使用它来安装包。

准备工作

这个配方的原料包括 Apache Karaf 分发套件、JDK 访问权限、Maven 和一个源代码编辑器。这个配方的示例代码可在 github.com/jgoodyear/ApacheKarafCookbook/tree/master/chapter1/chapter1-recipe4 找到。

如何操作...

  1. 第一步是生成一个基于 Maven 的项目。对于这个配方,我们需要创建一个 Maven POM 文件,将其打包设置为 bundle,并包含一个 build 部分。

  2. 下一步是编辑 POM 文件的 build 指令。我们在 POM 文件的 build 部分添加一个 resources 指令,并将其插件列表中的 maven-resources-pluginbuild-helper-maven-plugin 包括在内。考虑以下代码:

    <resources>
        <resource>
            <directory>src/main/resources</directory>
            <filtering>true</filtering>
        </resource>
    </resources>
    

    在前面的代码中,resources 指令表示我们将创建用于处理的特征文件的存储位置。现在,考虑以下代码:

    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-resources-plugin</artifactId>
        <executions>
            <execution>
                <id>filter</id>
                <phase>generate-resources</phase>
                <goals>
                    <goal>resources</goal>
                </goals>
            </execution>
        </executions>
    </plugin>
    

    在前面的代码中,maven-resources-plugin 被配置为处理我们的资源。现在,考虑以下代码:

    <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>build-helper-maven-plugin</artifactId>
        <executions>
            <execution>
                <id>attach-artifacts</id>
                <phase>package</phase>
                <goals>
                    <goal>attach-artifact</goal>
                </goals>
                <configuration>
                    <artifacts>
                        <artifact>
                            <file>${project.build.directory}/classes/${features.file}</file>
                            <type>xml</type>
                            <classifier>features</classifier>
                        </artifact>
                    </artifacts>
                </configuration>
            </execution>
        </executions>
    </plugin>
    

    最后,build-helper-maven-plugin 完成前面代码中描述的 features.xml 文件的构建。

  3. 第三步是创建一个 features.xml 资源。在 src/main/resources 文件夹中,添加一个名为 features.xml 的文件,其中包含您包的详细信息,如下面的代码所示:

    <?xml version="1.0" encoding="UTF-8"?>
    
    <features>
    
      <feature name='moduleA' version='${project.version}'>
        <bundle>
          mvn:com.packt/moduleA/${project.version}
        </bundle>
      </feature>
    
      <feature name='moduleB' version='${project.version}'>
        <bundle>
          mvn:com.packt/moduleB/${project.version}
        </bundle>
      </feature>
    
      <feature name='recipe4-all-modules' version='${project.version}'>
        <feature version='${project.version}'>moduleA</feature>
        <feature version='${project.version}'>moduleB</feature>
      </feature>
    
    </features>
    

    我们为每个功能提供了一个名称,Karaf 将使用该名称作为参考来安装配置中指定的每个元素。功能可以引用其他功能,从而提供对安装的精细控制。在前面的功能文件中,我们可以看到三个命名的功能:moduleAmoduleBrecipe4-all-modulesrecipe4-all-modules 功能包括其他两个功能的全部内容。

    小贴士

    如果您需要包含一个不作为捆绑包提供的 JAR 文件,请尝试使用wrap协议来自动提供带有 OSGi 清单头部的文件。更多信息,请参阅ops4j1.jira.com/wiki/display/paxurl/Wrap+Protocol

  4. 最后一步是构建和部署我们的功能。使用我们的示例配方项目,我们将通过执行mvn install来构建我们的功能。这执行了所有功能文件变量替换,并在您的本地m2仓库中安装处理后的副本。

    要使我们的功能对 Karaf 可用,我们将添加功能文件的 Maven 坐标,如下所示:

    karaf@root()>feature:repo-add mvn:com.packt/features-file/1.0.0-  SNAPSHOT/xml/features
    
    

    现在,我们可以使用 Karaf 的feature命令安装moduleAmoduleB,如下面的命令行片段所示:

    karaf@root()>feature:install recipe4-all-modules
    Apache Karaf starting moduleA bundle
    Apache Karaf starting moduleB bundle
    karaf@root()>
    
    

    使用这种方式在feature:install中可以帮助促进可重复部署并避免 OSGi 环境(如果没有缺少捆绑包依赖项)未捕获的组件安装(如果一切顺利,从容器角度来看)。我们可以通过调用以下命令来验证我们的功能是否已安装:

    karaf@root()>feature:list | grep –i "recipe"
    
    

    我们可以观察我们的功能是否被列出。

它是如何工作的…

当 Karaf 将功能描述符作为捆绑包、热部署或通过系统启动属性处理时,将发生相同的处理和组装功能,如下面的图所示:

如何工作…

功能描述符调用被转换为一个要在 OSGi 容器中安装的工件列表。在最低级别,功能中的单个元素有一个处理程序来获取描述的工件(如捆绑包、JAR 文件或配置文件)。我们的示例功能使用 Maven 坐标来获取捆绑包,并将调用 Maven 处理程序来处理这些资源。如果指定了 HTTP URL,则调用 HTTP 处理程序。指定功能中的每个工件都将被安装,直到整个列表被处理。

还有更多…

本配方中的如何做…部分概述了一种通用方法,用于为您的项目生成功能文件并自动化资源版本的过滤。从 Apache Karaf 的角度来看,它只是处理一个格式良好的功能文件,以便您可以手动编写文件并将其直接部署到 Karaf。

功能文件有额外的属性,可以用来设置捆绑包启动级别,将捆绑包标记为依赖项,并设置配置属性。更多信息,请访问karaf.apache.org/manual/latest/users-guide/provisioning.html

Karaf 功能文件的先进用法之一是构建一个 KAraf aRchiveKAR)。KAR 文件是功能文件的加工形式,将所有必需的工件收集到一个可部署的单一形式中。当您的 Karaf 实例无法访问远程仓库时,此存档非常适合部署,因为所有必需的资源都打包在 KAR 文件中。

参见

  • 我们将在本书的几个章节中使用 Apache Karaf 的功能概念来简化 Apache Camel、ActiveMQ 和 CXF 等其他项目的安装。

使用 JMX 监控和管理 Apache Karaf

默认情况下,Apache Karaf 可以通过 Java 管理扩展 (JMX) 进行管理。然而,系统构建者通常需要调整默认配置,以便将他们的部署集成到网络中。在这个菜谱中,我们将向您展示如何进行这些更改。

准备工作

此菜谱的配料包括 Apache Karaf 分发套件、对 JDK 的访问和源代码编辑器。此菜谱的示例配置可在 github.com/jgoodyear/ApacheKarafCookbook/tree/master/chapter1/chapter1-recipe5 找到。

小贴士

管理员在向他们的 Karaf 实例公开 JMX 访问时应该小心谨慎。建议启用 SSL 和使用强密码。

如何操作…

  1. 第一步是编辑管理配置。Apache Karaf 随带默认的管理配置。为了进行我们的修改,我们更新 etc/org.apache.karaf.management.cfg 文件。考虑以下代码:

    #
    # Port number for RMI registry connection
    #
    rmiRegistryPort = 11099
    
    #
    # Port number for RMI server connection
    #
    rmiServerPort = 44445
    

    默认端口,1099 和 44444,通常适用于一般部署。只有在您的部署中遇到端口冲突时才更改这些端口。现在,考虑以下片段:

    #
    # Role name used for JMX access authorization
    # If not set, this defaults to the ${karaf.admin.role} configured in etc/system.properties
    #
    jmxRole=admin
    

    在配置文件的底部附近,将有一个注释掉的 jmxRole 条目;通过删除井号字符来启用它。

  2. 下一步是更新用户文件。我们现在必须使用以下代码更新 etc/users.properties 文件:

    karaf = karaf,_g_:admingroup
    _g_\:admingroup = group,admin,manager,viewer,jmxRole
    
    

    users.properties 文件用于在 Karaf 中配置用户、组和角色。我们将 jmxRole 添加到管理员组中。此文件的语法遵循 Username = password, groups 格式。

  3. 最后一步是测试我们的配置。在做出之前的配置更改后,我们需要重新启动我们的 Karaf 实例。现在,我们可以测试我们的 JMX 设置。请看以下截图:如何操作…

    在重新启动 Karaf 后,使用您选择的基于 JMX 的管理工具(前面的截图显示了 JConsole)连接到容器。由于图像大小限制,完整的 URL 无法显示。完整的 URL 是 service:jmx:rmi://127.0.0.1:44445/jndi/rmi://127.0.0.1:11099/karaf-root。URL 的语法是 service:jmx:rmi://host:${rmiServerPort}/jndi/rmi://host:${rmiRegistryPort}/${karaf-instance-name}

重新配置 Apache Karaf 的 SSH 访问

通过其本地控制台使用 Apache Karaf 为用户提供了对他们的 OSGi 容器的卓越命令和控制能力。Apache Karaf 的远程控制台扩展了这一体验,并将远程控制台呈现给系统构建者,从而为他们提供了进一步加固系统的机会。在本配方中,我们将更改 Karaf 的默认远程连接参数。

准备工作

本配方的配料包括 Apache Karaf 分发套件、JDK 访问权限和源代码编辑器。本配方的示例配置可在 github.com/jgoodyear/ApacheKarafCookbook/tree/master/chapter1/chapter1-recipe6 找到。

如何操作...

  1. 第一步是编辑 shell 配置。Apache Karaf 随附一个默认的 shell 配置文件。将 etc/org.apache.karaf.shell.cfg 文件中的条目编辑为指向非默认端口是一种安全预防措施。请考虑以下代码:

    #
    # Via sshPort and sshHost you define the address you can login into Karaf.
    #
    sshPort = 8102
    sshHost = 192.168.1.110
    

    在前面的示例配置中,我们定义了 SSH 访问的端口为 8102,并将 sshHost 设置为主机机的 IP 地址(默认值 0.0.0.0 表示 SSHD 服务绑定到所有网络接口)。限制对特定网络接口的访问可以帮助减少未授权的访问。

  2. 下一步是重启 Karaf。在编辑配置后,我们必须重启 Karaf。重启后,您可以使用以下 SSH 客户端命令连接到 Karaf:

    ssh –p 8102 karaf@127.0.0.1
    
    

    连接后,您将被提示输入密码。

还有更多...

修改默认的远程访问配置是一个好的开始。然而,系统构建者还应考虑更改 users.properties 文件中找到的默认 karafuser/password 组合。

您也可能决定生成一个服务器 SSH 密钥文件以简化远程访问。有关此配置的信息可在 karaf.apache.org/manual/latest/users-guide/remote.html 找到。

将 Apache Karaf 作为服务安装

当我们安装 Apache Karaf 时,我们希望它在我们的主机平台上作为系统服务运行(就像 Windows 或 Linux 一样)。在本配方中,我们将设置 Karaf 在系统启动时启动。

准备工作

本配方的配料包括 Apache Karaf 分发套件、JDK 访问权限和源代码编辑器。本配方的示例包装配置可在 github.com/jgoodyear/ApacheKarafCookbook/tree/master/chapter1/chapter1-recipe7 找到。

如何操作...

  1. 第一步是安装服务包装功能。Apache Karaf 利用服务包装功能来处理收集和部署主机操作环境所需资源。我们通过调用以下命令开始其安装:

    karaf@root()>feature:install service-wrapper
    
    

    服务包装功能 URL 默认包含在 Karaf 中;因此,不需要额外的步骤来使其可用。

  2. 下一步是安装包装服务。现在,我们必须指示包装服务为我们配置和安装适当的服务脚本和资源。考虑以下命令:

    karaf@root()>wrapper:install –s AUTO_START –n Karaf3 –D "Apache Karaf Cookbook"
    
    

    前面的wrapper:install命令调用包括三个标志:-s用于启动类型,-n用于服务名称,–D用于服务描述。启动类型可以是两个选项之一:AUTO_START,在启动时自动启动服务,和DEMAND_START,仅在手动调用时启动。服务名称用作主机服务注册表中的标识符。描述为系统管理员提供了对您的 Karaf 安装的简要描述。执行install命令后,Karaf 控制台将显示包装服务生成的库、脚本和配置文件。您现在需要退出 Karaf 以继续服务安装。

  3. 最后一步是将它集成到主机操作系统中。这一步将需要管理员级别的权限来执行生成的 Karaf 服务包装安装脚本。

    以下命令将服务原生于 Windows 中安装:

    C:> C:\Path\To\apache-karaf-3.0.0\bin\Karaf3-service.bat install
    
    

    以下net命令允许管理员启动或停止 Karaf 服务:

    C:> net start "Karaf3"
    C:> net stop "Karaf3"
    
    

    Linux 集成将根据发行版而有所不同。以下命令适用于基于 Debian 或 Ubuntu 的系统:

    jgoodyear@ubuntu1204:~$ ln –s /Path/To/apache-karaf-3.0.0/bin/Karaf3-service /etc/init.d
    jgoodyear@ubuntu1204:~$ update-rc.d Karaf3-service defaults
    jgoodyear@ubuntu1204:~$ /etc/init.d/Karaf3-service start
    jgoodyear@ubuntu1204:~$ /etc/init.d/Karaf3-service stop
    
    

    第一个命令从 Karaf 的bin文件夹中的服务脚本创建到init.d目录的符号链接,然后更新启动脚本以包括 Karaf 服务,以便在启动时自动启动。其余两个命令可以用来手动启动或停止 Karaf 服务。

它是如何工作的...

包装服务功能将 Karaf 集成到主机操作系统的服务机制中。这意味着在基于 Windows 或 Linux 的系统上,Karaf 将利用可用的故障、崩溃、处理冻结、内存不足或类似事件检测,并自动尝试重新启动 Karaf。

参见

  • 设置 Apache Karaf 以实现高可用性配方

设置 Apache Karaf 以实现高可用性

为了帮助提供更高的服务可用性,Karaf 提供了设置 Apache Karaf 的二级实例以在操作环境错误时进行故障转移的选项。在本配方中,我们将配置一个主/从故障转移部署,并简要讨论如何将配方扩展到多个主机。

准备工作

本配方的原料包括 Apache Karaf 发行版套件、JDK 访问权限和源代码编辑器。本配方的示例配置可在github.com/jgoodyear/ApacheKarafCookbook/tree/master/chapter1/chapter1-recipe8找到。

如何做到这一点...

  1. 第一步是编辑系统属性文件。为了启用 Master/Slave 故障转移,我们编辑两个或更多 Karaf 实例的 etc/system.properties 文件,包括以下 Karaf 锁定配置:

    ##
    ## Sample lock configuration
    ##
    karaf.lock=true
    karaf.lock.class=org.apache.karaf.main.lock.SimpleFileLock
    # specify path to lock directory
    karaf.lock.dir=[PathToLockFileDirectory]
    karaf.lock.delay=10
    

    之前的配置示例包含基于文件锁定机制的必要条目,即两个或更多 Karaf 实例尝试在共享文件系统上获取对文件的独占所有权。

  2. 下一步是提供锁定资源。如果使用共享锁定文件方法适合您的部署,那么您现在必须做的只是在每个将托管 Karaf 实例的 Master/Slave 部署机器上挂载文件系统。

    小贴士

    如果您计划使用共享文件锁定,请考虑使用 NFSv4 文件系统,因为它正确实现了 flock。

    每个 Karaf 实例将在共享文件系统上包含相同的锁定目录位置,该文件系统对每个 Karaf 安装都是通用的。如果系统之间没有共享文件系统,则可以使用 JDBC 锁定机制。这将在以下代码中描述:

    karaf.lock=true
    karaf.lock.class=org.apache.karaf.main.DefaultJDBCLock
    karaf.lock.delay=10
    karaf.lock.jdbc.url=jdbc:derby://dbserver:1527/sample
    karaf.lock.jdbc.driver=org.apache.derby.jdbc.ClientDriver
    karaf.lock.jdbc.user=user
    karaf.lock.jdbc.password=password
    karaf.lock.jdbc.table=KARAF_LOCK
    karaf.lock.jdbc.clustername=karaf
    karaf.lock.jdbc.timeout=30
    

    JDBC 配置与 SimpleFileLock 配置类似。但是,它已扩展以包含 JDBC urldrivertimeoutuserpassword 选项。还包括两个额外的 JDBC 选项,允许多个 Master/Slave Karaf 部署使用单个数据库。这些是 JDBC tableclustername 选项。JDBC table 属性设置用于锁定的数据库表,而 JDBC clustername 属性指定 Karaf 实例属于哪个配对组(例如,主机 A 和 B 属于 prod 集群组,主机 C 和 D 属于 dev 集群组)。

    当使用 JDBC 锁定机制时,您必须向 Karaf 的 lib/ext 文件夹提供相关的 JDBC 驱动程序 JAR 文件。对于特定的数据库配置,请参阅 Karaf 的用户手册 (karaf.apache.org/manual/latest/index.html)。

  3. 最后一步是验证锁定行为。一旦您已配置每个 Karaf 实例成为 Master/Slave 部署的参与者,并确保所有锁定资源都已提供(挂载的文件系统或数据库驱动程序/连接性),您现在必须验证所有操作是否按预期进行。要执行的一般测试是启动一个 Karaf 实例,允许它获取锁定(您将在日志文件中看到这一记录),然后启动所有其他实例。只有第一个实例应该完全启动;其他实例应该正在尝试获取锁定。停止第一个实例应该导致另一个实例成为 Master。这一验证步骤至关重要。大多数 Master/Slave 部署故障都是由于配置错误或共享资源权限问题引起的。

如何工作…

Apache Karaf 的每个实例在其 etc/system.properties 文件中包含一份锁定配置的副本。这将在以下图中描述:

如何工作…

在 SimpleFileLock 配置的情况下,Karaf 尝试在文件上使用独占锁来管理哪个 Karaf 实例将作为活动(主)容器运行。该集合中的其他实例将尝试在karaf.lock.delay秒内获取锁文件访问权限。这可以在单个主机机器上通过两个都配置为使用相同锁定文件的 Karaf 安装轻松模拟。如果锁文件位于共享的 NFSv4 文件系统上,那么多个服务器可能能够使用此配置。然而,在多主机架构中,基于 JDBC 的锁是最常使用的。

还有更多……

Karaf 故障转移描述了一种高可用性的活动/被动方法。还有一个类似的概念,通过 Apache Karaf Cellar 提供活动/活动架构。

第二章:使用 Apache Camel 制作智能路由器

在本章中,我们将涵盖以下主题:

  • 将 Apache Camel 模块安装到 Apache Karaf 中

  • 在 Karaf 中列出 Camel 上下文

  • 在 Karaf 中显示 Camel 上下文信息

  • 在 Karaf 中启动和停止 Camel 上下文

  • 在 Karaf 中列出路由

  • 在 Karaf 中显示路由信息

  • 在 Karaf 中启动、停止、挂起和恢复路由

  • 在 Karaf 中列出端点

  • 制作一个纯 Java 基于的 Camel 路由器以在 Karaf 中部署

  • 创建基于 Blueprint 的 Camel 路由器以在 Karaf 中部署

  • 将配置管理器添加到基于 Blueprint 的 Camel 路由器

  • 创建 Camel 路由器的托管服务工厂实现

简介

Apache Karaf 提供了一个友好的基于 OSGi 的容器环境,用于部署、管理和最重要的是享受您的应用程序。在 Karaf 上托管的项目中,更常见的是基于 Apache Camel 的路由器。在本章中,我们将探讨一些食谱,帮助您快速、轻松、愉快地使用 Camel 在 Karaf 上。

在我们继续之前,让我们更仔细地看看 Apache Camel。

Apache Camel 提供了一个基于规则的 Java 路由和中介引擎,实现了企业集成模式(EIPs),如企业集成模式:设计、构建和部署消息解决方案Gregor Hohpe 和 Bobby WoolfAddison Wesley中所述。Camel 库的一个关键特性是其特定领域的语言,用于配置路由器和中介。这允许在集成开发环境中进行类型安全的规则完成,从而大大节省时间并减少复杂性。

小贴士

本章的目的是探索 Apache Camel-Apache Karaf 集成。要深入了解企业集成模式和 Camel,请阅读 Packt Publishing 出版的Apache Camel 开发者食谱Instant Apache Camel Messaging SystemInstant Apache Camel Message Routing

将 Apache Camel 模块安装到 Apache Karaf 中

在我们开始探索如何构建 Camel-Karaf 智能路由器之前,我们必须首先将所有必需的 Camel 模块安装到 Karaf 容器中。

准备工作

本食谱的成分包括 Apache Karaf 发行套件、对 JDK 的访问和互联网连接。

如何操作…

  1. 首先,我们使用以下命令将 Camel 功能 URL 添加到我们的 Karaf 安装功能仓库中:

    karaf@root()> feature:repo-add mvn:org.apache.camel.karaf/apache-camel/2.12.2/xml/features
     Adding feature url mvn:org.apache.camel.karaf/apache-camel/2.12.2/xml/features
    karaf@root()>
    
    

    小贴士

    或者,您可以使用feature:repo-add camel 2.12.2命令。

    在添加功能 URL 后,Karaf 将准备好安装所有 Apache Camel 依赖项。如果您想查看所有安装目标,请发出feature:list | grep –i camel命令。

  2. 下一步是将基础 Camel 功能安装到 Karaf 中。我们通过执行feature:install命令和功能名称来安装功能,如下面的命令所示:

    karaf@root()>  feature:install camel
    
    

    我们可以通过执行 list –t 0 | grep camel 命令来验证安装,该命令将列出 Karaf 中安装的所有 Camel 组件(camel-core、camel-karaf-commands、camel-spring 和 camel-blueprint)。

它是如何工作的…

Apache Camel 社区为每个项目版本维护一个 Apache Karaf 功能描述符。当描述符文件通过 feature:repo-add 命令添加到 Karaf 中时,容器会处理其内容,使每个功能的目标可安装。以下图示显示了各种 Camel 打包如何在基础 Karaf 系统之上部署:

如何工作…

当安装特定功能(在本例中为 Camel)时(使用 feature:install 命令),Karaf 将使用适当的 URL 处理程序来获取所需资源并将它们安装到容器中,然后尝试将它们带到 Started 状态。如果你在 Karaf 控制台中执行 list –t 0,你会看到部署到容器中的 Camel 和所有其他工件。我们可以通过展示关键 Camel 工件部署在标准 Karaf 安装之上来更简单地描述 Camel 组件集成到 Karaf 中的过程。

参见

  • 第一章 中 将应用程序作为功能部署 的配方,为系统构建者准备的 Apache Karaf

在 Karaf 中列出 Camel 上下文

将 Apache Camel 安装到 Apache Karaf 中包括一组自定义 Camel 命令,作为 camel-karaf-commands 打包的一部分。Camel 社区已经开发和维护了这些命令,以供 Karaf 用户使用,并因此帮助将 Camel 完全集成到 Karaf 体验中。以下截图列出了这些命令:

在 Karaf 中列出 Camel 上下文

截至 Apache Camel 2.12.2,有 18 个 Camel-Karaf 命令(如前一个截图所示),在以下配方中,我们将探讨最常用的命令。

Camel 用户想要执行的一个常见任务是列出部署到 Karaf 容器中的所有 Camel 上下文。

准备工作

此配方的成分包括 Apache Karaf 分发套件、对 JDK、Maven 和源代码编辑器的访问。

为此配方开发了一个 Camel 示例应用程序,可在 github.com/jgoodyear/ApacheKarafCookbook/tree/master/chapter2/chapter2-recipe2 找到。构建应用程序需要执行 Maven 安装,然后将组装好的打包部署到 Karaf 中(使用 install –s mvn:com.packt/sample 命令)。

按照在 将 Apache Camel 模块安装到 Apache Karaf 中 的配方中的说明操作,以提供操作示例代码的基本要求。我们将多次重用此配方的资源。

如何操作…

要列出 Karaf 中部署的所有 Camel 上下文,请执行 camel:context-list 命令:

karaf@root()> camel:context-list
 Context               Status         Uptime 
 -------               ------         ------ 
 CamelCommandContext   Started        1 hour 44 minutes 
karaf@root()>

在之前的命令调用中,我们观察到示例 Camel 路由器的上下文名称被显示(在这个例子中,上下文名称是在 Blueprint 中设置的——有关详细信息,请参阅本配方的源代码)。

工作原理…

当通过 Camel 功能将 camel-karaf-commands 包安装到 Karaf 中时,Camel 命令将自动在 Karaf 控制台中可用。在底层,Camel 命令蓝图描述符被实例化,并且各种 Camel-Karaf 命令被连接到容器中。

当执行 context-list 命令时,显示每个已部署 Camel 上下文的上下文 ID 以及它们当前的状态,如果有的话,还有它们的运行时间。

更多内容…

Apache Camel 社区维护着他们命令的最新信息,您可以在camel.apache.org/karaf.html找到这些信息。

参见

  • 在 Karaf 中显示 Camel 上下文信息 的配方

在 Karaf 中显示 Camel 上下文信息

Karaf 可以通过 camel:context-info 命令显示容器中部署的个别 Camel 上下文的详细信息。使用此命令可以发现上下文范围内的统计信息、行为、包含的组件等。

准备工作

按照本配方下的 在 Karaf 中列出 Camel 上下文 配方的 准备工作 部分的说明进行操作。

如何操作…

使用以下 camel:context-info 命令在 Karaf 控制台中检索上下文信息——请注意,可能会有大量输出生成:

karaf@root()> camel:context-info CamelCommandContext 

输出结果如下:

Camel Context CamelCommandContext
 Name: CamelCommandContext
 ManagementName: 123-CamelCommandContext
 Version: 2.12.2
 Status: Started
 Uptime: 1 hour 50 minutes
Statistics
 Exchanges Total: 1321
 Exchanges Completed: 1321
 Exchanges Failed: 0
 Min Processing Time: 0ms
 Max Processing Time: 6ms
 Mean Processing Time: 0ms
 Total Processing Time: 1110ms
 Last Processing Time: 1ms
 Delta Processing Time: 0ms
 Load Avg: 0.00, 0.00, 0.00
 Reset Statistics Date: 2014-02-27 16:01:41
 First Exchange Date: 2014-02-27 16:01:42
 Last Exchange Completed Date: 2014-02-27 17:51:43
 Number of running routes: 1
 Number of not running routes: 0

Miscellaneous
 Auto Startup: true
 Starting Routes: false
 Suspended: false
 Shutdown timeout: 300 sec.
 Message History: true
 Tracing: false

Properties

Advanced
 ClassResolver: org.apache.camel.core.osgi.OsgiClassResolver@2ffd5a29
 PackageScanClassResolver: org.apache.camel.core.osgi.OsgiPackageScanClassResolver@222a525c
 ApplicationContextClassLoader: BundleDelegatingClassLoader(sample [123])
Components
 mock
 bean
 timer
 properties

Dataformats

Languages
 simple

Routes
 CamelRoute-timerToLog

前面的 camel:context-info 调用表明,每个上下文都有大量数据可用;用户捕获此输出进行分析并不罕见。

工作原理…

context-info 命令连接到 Camel 自身的设施以访问上下文信息。检索后,数据随后被格式化以在 Karaf 控制台上显示。

参见

  • 在 Karaf 中启动和停止 Camel 上下文 的配方

在 Karaf 中启动和停止 Camel 上下文

启动和停止包含 Camel 上下文的包可能非常繁琐;您可以使用 camel:context-startcamel:context-stop 命令来管理特定上下文。

准备工作

按照本配方下的 在 Karaf 中列出 Camel 上下文 配方的 准备工作 部分的说明进行操作。

如何操作…

在 Karaf 中管理 Camel 上下文很容易,但需要您熟悉两个命令,如下所示:

  • camel:context-start contextName:此命令用于启动上下文

  • camel:context-stop contextName:此命令用于停止上下文

以下 Camel 命令调用演示了停止上下文的结果:

karaf@root()> camel:context-list
 Context               Status         Uptime 
 -------               ------         ------ 
 CamelCommandContext   Started        3.139 seconds 
karaf@root()> camel:context-stop CamelCommandContext 
karaf@root()> camel:context-list
karaf@root()>

工作原理…

上下文命令在 Camel 框架上操作,并不代表 OSGi 生命周期。根据您的应用程序,停止的上下文可能需要重新启动其宿主包。

参见

  • 在 Karaf 中列出路由 的配方

在 Karaf 中列出路由

在 Apache Karaf 容器中部署数十个 Camel 路由是很常见的。为了使管理这些路由更容易,Apache Camel 提供了一个命令,通过 ID 列出所有 Camel 部署的路由。

准备就绪

按照本食谱的 在 Karaf 中列出 Camel 上下文 食谱的 准备就绪 部分的说明进行操作。

如何操作…

使用 camel:route-list 命令列出 Karaf 中部署的所有路由,如下所示:

karaf@root()> camel:route-list 
 Context               Route                   Status 
 -------               -----                   ------ 
 CamelCommandContext   CamelRoute-timerToLog   Started 

前面的调用收集并显示在 Karaf 控制台上部署的所有路由。

它是如何工作的…

当执行 route-list 命令时,每个 Camel 上下文中每个路由的路由 ID 以及它们当前的状态都会显示出来。

小贴士

在开发路由时,分配一个描述性的 ID 以帮助简化管理。

相关链接

  • 在 Karaf 中显示路由信息 食谱

在 Karaf 中显示路由信息

Apache Camel 提供了收集 Camel 上下文中部署的路由周围信息的机制。route-info 命令已提供,用于在 Karaf 控制台显示路由属性、统计信息和定义。

准备就绪

按照本食谱的 在 Karaf 中列出 Camel 上下文 食谱的 准备就绪 部分的说明进行操作。

如何操作…

使用以下 camel:route-info routeId 命令在 Karaf 控制台显示 Camel 路由的信息;类似于 camel:context-info 命令,此命令可能会生成大量输出:

karaf@root()> camel:route-info CamelRoute-timerToLog 
Camel Route CamelRoute-timerToLog
 Camel Context: CamelCommandContext

Properties
 id = CamelRoute-timerToLog
 parent = 20443040

Statistics
 Inflight Exchanges: 0
 Exchanges Total: 44
 Exchanges Completed: 44
 Exchanges Failed: 0
 Min Processing Time: 0 ms
 Max Processing Time: 2 ms
 Mean Processing Time: 0 ms
 Total Processing Time: 38 ms
 Last Processing Time: 1 ms
 Delta Processing Time: 0 ms
 Load Avg: 0.00, 0.00, 0.00
 Reset Statistics Date: 2014-02-27 17:59:46
 First Exchange Date: 2014-02-27 17:59:47
 Last Exchange Completed Date: 2014-02-27 18:03:22

Definition
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<route customId="true" id="CamelRoute-timerToLog" >
 <from uri="timer:foo?period=5000"/>
 <setBody id="setBody8">
 <method ref="helloBean" method="hello"></method>
 </setBody>
 <log message="The message contains ${body}" id="log8"/>
 <to uri="mock:result" id="to8"/>
</route>

karaf@root()>

前面的调用显示了当我们的示例 Camel 应用程序的路由显示时生成的输出。

它是如何工作的…

Apache Camel 提供了跟踪路由统计信息的机制;route-info 命令连接到这些设施,向 Karaf 控制台提供路由信息。注意在路由定义中各种 ID 后附加的 8,这是 Camel 生成的,以帮助区分不同的组件并避免名称冲突。原始路由定义将不会携带此值。

相关链接

  • 在 Karaf 中启动、停止、挂起和恢复路由 食谱

在 Karaf 中启动、停止、挂起和恢复路由

Apache Camel 为用户提供了对 Camel 上下文中部署的路由的精细控制,因此为 Karaf 提供了访问这些控制的功能。这些管理设施独立于 OSGi 的生命周期模型,允许用户选择当前正在执行的小部分 Camel 代码来启动、停止、挂起和恢复操作。

准备就绪

按照本食谱的 在 Karaf 中列出 Camel 上下文 食谱的 准备就绪 部分的说明进行操作。

如何操作…

在 Karaf 中管理 Camel 路由很简单,需要你熟悉以下四个命令:

  • camel:route-start routeName:此命令用于启动一个路由

  • camel:route-stop routeName:此命令用于停止一个路由

  • camel:route-suspend routeName:此命令用于挂起一个路由

  • camel:route-resume routeName:此命令用于恢复挂起的路由

为了使这些命令更清晰,让我们回顾如何使用在 Karaf 中列出 Camel 上下文食谱中提供的示例 Camel 应用程序来使用它们。

在以下调用中,我们列出 Karaf 中部署的所有 Camel 路由,然后对CamelRoute-timerToLog(我们的示例 Camel 应用程序)发出停止命令。我们可以观察到它将路由的状态从Started更改为Stopped。这可以通过以下命令完成:

karaf@root()> camel:route-list
 Context               Route                   Status 
 -------               -----                   ------ 
 CamelCommandContext   CamelRoute-timerToLog   Started 
karaf@root()> camel:route-stop CamelRoute-timerToLog
karaf@root()> camel:route-list
 Context               Route                   Status 
 -------               -----                   ------ 
 CamelCommandContext   CamelRoute-timerToLog   Stopped 

然后,我们可以使用route-start命令将路由恢复到Started状态,如下面的命令片段所示:

karaf@root()> camel:route-start CamelRoute-timerToLog 
karaf@root()> camel:route-list
 Context               Route                   Status 
 -------               -----                   ------ 
 CamelCommandContext   CamelRoute-timerToLog   Started 

我们现在可以通过route-suspend命令挂起路由,并确认命名路由进入Suspended状态,如下面的命令片段所示:

karaf@root()> camel:route-suspend CamelRoute-timerToLog 
karaf@root()> camel:route-list
 Context               Route                   Status 
 -------               -----                   ------ 
 CamelCommandContext   CamelRoute-timerToLog   Suspended 

最后,我们可以使用route-resume命令将挂起的路由恢复到Started状态,如下面的命令片段所示:

karaf@root()> camel:route-resume CamelRoute-timerToLog 
karaf@root()> camel:route-list
 Context               Route                   Status 
 -------               -----                   ------ 
 CamelCommandContext   CamelRoute-timerToLog   Started 
karaf@root()>

它是如何工作的…

Camel 路由管理命令是从 Camel 上下文的角度工作的,独立于 OSGi 生命周期。在执行命令期间,宿主包的 OSGi 状态不受影响。这为用户提供了一种细粒度的管理方法,允许宿主包及其上下文(s)保持运行,仅操作一个或多个路由。

参见

  • 在 Karaf 中启动和停止 Camel 上下文的食谱

在 Karaf 中列出端点

Apache Camel 用户使用端点来表示事件和信息来自或去向的 URI。在 Karaf 中,endpoint-list命令已被提供,以帮助简化跟踪这些 URI。

准备工作

按照第在 Karaf 中列出 Camel 上下文食谱的准备工作部分的说明进行此食谱。

如何做…

使用camel:endpoint-list命令列出 Karaf 中的所有端点(如果您想限制输出到一个上下文的路由,请使用camel:endpoint-list context-name命令)。这如下面的命令所示:

karaf@root()> camel:endpoint-list 
 Context               Uri                       Status 
 -------               ---                       ------ 
 CamelCommandContext   mock://result             Started 
 CamelCommandContext   timer://foo?period=5000   Started 

在前面的调用中,Karaf 中找到的所有端点都会显示(在这个例子中,端点是在 Blueprint 中设置的——有关详细信息,请参阅食谱的源代码)。

它是如何工作的…

当执行端点列表命令时,会扫描并列出每个 Camel 上下文中的所有路由到 Karaf 的控制台。

参见

  • 使用 Maven 存档创建我们自己的自定义 Karaf 命令的第一章,Apache Karaf for System Builders

创建一个纯 Java 的 Camel Router 以在 Karaf 中部署

开发我们的第一个 Camel Router 以在 Karaf 中部署不一定需要使用大量的框架和库。在这个食谱中,我们将使用纯 Java 代码以及少量的 OSGi 和 Camel 库来创建 Camel 路由器。

准备工作

此食谱的成分包括 Apache Karaf 发行套件、对 JDK、Maven 和源代码编辑器的访问。此食谱的示例代码可在github.com/jgoodyear/ApacheKarafCookbook/tree/master/chapter2/chapter2-recipe3找到。按照将 Apache Camel 模块安装到 Apache Karaf食谱中的说明,提供操作示例代码的基本要求。

如何做到这一点...

  1. 第一步是创建一个基于 Maven 的项目。一个包含基本 Maven 坐标信息和包打包指令的pom.xml文件就足够了。

  2. 下一步是将 Apache Camel 和 OSGi 依赖项添加到 POM 文件中。我们需要将camel-coreorg.osgi.core工件依赖项添加到 POM 文件中。这将在以下代码中描述:

    <dependencies>
        <dependency>
            <groupId>org.apache.camel</groupId>
            <artifactId>camel-core</artifactId>
            <version>${camel.version}</version>
        </dependency>
        <dependency>
            <groupId>org.osgi</groupId>
            <artifactId>org.osgi.core</artifactId>
            <version>${osgi.version}</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
    

    对于 Apache Karaf 3.0.0,我们使用 Camel 版本 2.12.2 和 OSGi 版本 5.0.0。

  3. 接下来,我们添加我们的构建配置,如下所示:

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.felix</groupId>
                <artifactId>maven-bundle-plugin</artifactId>
                <version>${maven-bundle-plugin.version}</version>
                <extensions>true</extensions>
                <configuration>
                    <instructions>
                        <Bundle-SymbolicName>
                        ${project.artifactId}
                        </Bundle-SymbolicName>
                        <Bundle-Version>
                        ${project.version}
                        </Bundle-Version>
     <Bundle-Activator>
     com.packt.Activator
     </Bundle-Activator>
                        <Export-Package>
                        com.packt*;version=${project.version}
                        </Export-Package>
                        <Import-Package>*</Import-Package>
                    </instructions>
                </configuration>
            </plugin>
        </plugins>
    </build>
    

    我们使用maven-bundle-plugin来构建我们的包,添加Bundle-Activator指令。当包部署到 Karaf 中时,OSGi 容器将调用com.packt.Activator类中包含的启动和停止方法。

  4. 下一步是实现 OSGi BundleActivator。现在我们已经建立了一个基本的项目结构,我们可以实现我们的 Java 代码。我们首先在src/main/java/com/packt文件夹中创建Activator.java文件。

    我们编写的Activator类将实现BundleActivator接口。BundleActivator接口实现了 OSGi 容器在启动或停止包时调用的方法。我们将使用包的启动和停止方法来控制 Camel Context 的创建、Camel 路由的添加以及路由的实际启动和停止。有关 Apache Camel 的更多详细信息,请访问camel.apache.org。以下是一个示例代码:

    public void start(BundleContext context) {
        System.out.println("Starting the bundle");
     camelContext = new DefaultCamelContext();
        try {
     camelContext.addRoutes(new MyRouteBuilder());
     camelContext.start();
        }
        catch (Exception ex) {
            // Use logging subsystem in non-sample code.
            System.out.println("Exception occured! " + ex.getMessage());
        }
    }
    

    当我们的激活器接口启动时,我们将创建一个新的CamelContext对象,然后尝试添加一个MyRouteBuilder函数(这创建了一个 Camel 路由),然后启动上下文及其包含的路由。以下是一个示例代码:

    public void stop(BundleContext context) {
        System.out.println("Stopping the bundle");
        if (camelContext != null) {
            try { 
     camelContext.stop();
            }
            catch (Exception ex) {
                System.out.println("Exception occurred during stop context.");
            }
        }
    }
    

    当我们的激活器接口停止时,我们首先检查我们的CamelContext对象是否为 null,然后尝试调用其上的stop函数。当上下文停止时,其中包含的所有路由也会停止。

  5. 接下来,我们实现我们的 Camel Router。我们的 Camel Router 定义在一个自定义路由构建器中,我们从这个 Camel 的RouteBuilder类扩展。以下是一个示例代码:

    public class MyRouteBuilder extends RouteBuilder {
    
        public void configure() {
    
            from("file:src/data?noop=true")
                .choice()
                    .when(xpath("/recipe = 'cookie'"))
                        .log("Cookie  message")
                        .to("file:target/messages/cookies")
                    .otherwise()
                        .log("Other message")
                        .to("file:target/messages/others");
        }
    
    }
    

    MyRouteBuilder类扩展了 Camel 的RouteBuilder类,它提供了路由配置接口。我们使用基于 Java 的 DSL 在configure方法中添加一个 Camel 路由定义。

  6. 下一步是构建和部署我们的 Camel 路由器到 Karaf。现在我们已经实现了我们的构建配置(POM 文件),将其与 OSGi 运行时(Activator 类)绑定,并实现了我们的 Camel 路由器(MyRouteBuilder 类),我们可以继续编译和部署代码到 Karaf。我们的第一步是调用 mvn install,然后在 Karaf 控制台中执行 install –s mvn:com.packt/osgi (mvn:groupId/artifactId)。

  7. 作为最后一步,我们准备测试我们的 Camel 路由器!一旦我们的路由器包在 Karaf 中安装并激活,你将在 KARAF_HOME 文件夹中看到一个 src/data 文件夹。我们的示例路由器配置处理基于 XML 的食谱文件。当它看到饼干食谱(<recipe>cookie</recipe>)时,它将一个副本放在 KARAF_HOME/target/messages/cookies 文件夹中;否则,它将该副本放在 KARAF_HOME/target/message/other 文件夹中。

它是如何工作的…

我们基于纯 Java 的 Camel 路由器通过利用 OSGi 的 BundleActivator 启动和停止接口以及直接使用 Apache Camel 库来工作。当部署到 Karaf 中时,我们可以可视化以下图中显示的相关组件:

它是如何工作的…

当一个包在一个 OSGi 容器中启动时,它将调用其 Bundle-Activator 类的 start 方法。我们的示例项目将其配置为 com.packt.Activator 类。我们重用 Activator 的启动和停止方法来控制 Camel 上下文,该上下文包含由我们的 RouteBuilder 类实现构建的 Camel 路由器。

参见

  • 基于 Karaf 部署的“创建蓝图模式的 Camel 路由器”食谱

为 Karaf 部署创建基于蓝图模式的 Camel 路由器

蓝图为 OSGi 提供了一个依赖注入框架。许多用户会发现它与 Spring 框架有相似之处。然而,蓝图已被设计来处理 OSGi 的动态运行时,其中服务经常来来去去。

标准的 Apache Camel-Karaf 功能包含用户立即开始使用蓝图连接路由所需的 Camel-Blueprint 库。在本食谱中,我们将构建一个 Camel 路由器,利用蓝图控制反转框架的优势。

准备就绪

本食谱的成分包括 Apache Karaf 分发套件、对 JDK、Maven 和源代码编辑器的访问。本食谱的示例代码可在github.com/jgoodyear/ApacheKarafCookbook/tree/master/chapter2/chapter2-recipe4找到。按照“将 Apache Camel 模块安装到 Apache Karaf”食谱中“准备就绪”部分的说明,提供操作示例代码的基本要求。

如何操作…

Apache Camel 社区为用户提供了一个 Maven 架构来生成基于蓝图的 OSGi Camel 路由器:

  1. 第一步是使用 Maven 原型生成 Camel Blueprint 项目。我们可以通过调用以下命令片段中的原型来创建我们的项目:

    mvn archetype:generate \
     -DarchetypeGroupId=org.apache.camel.archetypes \
     -DarchetypeArtifactId=camel-archetype-blueprint \
     -DarchetypeVersion=2.12.2 \
     -DarchetypeRepository=https://repository.apache.org/content/groups/snapshots-group
    
    

    在生成过程中,你将被要求提供groupIdartifactId和项目version值。

    此原型调用将生成一个 POM 文件、一个 Hello 接口的 Java 源代码以及实现 HelloBean 的 Blueprint 描述符 XML 文件,以及一个示例单元测试。接口和 bean 组件纯粹用于示例目的;在现实世界的开发场景中,你将删除这些工件。

  2. 下一步是将项目构建并部署到 Karaf 中。要构建项目,我们调用mvn install命令。这将把我们的 bundle 填充到你的本地m2仓库中。要安装示例,请在 Karaf 控制台执行以下命令:

    karaf@root()> bundle:install –s mvn:com.packt/bp/1.0.0-SNAPSHOT
    
    

    在前面的命令中,我们用格式mvn:{groupId}/{artifactID}/{version}替换你的 Maven 坐标。

  3. 最后一步是验证路由功能。一旦安装并启动(使用start BundleID命令),你将在你的 Karaf 日志文件中观察到以下条目:

    2014-02-06 10:00:49,074 | INFO  | Local user karaf | BlueprintCamelContext            | 85 - org.apache.camel.camel-core - 2.12.2 | Total 1 routes, of which 1 is started.
    2014-02-06 10:00:49,075 | INFO  | Local user karaf | BlueprintCamelContext            | 85 - org.apache.camel.camel-core - 2.12.2 | Apache Camel 2.12.2 (CamelContext: blueprintContext) started in 0.357 seconds
    2014-02-06 10:00:50,075 | INFO  | 12 - timer://foo | timerToLog       | 85 - org.apache.camel.camel-core - 2.12.2 | The message contains Hi from Camel at 2014-02-06 10:00:50
    

工作原理…

当我们的项目 bundle 部署到 Karaf 中时,项目的依赖关系被解决,在启动时,Blueprint 描述符文件被处理,对象被实例化并填充到 Blueprint 容器中。以下图表突出了在 Karaf 中部署的组件的高级视图:

工作原理…

给定在 Blueprint 容器中成功实例化的服务,容器内嵌入的CamelContext对象将自动启动。

更多内容…

Apache Camel 项目还提供了 Camel-Spring 库,作为其标准功能部署的一部分。Spring 框架可以在 Karaf 环境中工作,但通常更倾向于使用 Blueprint 或声明性服务。

参见

  • 将配置管理添加到基于 Blueprint 的 Camel 路由器菜谱

将配置管理添加到基于 Blueprint 的 Camel 路由器

Blueprint 允许我们将一些配置元素从我们的代码中外部化;我们可以通过利用 OSGi 配置管理服务(通常称为配置管理)来将这一过程提升到下一个层次。

配置管理服务为 OSGi 容器中的服务提供配置属性。在 Apache Karaf 中,通过包括 Apache Felix File Install 目录管理代理来改进这一功能。File Install 监控一个目录,可以自动安装和启动一个 bundle 或对配置管理进行配置文件更新。

注意

Apache Felix File Install 提供了 Karaf 的deployetc文件夹背后的魔法,自动处理文件在添加、删除或更新时的操作。

在这个菜谱中,我们将集成配置管理服务到我们的基于 Blueprint 的 Camel 项目中。

准备工作

此配方所需的配料包括 Apache Karaf 发行套件、对 JDK、Maven 和源代码编辑器的访问。此配方的示例代码可在github.com/jgoodyear/ApacheKarafCookbook/tree/master/chapter2/chapter2-recipe5找到。按照将 Apache Camel 模块安装到 Apache Karaf配方中的说明,提供操作示例代码的基本要求。

如何做到这一点...

Apache Camel 社区为其用户提供了一个 Maven 存档来生成基于蓝图的 OSGi Camel 路由器。我们将以此作为起点来构建我们的项目,并添加所需的支持配置管理员的功能:

  1. 第一步是使用 Maven 存档生成 Camel 蓝图项目。我们可以通过以下方式调用存档来创建我们的项目:

    mvn archetype:generate \
     -DarchetypeGroupId=org.apache.camel.archetypes \
     -DarchetypeArtifactId=camel-archetype-blueprint \
     -DarchetypeVersion=2.12.2 \ 
     -DarchetypeRepository=https://repository.apache.org/content/groups/snapshots-group
    
    

    在生成过程中,您将被要求提供groupIdartifactId和项目version值。

    这将生成一个 POM 文件、实现 Hello 接口的 Java 源代码以及实现 HelloBean 的蓝图描述符 XML 文件,以及一个示例单元测试。

  2. 现在我们已经有一个基本的项目结构,让我们修改它以使用配置管理员。由于我们使用蓝图,我们只需要修改位于src/main/resources/OSGI-INF/blueprint文件夹中的描述符文件。

    我们的第一步是添加一个额外的命名空间用于配置管理员,如下面的代码所示:

    <blueprint 
    
            xsi:schemaLocation="
            http://www.osgi.org/xmlns/blueprint/v1.0.0
            http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd
     http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.1.0
            http://camel.apache.org/schema/blueprint http://camel.apache.org/schema/blueprint/camel-blueprint.xsd">
    

    我们将使用cm命名空间来访问配置管理。

  3. 下一步是修改蓝图文件中的 bean 连接,以使用配置管理员变量。我们更新提供的HelloBean bean 以接受一个配置变量,如下面的代码所示:

    <bean id="helloBean" class="com.packt.HelloBean">
     <property name="say" value="${greeting}"/>
    </bean>
    

    变量使用语法${variable-name}

  4. 现在,我们可以添加我们的配置管理员引用,如下所示:

    <!-- OSGI blueprint property placeholder -->
    <!-- etc/recipe.cfg -->
    <cm:property-placeholder persistent-id="recipe" update-strategy="reload">
    
        <!-- list some properties for this test -->
     <cm:default-properties>
     <cm:property name="greeting" 
     value="Hello World"/>
     <cm:property name="result" 
     value="mock:result"/>
        </cm:default-properties>
    </cm:property-placeholder>
    

    在这里,我们将配置管理行为设置为在配置更新时重新加载应用程序。然后,我们为在蓝图文件中使用的两个变量提供默认值。我们还设置了一个persistent-id占位符,我们可以与 Karaf 的etc文件夹结合使用,以提供动态外部配置。在前面提供的代码中,我们可以在etc文件夹中创建一个recipe.cfg文件,该文件包含greetingresult属性。

    小贴士

    当编写 Apache Karaf 功能描述符时,您可以将配置文件添加到功能将安装的资源列表中。

  5. 下一步是更新蓝图中的 Camel 上下文以使用配置管理员变量。这可以通过以下方式完成:

    <camelContext id="blueprintContext" trace="false" >
        <route id="timerToLog">
            <from uri="timer:foo?period=5000"/>
            <setBody>
     <method ref="helloBean" method="hello"/>
            </setBody>
            <log message="The message contains ${body}"/>
     <to uri="{{result}}"/>
        </route>
    </camelContext>
    

    我们的 Camel 路由包含一个直接修改:引入一个{{result}}变量。Camel 使用双大括号语法来表示外部变量。helloBean引用保持不变。然而,它的运行时行为现在是使用蓝图描述符中的默认变量或配置管理员提供的值。

  6. 下一步是构建和部署项目到 Karaf 中。要构建项目,我们调用 mvn install 命令。这将把我们的包填充到你的本地 m2 仓库中。要安装示例,请在 Karaf 控制台中执行以下命令:

    karaf@root()> bundle:install –s mvn:com.packt/bp-configadmin/1.0.0-SNAPSHOT
    
    

    在前面的命令中,我们将你的 Maven 坐标替换为 mvn:{groupId}/{artifactID}/{version} 格式。

  7. 最后一步是验证路由功能。一旦路由器安装并启动,你将在你的 Karaf 日志文件中观察到以下形式的条目:

    2014-02-06 13:36:01,892 | INFO  | Local user karaf | BlueprintCamelContext | 85 - org.apache.camel.camel-core - 2.12.2 | Total 1 routes, of which 1 is started.
    2014-02-06 13:36:01,892 | INFO  | Local user karaf | BlueprintCamelContext | 85 - org.apache.camel.camel-core - 2.12.2 | Apache Camel 2.12.2 (CamelCon: blueprintContext) started in 0.272 seconds
    2014-02-06 13:36:02,891 text| INFO  | 15 - timer://foo | timerToLog | 85 - org.apache.camel.camel-core - 2.12.2 |   The message contains Hello World at2014-02-06 13:36:02 
    

    小贴士

    更改变量值需要编辑 Blueprint 文件并在 Karaf 中刷新包。然而,对配置文件中值的更改几乎会立即被捕获。

如何工作…

这个脚本通过引入 Configuration Admin 到设计中扩展了 在 Karaf 中部署的基于 Blueprint 的 Camel 路由 脚本。以下图表突出了在 Karaf 中部署的组件的高级视图:

如何工作…

将 Configuration Admin 添加到 Blueprint 中,将 Camel 路由暴露给外部配置。这个外部配置在 Karaf 的 etc 文件夹中表现为 Java 属性文件。

那么,我们如何将属性文件关联到我们的 Blueprint 规范中的 Configuration Admin 引用呢?这是通过在 Configuration Admin 属性占位符的 Blueprint 定义中设置持久化 ID 来实现的。在我们的演示代码中,我们使用持久化 ID 脚本——在我们的 etc 文件夹中,我们会使用一个名为 recipe.cfg 的相应文件。

我们如何使用属性名值对?我们在 Blueprint 定义中包含默认的名值对。如果提供了值,这些值将被 Configuration Admin 自动覆盖。我们使用单大括号在 beans 中访问它们的值,在 Blueprint 定义的 Camel 上下文中使用双大括号。

参见

  • 创建 Camel 路由的托管服务工厂实现 脚本

创建 Camel 路由的托管服务工厂实现

在这个脚本中,我们将介绍 OSGi 模式 ManagedServiceFactory 接口在 Apache Camel 智能路由器中的强大功能。这个模式将允许我们通过配置管理多个服务实例,或者在我们的情况下,Camel 路由。实际上,我们将为每个提供给服务工厂的配置生成一个新的路由器实例!

准备工作

这个脚本的配料包括 Apache Karaf 分发套件、对 JDK、Maven 和源代码编辑器的访问。这个脚本的示例代码可在 github.com/jgoodyear/ApacheKarafCookbook/tree/master/chapter2/chapter2-recipe6 找到。按照 将 Apache Camel 模块安装到 Apache Karaf 脚本中的说明,提供操作示例代码的基本要求。

如何操作…

这个配方将比我们之前的配方稍微复杂一些。强烈建议您按照提供的示例代码进行操作:

  1. 首先,使用我们在之前的配方中使用的便捷 Maven 存档创建一个基于 Blueprint 的 Camel 项目(参见在 Karaf 中部署基于 Blueprint 的 Camel Router配方)。我们将以此为基础构建项目,根据需要删除和/或修改资源。

  2. 下一步是在 POM 文件中添加依赖项。我们将编辑 POM 文件,添加对 OSGi 核心和组合库的依赖,如下面的代码所示:

    <dependency>
        <groupId>org.osgi</groupId>
        <artifactId>org.osgi.core</artifactId>
        <version>${osgi-core-version}</version>
    </dependency>
    <dependency>
        <groupId>org.osgi</groupId>
        <artifactId>org.osgi.compendium</artifactId>
        <version>${osgi-compendium-version}</version>
    </dependency>
    

    对于 Apache Karaf 3.0.0,我们使用 OSGi 核心和组合库版本 5.0.0。

  3. 现在,我们将修剪生成的项目结构。我们将删除预先填充的blueprint.xml文件、main文件夹和test文件夹。

  4. 现在,我们将在src/main/java文件夹中实现我们的 ManagedServiceFactory 接口。为此,我们将创建一个实现 ManagedServiceFactory 接口的工厂类,并将一个调度器插入到这个框架中,该框架将处理 Camel 路由的构建和执行。我们将在本配方的工作原理部分详细说明这些类的复杂性。

    小贴士

    OSGi 组合库 rev 5 的 ManagedServiceFactory 接口可以在 OSGi 联盟网站上找到,网址为www.osgi.org/javadoc/r5/cmpn/org/osgi/service/cm/ManagedServiceFactory.html

  5. 接下来,我们在src/main/resources文件夹中创建一个 Blueprint 文件,将配置管理器、Camel 上下文和我们的Factory类连接起来。

    <blueprint 
    
        xsi:schemaLocation="
        http://www.osgi.org/xmlns/blueprint/v1.0.0
        http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd
        http://camel.apache.org/schema/blueprint 
        http://camel.apache.org/schema/blueprint/camel-blueprint.xsd">
    
      <!—- Setup process Id name for configuration file -->
      <cm:property-placeholder persistent-id="com.packt.hellofactory" update-strategy="reload">
        <cm:default-properties>
            <cm:property name="com.packt.hellofactory.pid" value="com.packt.hellofactory"/>
        </cm:default-properties>
      </cm:property-placeholder>
    
      <!—- Create Camel Context -->
      <camelContext id="helloContext"  autoStartup="true">
      </camelContext>
    
      <!—- Setup Bean, wiring in contexts, andconfiguration data  -->
      <bean id="apacheKarafCookbook" class="com.packt.HelloFactory" init-method="init" destroy-method="destroy">
        <property name="bundleContext" ref="blueprintBundleContext"/>
        <property name="configurationPid"       value="${com.packt.hellofactory.pid}"/>
        <property name="camelContext" ref="helloContext"/>
      </bean>
    
    </blueprint>
    

    上一段代码中的 Blueprint 文件包含所需的一般结构元素;我们将在本配方中的工作原理部分详细说明这些条目的细节。

  6. 下一步是构建和部署路由到 Karaf。我们将把这个组装作为一个包构建并部署到 Karaf 中,并在etc文件夹中提供配置文件。配置文件的形式为PID-name.cfg,其内容为 Java 风格的属性。

    要构建我们的示例项目,请执行mvn install命令。部署将需要以下两个命令:

    karaf@root()> install -s mvn:commons-lang/commons-lang/2.6
    karaf@root()> install -s mvn:com.packt/msf/1.0.0-SNAPSHOT
    
    
  7. 我们对 Camel Router 的 ManagedServiceFactory 实现现在已准备好使用。最后一步是为我们的路由实例创建配置。考虑以下etc/com.packt.hellofactory-test1.cfg文件的示例配置:

    HELLO_GREETING=hello 
    HELLO_NAME=Jamie
    

    还要考虑以下etc/com.packt.hellofactory-test2.cfg文件的条目:

    HELLO_GREETING=hi 
    HELLO_NAME=Laura 
    

    在运行适当的命令后,这些示例配置将产生以下输出:

    karaf@root()> camel:route-list 
     Context        Route          Status 
     -------        -----          ------ 
     helloContext   Hello Jamie    Started 
     helloContext   Hello Laura    Started 
    karaf@root()> log:display
    hello Jamie
    hi Laura
    
    

现在我们已经回顾了构建 Camel Router 的 ManagedServiceFactory 实现的高级流程,让我们深入探讨它是如何以及为什么能工作的。

工作原理…

实现了 ManagedServiceFactory 接口的包连接到配置管理器服务的构建和配置包实例的能力。在我们的示例项目中,我们使用此功能根据提供的配置创建新的路由实例。

每个服务(路由)实例由一个称为 PID 的工厂配置表示。当给定的 PID 更新时,配置管理器将调用工厂的更新方法。如果传入新的 PID,则创建新实例;如果 PID 存在,则更新其配置。以下图表突出了在 Karaf 中部署的组件的高级视图:

如何工作…

当我们将 Camel 组件和示例项目部署到 Karaf 中时,我们将通过 Blueprint 将 MSF 包和配置管理器连接起来。在底层,MSF 包由三个类组成:HelloConstantsHelloDispatcherHelloFactory

HelloFactory类实现了 ManagedServiceFactory 接口。在我们的示例项目中,我们重写了getName()updated(String pid, Dictionary dict)deleted(String pid)方法。然后,我们提供了初始化和销毁方法来清理我们的路由器。最后,我们提供了设置器来连接我们的 PID、bundleContextcamelContext对象。让我们更详细地看看HelloFactory类中的核心接口实现。考虑以下代码:

@Override
public String getName() {
    return configurationPid;
}

我们重写了getName()方法,返回我们的配置 PID,如前述代码所示。考虑以下代码:

@Override
public void updated(String pid, Dictionary dict) 
        throws ConfigurationException {
        log.info("updated " + pid + " with " + dict.toString());
        HelloDispatcher engine = null;

        if (dispatchEngines.containsKey(pid)) {
            engine = dispatchEngines.get(pid);

            if (engine != null) {
                destroyEngine(engine);
            }
            dispatchEngines.remove(pid);
        }

在前述代码中的updated方法中,我们首先设置我们的HelloDispatcher引擎。维护一个<PID, HelloDispatcher>映射,我们使用它来内部跟踪我们的 Camel 路由器。如果我们有分配给调度器的 PID 条目,那么我们安全地销毁现有的引擎,以便可以构建一个新的。现在,考虑以下代码:

    //Verify dictionary contents before applying them to Hello
if (dict.get(HelloConstants.HELLO_GREETING) != null && !StringUtils.isEmpty(dict.get(HelloConstants.HELLO_GREETING).toString())) {
  log.info("HELLO_GREETING set to " + dict.get(HelloConstants.HELLO_GREETING));
} else {
  throw new IllegalArgumentException("Missing HELLO_GREETING");
}

if (dict.get(HelloConstants.HELLO_NAME) != null && !StringUtils.isEmpty(dict.get(HelloConstants.HELLO_NAME).toString())) {
  log.info("HELLO_NAME set to " + dict.get(HelloConstants.HELLO_NAME));
} else {
  throw new IllegalArgumentException("Missing HELLO_NAME");
}

然后,我们验证在配置管理器提供的属性字典中是否存在所需的配置条目。这些字典是在配置文件放置在 Karaf 的etc文件夹时构建的。请看以下代码:

    //Configuration was verified above, now create engine.
engine = new HelloDispatcher();
engine.setCamelContext(camelContext);
engine.setGreeting(dict.get(HelloConstants.HELLO_GREETING).toString());
engine.setName(dict.get(HelloConstants.HELLO_NAME).toString());

dispatchEngines.put(pid, engine);
log.debug("Start the engine...");
if (engine == null) {
    log.debug("Engine was null, check configuration.");
}
 engine.start();
}

然后,updated方法创建并配置一个新的HelloDispatcher对象,并开始操作它。现在,考虑以下代码:

@Override
public void deleted(String pid) {
    if (dispatchEngines.containsKey(pid)) {
        HelloDispatcher engine = dispatchEngines.get(pid);

        if (engine != null) {
            destroyEngine(engine);
    }
        dispatchEngines.remove(pid);
    }
    log.info("deleted " + pid);
}

当 PID 被移除时,deleted方法安全地清理所有当前正在执行的HelloDispatcher对象(我们的路由器)。

为了将HelloFactory对象集成到容器中,我们包含了initdestroy方法。init调用用于将 ManagedServiceFactory 接口与注册服务注册,并在 ManagedServiceFactory 接口包和 Configuration Admin 服务之间建立 ServiceTracker 连接(这是一个实用工具类,它简化了从服务注册表中处理服务引用的工作)。destroy调用的主要功能是通过从 registerService 安全注销包并关闭其ServiceTracker对象来清理。

HelloDispatcher对象实现了我们的 Camel 路由器。我们提供了启动和停止方法,用于将我们的 Camel 路由实例集成到现有的 Camel 上下文中。我们还提供了设置我们的参数和指定我们想要部署路由的 Camel 上下文的方法。最后,我们提供了一个机制,通过该机制我们的 Camel 路由将被构建。让我们更详细地看看HelloDispatcher对象中的路由构建器。如下代码所示:

protected RouteBuilder buildHelloRouter() throws Exception {

    return new RouteBuilder() {

        @Override
        public void configure() throws Exception {

 from("timer://helloTimer?fixedRate=true&period=10000").
 routeId("Hello " + name).
 log(greeting + " " + name);
    }
};

上述路由使用计时器组件每 10 秒生成一个事件,然后将消息发送到日志中。配置元素基于名称参数提供routeId,日志消息包含greetingname参数。

最后,HelloConstants是一个提供我们的配置参数名称常量的实用工具类。

ManagedServiceFactory 包到 Configuration Admin 的连接发生在我们的 Blueprint XML 文件内部。让我们更详细地看看这个描述符文件中的三个重要部分,如下所示:

<cm:property-placeholder persistent-id="com.packt.hellofactory" update-strategy="reload">
  <cm:default-properties>
 <cm:property name="com.packt.hellofactory.pid" value="com.packt.hellofactory"/>
  </cm:default-properties>
</cm:property-placeholder>

cm命名空间用于设置配置管理。考虑以下代码:

<camelContext id="helloContext"autoStartup="true">
</camelContext>

我们的 Camel 上下文作为我们的路由容器被创建。这个上下文将与我们引入系统的每个路由实例共享。考虑以下代码:

<bean id="apacheKarafCookbook" class="com.packt.HelloFactory" init-method="init" destroy-method="destroy">
  <property name="bundleContext"
 ref="blueprintBundleContext"/>
  <property name="configurationPid"
 value="${com.packt.hellofactory.pid}"/>
  <property name="camelContext" ref="helloContext"/>
</bean>

最后,我们将HelloFactory对象连接到 Blueprint 上下文、Configuration Admin 服务和我们的共享 Camel 上下文中。我们还连接了我们的工厂的initdestroy方法。以下图表说明了三个 Camel 路由实例,每个实例根据其配置产生不同的消息:

如何工作…

一旦我们的 ManagedServiceFactory 包、Blueprint 文件和 Configuration Admin 服务连接在一起并启动,它现在将接受配置并实例化路由。在 Karaf 中,你可以将pid-name.cfg格式的配置文件添加到etc文件夹中。例如,在我们的示例项目中,我们的配置文件命名为com.packt.hellofactory-test1.cfgcom.packt.hellofactory-test2.cfg等等。

参见

  • 关于 ActiveMQ 和 CXF 的更多信息,请参阅第三章,使用 Apache ActiveMQ 部署消息代理和第四章,使用 Pax Web 托管 Web 服务器

第三章:使用 Apache ActiveMQ 部署消息代理

在本章中,我们将介绍以下食谱:

  • 将 Apache ActiveMQ 模块安装到 Apache Karaf 中

  • 使用 ActiveMQ 查询命令

  • 使用 ActiveMQ 列表命令

  • 使用 ActiveMQ dstat 命令

  • 使用 ActiveMQ 清理命令

  • 使用 JMS 连接工厂命令

  • 使用 JMS 发送命令

  • 使用 JMS 浏览命令

  • 使用 Apache Karaf 配置和部署主/从代理

  • 使用 Apache Karaf 配置和部署代理网络

简介

ActiveMQ 是在 TCP、SSL、HTTP(s)、VM 和 STOMP 等多种方式中实现 JMS 消息传递的常用框架之一,这是允许组件间通信的许多方法之一。ActiveMQ 提供了许多好处,从处理数据突发到提供故障转移和扩展。在本章中,我们将介绍在 Karaf 环境中实现嵌入式 ActiveMQ 代理的原因和方法。我们还将探讨在不同部署拓扑下如何管理代理。

在我们开始之前,我们应该讨论何时使用嵌入式代理部署策略与独立部署。这和学会如何做一样重要,因为嵌入式代理可能会像糟糕的架构一样快速地将系统拖垮。在许多情况下,最初的考虑是嵌入式 ActiveMQ 更容易部署,并且会使消息传递更快。虽然这一点有一定的真实性,但在大多数情况下,这种好处并不超过成本。允许 ActiveMQ 共享 JVM 资源将在高负载系统中引起竞争。此外,如果 ActiveMQ 出现问题导致其失败,它很可能会对 Karaf 实例产生直接影响,进而导致应用程序失败,反之亦然。嵌入式 ActiveMQ 为企业应用程序带来了很多价值;只是确保它被用于正确的目的。

一个常见的嵌入式解决方案是用于地理位置分离的客户端/服务器应用程序。一个例子是客户端应用程序位于跨越 WAN 的 Karaf 实例中,可能会发生间歇性中断。那么,拥有一个本地的嵌入式 ActiveMQ 以允许客户端在代理与服务器重新建立通信时继续运行可能是个好主意。以下图示展示了这一点:

简介

小贴士

仔细考虑是否真的需要一个嵌入式代理。

将 Apache ActiveMQ 模块安装到 Apache Karaf 中

将 ActiveMQ 代理安装到 Karaf 实例中几乎不需要任何努力。本食谱将向您展示如何轻松地集成并运行 ActiveMQ。为了在 Karaf 中安装 ActiveMQ 代理,我们首先需要添加功能 URL。

如何操作...

开箱即用,Karaf 并未安装 ActiveMQ。但无需担心,Karaf 使得安装它变得非常简单。步骤如下:

  1. 首先,我们需要通过添加我们期望使用的版本的仓库来安装 XML 功能。这可以通过以下命令完成:

    feature:repo-add activemq <version>
    
    

    以下截图展示了如何启动:

    如何操作…

  2. 现在我们有了这些功能可用,我们可以使用以下命令列出它们:

    feature:list | grep activemq
    
    

    截图应该看起来像以下这样:

    如何操作…

有几种方法可以只安装我们需要的部分。如果你的应用程序连接到任何 ActiveMQ 实例,那么在 OSGi 环境中你只需要连接客户端 API。只需安装activemq-client功能;这提供了实例化连接和发送或接收消息所需的必要类。你可以使用以下命令完成此操作:

feature:install activemq-client

但是,由于我们在 Karaf 实例中嵌入代理,我们需要运行以下命令:

feature:install activemq-broker

注意以下截图中的activemq-clientactivemqactivemq-brokeractivemq-web-console功能已被安装。这由第三列中的'X'表示。

如何操作…

它是如何工作的…

如果我们安装 ActiveMQ 代理,其实例化配置在文件etc/org.apache.activemq.server-default.cfg中。该文件在启动嵌入式代理时被读取,并将使用引用的activemq.xml文件来定义 ActiveMQ 代理的初始化方式。该文件还配置了各种其他 JVM 参数。默认配置文件看起来像以下代码:

broker-name=amq-broker
data=${karaf.data}/${broker-name}
config=${karaf.base}/etc/activemq.xml

将配置设置放在.cfg文件中的一个大好处是,对配置文件中值的任何更改都会导致代理停止并重新启动,从而合并更改。对activemq.xml文件的更改需要手动停止和启动代理。一个很好的配置文件更新是添加如下内存设置:

jvmMemory=50
storage=100gb
tempStorage=10gb

然后,使用以下代码在activemq.xml文件中添加属性:

<memoryUsage>
   <memoryUsage percentOfJvmHeap="${jvmMemory}"/>
</memoryUsage>
<storeUsage>
   <storeUsage limit="${storage}"/>
</storeUsage>
<tempUsage>
   <tempUsage limit="${tempStorage}"/>
</tempUsage>

小贴士

你能设置的配置属性越多,管理运行时就越容易。

使用 ActiveMQ 查询命令

嵌入式 ActiveMQ 的一个方便的特性是能够运行命令来监控代理活动。query命令提供了关于代理的基本信息。

准备就绪

为了开始,我们需要按照前一个菜谱中概述的步骤安装activemq-broker功能。一旦安装完成,我们就可以使用以下命令:

activemq

输入命令后,按Tab键。这将列出所有可用的 ActiveMQ 命令,如下面的截图所示:

准备就绪

现在,仅仅运行命令相当无聊;在一个空的代理中看不到太多东西。

让我们加载数据,以便我们可以看到正在发生的事情。

如何操作…

现在,让我们开始通过代理发送一些数据,以便使这个例子更有趣、更真实。这个菜谱有一些辅助类我们可以用来加载数据到代理中:

  1. 在“将 Apache ActiveMQ 模块安装到 Apache Karaf”的示例代码中运行mvn clean install命令。然后,你可以运行发布者对嵌入的 ActiveMQ。如果没有从activemq.xml文件更改默认设置,那么发布者代码中的默认设置将工作。我们可以使用以下命令运行发布者:

    java -cp target/openwire-example-0.1-SNAPSHOT.jar example.Publisher
    
    

    这将向代理发送 10001 条消息,包括关闭消息。

  2. 现在我们可以看到测试队列中有 10001 条消息等待,让我们继续使用我们的Listener来消费它们,如下面的命令所示:

    java -cp target/openwire-example-0.1-SNAPSHOT.jar example.Listener
    
    

    这将在队列上创建一个消费者并拉取消息。

  3. 能够一眼看到队列是件好事,但如果我们需要看到更多关于队列的信息怎么办?在许多情况下,我们需要看到队列消耗了多少内存,或者消费者数量,或者任何数量的参数。一个好的方法是使用查询命令,如下所示:

    activemq:query -QQueue=test
    
    

这将列出队列的属性,就像你可能在 JConsole 中找到的那样,JConsole 是 Java 提供的一个 JMX 监控工具。这是一种使用脚本监控队列属性或用于持续集成测试监控结果的好方法。我们可以查看在调试时在 JConsole 中经常查看的一些属性。我们可以看到队列的深度是 10001。这可以在 JConsole 的QueueSize参数下找到。但如果我们想看到一个告诉我们队列健康状况的参数,让我们看看MemoryPercentageUsage参数。

我们可以在 JConsole 中看到当前值是3,如下面的屏幕截图所示:

如何做…

要查看这些参数的一个更简单的方法是使用 Karaf 控制台命令activemq:query。这使我们免去了打开 JConsole 并输入长远程进程 URL 的麻烦:service:jmx:rmi://localhost:44444/jndi/rmi://localhost:1099/karaf-root

然后,我们可以输入用户名和密码。或者,我们可以简单地使用activemq:query –QQueue=<queue_name>命令,通过 JMX 查询队列统计信息。下面的屏幕截图显示了控制台将显示的内容:

如何做…

由于我们是命令行高手,我们可以使用|命令来 grep 我们感兴趣的信息。这在上面的屏幕截图中有展示:

如何做…

注意到MemoryPercentUsage(如前一个屏幕截图所示)的统计数据包含在内,以及所有其他基于内存的属性。另一个有用的过滤器是计数。这将显示enqueuedequeueinflightproducerconsumer等的所有计数。

查询命令有许多选项。这些可以通过使用--help参数查看,如下所示:

activemq:query --help

它是如何工作的…

队列大小列显示了当前在队列中等待被消费的消息数量。出队列是已被监听器消费的总消息数量。如果我们再次运行发布者代码,我们会看到队列大小再次增加到 10001,但出队值没有变化,这表明队列中有 10001 条消息。

大多数 ActiveMQ 命令都是通过 MBeans 或 JMX 提供的。许多相同的功能也通过 JConsole 提供。

相关内容

  • 使用 ActiveMQ list 命令的配方

  • 使用 ActiveMQ dstat 命令的配方

使用 ActiveMQ list 命令

list命令可以用来列出在 Karaf 容器内当前运行的代理。

准备中

为了开始,我们需要安装activemq-broker功能。参考以下屏幕截图中的activemq:query命令:

准备中

如何操作...

要列出在这个 Karaf 实例内部当前运行的嵌入式所有代理,我们可以简单地运行以下命令:

karaf@root()> activemq:list

这将列出以下嵌入式代理名称:

brokerName = amq-broker

工作原理...

这个activemq命令将在 JMX 连接上调用JmxMBeansUtil.getAllBrokers类,并检索任何当前正在运行的代理的名称。

相关内容

  • 使用 ActiveMQ 查询命令的配方

  • 使用 ActiveMQ dstat 命令的配方

使用 ActiveMQ dstat 命令

dstat命令是一种方便的方法,可以一眼看到队列消息统计信息。它将列出队列,包括队列大小、生产者和消费者数量、入队和出队消息数量以及内存使用百分比。

准备中

为了开始,我们需要安装activemq-broker功能。

小贴士

如果你运行过任何之前的配方,一个好的方法是停止 Karaf,删除数据目录,然后重新启动。这将清理之前运行的所有数据。记住在重新启动后重新安装activemq-broker功能。

例如,让我们运行activemq:dstat命令,如下面的屏幕截图所示。

准备中

并不太令人印象深刻。没有数据,这些命令可能会相当无聊。在之前的屏幕截图中,我们可以看到此刻我们定义了一个队列,没有消息,没有生产者,也没有消费者。所以,让我们加载数据来看看发生了什么。

如何操作...

我们需要开始通过代理发送一些数据,以便使这个过程更加有趣和真实。这个配方有一些辅助类我们可以用来加载数据到代理。这可以按以下方式完成:

  1. 将 Apache ActiveMQ 模块安装到 Apache Karaf配方下的示例代码中运行mvn clean install命令。然后,你可以对嵌入式 ActiveMQ 运行发布者。

  2. 如果没有从 activemq.xml 文件更改默认值,则发布者代码中的默认值将工作。我们可以使用以下命令运行发布者:

    java -cp target/openwire-example-0.1-SNAPSHOT.jar example.Publisher
    
    

    这将向代理发送 10001 条消息。现在我们可以查看 dstat 代理,看看是否有任何变化。以下屏幕截图显示了 dstat 代理的结果:

    如何做…

    使用 dstat 命令,我们可以看到发布者已经在测试队列上放置了 10001 条消息,并且还有几条通知消息已经入队。

    提示

    通知消息是特定于 ActiveMQ 的消息,旨在通知您事件。

  3. 既然我们可以看到我们有 10001 条消息在测试队列中等待,让我们继续使用我们的监听器使用以下命令消费它们:

    java -cp target/openwire-example-0.1-SNAPSHOT.jar example.Listener
    
    

    这将在队列上创建一个消费者并拉取消息。我们可以通过 dstat 命令看到我们队列上发生的事情。这将在以下屏幕截图中显示:

    如何做…

工作原理…

dstat 命令使用 ServerInvocationHandler 接口通过 JMX 连接创建 QueueViewMBean 接口或 TopicViewMBean 接口的实例。这些提供了关于队列或主题的统计信息。统计信息简单地列在控制台上,以提供队列/主题的高级快照。

参见

  • 使用 ActiveMQ 清除命令 菜谱

  • 使用 ActiveMQ 查询命令 菜谱

使用 ActiveMQ 清除命令

清除数据的实用命令是 purge 命令。它可以与通配符结合使用,以清除大量队列。

准备工作

以类似 使用 ActiveMQ dstat 命令 菜谱中的方式安装 activemq-broker 功能。

如何做…

在我们能够清除数据之前,我们首先需要加载数据。我们可以使用前面菜谱中提供的示例代码加载数据。步骤如下:

  1. 我们可以使用以下命令再次运行发布者:

    java -cp target/openwire-example-0.1-SNAPSHOT.jar example.Publisher
    
    
  2. 使用前面菜谱中的 dstat 命令,我们可以在以下屏幕截图中看到我们已将 10001 条消息加载到嵌入式代理的测试队列中:如何做…

    purge 命令将使用通配符和 SQL92 语法从任意数量的队列中删除数据。

  3. 现在,使用我们在 将 Apache ActiveMQ 模块安装到 Apache Karaf 脚本中看到的发布者向队列中添加消息。为了乐趣,运行几次。现在,使用以下命令查询队列,看看我们有多少条消息:

    activemq:query -QQueue=* --view EnqueueCount
    
    

    注意

    使用 * 字符作为队列名称将返回所有队列的 EnqueueCount 值,但在这个例子中,我们只有测试队列。

    我们可以看到,经过两次运行,我们的 EnqueueCount 值为 20002,如下面的屏幕截图所示:

    如何做…

  4. 在运行发布者几次之后,现在我们可以看到队列中有成千上万的消息。现在运行以下 purge 命令针对测试队列:

    activemq:purge test
    
    

    如何操作…

    purge 命令会移除给定队列中的所有消息。让我们重新运行以下 query 命令,看看队列是否被清空:

    activemq:query -QQueue=* --view EnqueueCount
    
    

它是如何工作的…

ActiveMQ 代码库中的 PurgeCommand 类用于在所选队列中清除消息。如果没有定义队列,如以下命令所示,它将清除所有队列中的所有消息:

activemq:purge

我们可以使用类似于 JMSPriority>2,MyHeader='Foo' 的 AMQ 特定语言,或者我们可以使用 SQL-92 语法 (JMSPriority>2) AND (MyHeader='Foo') 来选择要删除的特定消息。

参见

  • 使用 ActiveMQ 列表命令 的配方

  • 使用 ActiveMQ 查询命令 的配方

使用 JMS 连接工厂命令

现在我们已经有一个嵌入式代理,并且查看了一些用于查看代理属性和统计信息的命令,让我们看看如何使用 JMS 命令与代理交互。在这个配方中,我们将通过创建连接工厂来查看创建和与代理交互的命令。

准备就绪

为了我们能够控制连接工厂并向嵌入式代理发送消息,我们首先需要使用以下 JMS 功能命令安装所需的命令:

feature:install jms

使用 Tab 键,我们将看到可用于创建、发送、浏览消息以及创建连接工厂的 JMS 命令列表。这些命令在以下屏幕截图中列出:

准备就绪

首先,让我们为我们的嵌入式代理创建一个连接工厂。我们使用 jms:create 命令来完成这项工作。

小贴士

请务必参考 --help 命令以获取所需和可选参数。

如何操作…

让我们首先创建一个连接工厂,这样我们就可以发送消息了。创建连接工厂只需要以下命令:

jms:create [options] name

因此,对于这个例子,以下命令将用于将类型为 -t activemq 的连接到 URL -u tcp://localhost:61616

jms:create -t activemq --url tcp://localhost:61616 cookbook

它是如何工作的…

这将使用由 –t 选项定义的 ActiveMQConnectionFactory 创建连接工厂(另一个选项是 WebsphereMQ)。我们可以通过在命令行中执行 la 命令来验证我们的工厂确实已设置。你的最后一条条目应该类似于以下命令行输出:

51 | Active   |  80 | 0.0.0 | connectionfactory-cookbook.xml

注意

注意,XML 文件命名的模式是 connectionfactory-<name>.xml。在我们的例子中,我们使用了名称 'cookbook'。如果不是最后一个条目,或者你没有看到它,尝试使用 la | grep cookbook 命令。

在 Karaf 实例的deploy目录中创建了一个名为connectionfactory-cookbook.xml的文件,如下面的代码所示。这是一个 Blueprint XML 文件,它将实例化连接工厂、池化连接工厂、资源管理器和事务管理器。

<?xml version="1.0" encoding="UTF-8"?>
<blueprint >

    <bean id="activemqConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
        <property name="brokerURL" value="tcp://localhost:61616" />
    </bean>

    <bean id="pooledConnectionFactory" class="org.apache.activemq.pool.PooledConnectionFactory">
        <property name="maxConnections" value="8" />
        <property name="connectionFactory" ref="activemqConnectionFactory" />
    </bean>

    <bean id="resourceManager" class="org.apache.activemq.pool.ActiveMQResourceManager" init-method="recoverResource">
        <property name="transactionManager" ref="transactionManager" />
        <property name="connectionFactory" ref="activemqConnectionFactory" />
        <property name="resourceName" value="activemq.localhost" />
    </bean>

    <reference id="transactionManager" interface="javax.transaction.TransactionManager" />

    <service ref="pooledConnectionFactory" interface="javax.jms.ConnectionFactory">
        <service-properties>
            <entry key="name" value="cookbook" />
            <entry key="osgi.jndi.service.name" value="/jms/cookbook" />
        </service-properties>
    </service>

</blueprint>

池化连接工厂以 JNDI 名称jms/cookbook添加到服务中。这里需要注意的重要信息是用于定义基于该 URL 的连接的-u选项,这也基于该 URL 创建了连接池。这对于系统资源管理方面是一个重要的输入。

相关内容

  • 使用 JMS 发送命令菜谱

使用 JMS 发送命令

在调试或测试代码时,能够向特定队列发送消息是非常方便的。这可以通过在 Karaf 的命令控制台中使用 JMS 子 shell 来完成。

准备工作

确保 JMS 功能已安装并可用。为了通过 JMS 使用大多数命令,我们需要创建一个连接工厂(参见使用 JMS 连接工厂命令菜谱)。

如何操作…

首先,让我们使用info命令确保服务到位。连接工厂可以通过指定的名称cookbook或 JNDI 服务名称jms/cookbook来引用,如下面的命令所示:

karaf@root()> jms:info jms/cookbook

Property | Value 
-------------------
product  | ActiveMQ
version  | 5.9.0

现在我们已经验证了 JMS 连接工厂,我们可以使用它向代理发送消息。这可以通过以下命令完成:

karaf@root()> jms:send jms/cookbook cookbookQueue "the recipes are sweet"

这将使用我们连接工厂的 JNDI 名称jms/cookbookcookbookQueue发送一条消息,the recipes are sweet

工作原理…

send命令将包装命令行中的字符串变量,并将其放入 JMS 消息中,然后将消息发送到指定的队列。如果您查看命令行,有三个参数,如下所示:

  • 连接到连接工厂的 JNDI 引用

  • 我们要发送消息到的队列名称

  • 消息体

相关内容

使用 JMS 浏览命令

现在我们已经有一个嵌入式代理并且查看了一些用于查看代理属性和统计信息的命令,让我们看看如何使用命令与代理交互。在这个菜谱中,我们将查看浏览消息的命令。

准备工作

为了让我们能够控制连接工厂并向嵌入式代理发送消息,我们首先需要使用以下 JMS 功能命令安装命令,就像我们在前面的菜谱中所做的那样:

feature:install jms

首先,让我们为我们的嵌入式代理创建一个连接工厂。我们使用jms:create命令来完成此操作。

小贴士

一定要参考--help命令以获取所需或可选参数。

如何操作…

命令的实际核心是浏览队列中消息的能力。以下是一个示例 browse 命令:

karaf@root()> jms:browse jms/cookbook cookbookQueue

这允许您查看消息内容、持久性(投递模式)、过期时间、ID、回复到值、目的地等。这是在任何给定时间监控队列中内容的快速方法。前面 browse 命令的输出如下:

ID:mbp.pk5001z-55253-1393261878969-4:2:1:1:1 | the recipes are sweet | UTF-8   |   |   | Persistent    | queue://cookbookQueue | Never      | 4        | false       |         | Mon Feb 24 14:55:21 MST 2014

它是如何工作的…

在队列中浏览消息很简单。命令行上的参数告诉 browse 命令使用哪个连接工厂以及要浏览哪个队列。

使用 Apache Karaf 配置和部署主/从代理

在这个菜谱中,我们将设置和部署两个带有嵌入式 ActiveMQ 的 Karaf 实例,配置为主/从配置。这用于消息系统中的高可用性。这将允许系统在活动实例失败的情况下继续运行。

准备工作

首先,我们需要启动两个带有嵌入式代理的 Karaf 实例。如果我们在这两台不同的机器上执行此操作,实际上会更简单,因为我们不会在两台机器上使用默认值时遇到端口冲突。记住,如果你决定在单台机器上运行此部署,Karaf 中嵌入的 Jetty 和 ActiveMQ 中的一个实例需要更改其端口。

如何操作…

由于我们只能假设我们有一台机器可以工作,我们将介绍如何在单台机器上运行两个 Karaf 实例。我们可以通过将 Karaf 的 .zip.tar 文件解压到新目录或直接复制粘贴当前实例到新目录来创建第二个实例。现在,我们必须更改其中一个实例的端口号设置。这可以通过以下步骤完成:

  1. 打开 <karaf-home>/etc/ 文件夹中的 org.apache.karaf.management.cfg 文件,找到以下代码行:

    #
    # Port number for RMI registry connection
    #
    rmiRegistryPort = 1099
    
  2. 将端口更改为未使用的端口,例如 1096。然后,如以下代码所示更改 RMI 服务器连接端口:

    #
    # Port number for RMI server connection
    #
    rmiServerPort = 44444
    
  3. 我们可以将其更改为类似 44446 的值。现在,我们可以启动第二个实例而不会发生端口冲突。但我们的工作还没有完成。我们仍然需要配置 ActiveMQ 实例以实现故障转移。

对 Karaf 的实例 1 和实例 2 进行以下更改:

  1. 打开 <karaf-home>/etc/ 文件夹中的 org.apache.activemq.server-default.cfg 文件,将以下代码更改为磁盘上的硬编码位置:

    data=${karaf.data}/${broker-name}
    

    一个示例可能如下所示:

    data=/Users/default/data
    
  2. 打开 <karaf-home>/etc/ 文件夹中的 jetty.xml 文件,将 jetty.port 的值更改为类似 8186 的值,如下面的代码所示:

    <Property name="jetty.port" default="8186" />
    
  3. 打开 <karaf-home>/etc/ 文件夹中的 org.apache.karaf.shell.cfg 文件,将 sshPort 的值更改为类似 8106 的值,如下面的代码所示:

    sshPort = 8106
    sshHost = 0.0.0.0
    
  4. 由于我们在同一台机器上运行两个 ActiveMQ 实例,因此将第二个 ActiveMQ 实例的端口更改为不同的端口会更完整,尽管这不是必需的,因为端口只有在 ActiveMQ 锁定文件位置后才会分配(在这个配置中,这一步是可选的;然而,在单台机器上的代理网络配置中更为重要,因为两个实例都是活动的)。可以按照以下方式完成:

    <transportConnector name="openwire" uri="tcp://0.0.0.0:61617?maximumConnections=1000&amp;wireFormat.maxFrameSize=104857600"/>
    
  5. 在更改配置文件后,保存它们并启动 Karaf 的两个实例。在实例 1 上按照以下方式安装 activemq-broker 功能:

    karaf> feature:repo-add activemq 5.9.0
    karaf> feature:install activemq-broker
    
    
  6. 我们将看到嵌入式 ActiveMQ 实例启动没有问题。现在在实例 2 上做同样的事情,并查看 data/log 文件夹中的 karaf.log 文件中的实例 2 的日志。

  7. 在实例 2 的日志文件中搜索短语 could not be locked。我们将看到以下输出行:

    2014-02-23 20:37:12,472 | INFO  | ctivemq.server]) | SharedFileLocker                 | 103 - org.apache.activemq.activemq-osgi - 5.9.0 | Database /Users/default/data/kahadb/lock is locked... waiting 10 seconds for the database to be unlocked. Reason: java.io.IOException: File '/Users/default/data/kahadb/lock' could not be locked.
    
  8. 这表明第二个嵌入式代理实例处于等待状态,并未完全实例化。要查看实例 2 的启动和初始化,请在实例 1 上按 Ctrl + D(以关闭实例)。如果您正在跟踪日志文件或刷新您的查看器中的日志文件,您将看到以下输出:

    2014-02-23 20:40:02,909 | INFO  | ctivemq.server]) | TransportServerThreadSupport     | 103 - org.apache.activemq.activemq-osgi - 5.9.0 | Listening for connections at: tcp:// mbp.pk5001z:61616?maximumConnections=1000&wireFormat.maxFrameSize=104857600
    2014-02-23 20:40:02,909 | INFO  | ctivemq.server]) | TransportConnector               | 103 - org.apache.activemq.activemq-osgi - 5.9.0 | Connector openwire started
    2014-02-23 20:40:02,909 | INFO  | ctivemq.server]) | BrokerService                    | 103 - org.apache.activemq.activemq-osgi - 5.9.0 | Apache ActiveMQ 5.9.0 (cookbook-broker, ID: mbp.pk5001z-53046-1393213202778-0:1) started
    

这表明实例 2 上的从代理已启动并初始化了连接。

如何工作…

ActiveMQ 通过几个选项为我们提供了高可用性。在本食谱的情况下,我们使用基于文件的持久性(kahadb)。这允许我们将两个实例指向相同的数据文件位置以建立主实例或从实例。第一个启动的实例将锁定文件位置,然后完成初始化。另一个实例将查看相同的文件位置,并看到已经建立了锁定。然后,它将记录 IOException 并在默认的 10 秒后再次尝试。

如何工作…

之前的图示显示主 ActiveMQ 实例已从文件位置建立锁定。它已实例化了在 etc 文件夹下由传输连接元素定义的 activemq.xml 文件中配置的所有连接。这允许客户端现在连接到主实例。一旦实例关闭,这些连接将被终止。

假设客户端已配置为使用以下故障转移协议连接到从实例:

failover:(tcp://master:61616,tcp://slave:61617)?options

一旦辅助代理完成初始化,客户端机器将自动重新连接到从机器。这在下图中得到演示:

如何工作…

在这一点上,我们可以重新启动原始主实例(实例 1),它现在将等待获取文件锁定。这使我们能够在不重新启动实例或恢复数据的情况下获得主/从设置。

参见

  • 使用 Apache Karaf 配置和部署代理网络 食谱

使用 Apache Karaf 配置和部署代理网络

许多时候,单个系统可能不足以处理应用程序的负载。在这种情况下,需要扩展以在多个系统之间平衡负载。我们可以使用代理网络NoB)配置中的嵌入式 ActiveMQ 实例来扩展应用程序。

这与主/从配置不同,因为我们会有两个活动实例,而不是一个活动实例和一个被动实例。

准备工作

通过遵循上一个配方中的相同初始设置,我们需要运行两个 Karaf 实例。我们可以在单个机器或多个机器上证明这个概念。对于这个配方,我们再次将向您展示如何在同一台机器上设置两个实例,这需要为不同的 Karaf 实例使用不同的端口。

确保两个实例的默认设置就绪。

如何操作…

请按照以下步骤配置和部署一个代理网络配置:

  1. 打开<karaf-home>/etc/文件夹下的org.apache.activemq.server-defaults.cfg文件,并确保以下数据值已设置:

    data=${karaf.data}/${broker-name}
    

    这将告诉嵌入式 ActiveMQ 实例使用前面提到的参数定义的本地数据文件夹。

  2. 将实例 1 的代理名称选项更改为recipe-3-broker-1,并将实例 2 的代理名称选项更改为recipe-3-broker-2。因此,文件中的值应如下代码所示:

    Instance 1:
    broker-name=recipe-3-broker-1
    Instance 2:
    broker-name=recipe-3-broker-2
    

    这样,在通过 JMX 查看时,可以很容易地区分不同的代理。karaf.data属性是基于 Karaf 位置设置的系统属性。

  3. 对于这个配方,让我们处理实例 2。首先,我们必须修改etc/activemq.xml文件,以使其具有一个网络连接器,将其与实例连接起来。以下是需要添加的 XML 代码:

    <networkConnectors>
      <networkConnector  uri="static:(tcp://0.0.0.0:61616)"
                         duplex="true"
                         userName="karaf"
                         password="karaf"/>
    </networkConnectors>
    

    注意 URI 具有静态协议,通过 TCP 连接到实例 1 的端口(在这种情况下,实例 1 的默认端口被保留为61616)。我们打开双工,允许通过单个管道进行双向通信,并传递用户名和密码。

  4. 为了避免端口冲突,我们需要将实例 2 的传输连接器端口更改为除了61616之外的其他端口,如下所示:

    <transportConnector name="openwire" uri="tcp://0.0.0.0:61617?
    maximumConnections=1000&wireFormat.maxFrameSize=104857600"/>
    
  5. 再做一次更改,我们就可以开始测试了。我们需要更改位于etc文件夹下的jetty.xml文件中的端口。默认端口是8181,但由于实例 1 正在使用该端口,我们需要将实例 2 的端口更改为其他端口。对于这个配方,我使用了8182,如下代码所示:

    <Set name="port">
       <Property name="jetty.port" default="8182" />
    </Set>
    
  6. 我们现在可以启动这两个实例。如果我们监控实例 2 的日志,我们将看到以下输出:

    Establishing network connection from vm://recipe-3-broker-1?async=false&network=true to tcp://0.0.0.0:61616
    
    vm:// recipe-3-broker-1 started
    
    Network connection between vm:// recipe-3-broker-1#24 and tcp://mbp.pk5001z/192.168.0.97:61616@55254 (recipe-3-broker-2) has been established.
    

    日志条目表明实例 2 已找到实例 1,并在两者之间建立了连接。

它是如何工作的…

经纪人网络配置可用于两种不同的拓扑:垂直或水平。首先,让我们看看水平扩展,这更多的是为了扩展目的。这种拓扑通常用于为系统提供极高的吞吐量。主要思想是不要增加消息必须通过的数量,以便被您的应用程序消费。相反,它旨在平衡多个服务器之间的负载。注意在以下图中,每个实例都连接了两个客户端机器,使得每个经纪人/Karaf 对只需处理两个客户端的消息。

小贴士

每个经纪人实例都有自己的消息存储,并且不被认为是高可用拓扑。

即使客户端可以从一个客户端切换到另一个客户端,持久化数据在实例之间并不共享。这意味着如果经纪人实例中的一个实例死亡,那么在该存储中的挂起消息将不会处理,直到经纪人重新启动,尽管客户端已经切换到活动的经纪人实例。看看以下图示:

它是如何工作的…

拥有一个经纪人网络的好处是能够将消息转发给空闲的客户。如果一个消息被发送到本地的经纪人 1,但所有客户都在忙碌,那么 ActiveMQ 会将消息转发到经纪人 2上的任何空闲客户。这被认为是水平扩展。

还有更多…

还有垂直扩展的概念。但这有一个完全不同的用例。当使用垂直扩展时,通常不是性能问题。在大多数情况下,垂直扩展用于 WAN 上的通信问题。以下图示中概述了一个很好的例子:

还有更多…

在之前的图中,使用本地嵌入的 ActiveMQ 以避免在洛杉矶和达拉斯之间的连接丢失时导致应用程序停机。因此,本地应用程序将直接与本地实例通信,无需担心 WAN 是否在线。然后,本地 ActiveMQ 将负责建立和维护与后端服务的连接。

注意

垂直扩展会有性能成本。

垂直扩展会强制进行两个经纪人的跳转。因此,您必须确定从需求中哪个更重要:性能或稳定性。

参见

  • 配置部署Apache Karaf 的 master/slave 经纪人食谱

第四章:使用 Pax Web 托管 Web 服务器

在本章中,我们将涵盖以下主题:

  • 在 Apache Karaf 中安装 Pax 模块

  • 在 Apache Karaf 中安装扩展 Http 服务

  • 在 Apache Karaf 中配置部署的 Pax Web 模块

  • 构建一个可在 Apache Karaf 中托管的 Http 服务项目

  • 在 Apache Karaf 中使用白板模式构建 Http 服务

  • 使用 Apache Karaf 自定义 HttpContext 构建应用程序

  • 在 Apache Karaf 中构建一个标准 Web 项目以托管

  • 在 Apache Karaf 中配置 Web 应用程序的安全

  • 在 Apache Karaf 中将 Web 项目绑定到特定主机

  • 使用 Apache Karaf 构建带有 Servlet 3.0 注解的 Web 应用程序

  • 使用 Apache Karaf 创建 CDI Web 应用程序

简介

本章解释了如何通过 Pax Web 增强您的 Apache Karaf 以作为 Web 容器。OPS4j Pax Web 项目是一个 OSGi R4 Http 服务和 Web 应用程序(参考第一百二十八章OSGi 企业发布 4)的实现。它通过更好的 servlet、过滤器、监听器、错误页面、JavaServer Pages(JSPs)等支持扩展 Http 服务,以支持最新的 Java Servlet 规范。

Karaf 的标准功能包含一组选项,用于以不同风味安装 Pax Web。有如下选项:

  • 基本 Http 服务

  • 支持白板模式和 JSPs 的增强 Http 服务

  • 支持Web 应用程序存档WAR)和Web 应用程序包WAB)文件的完整 Web 容器配置

小贴士

对 Apache Karaf 和 Pax Web 结合使用有更深入理解兴趣的读者应咨询 OPS4j 社区。您将找到更多关于 Pax Web 通用用法和 Apache Karaf 特长的示例。

在 Apache Karaf 中安装 Pax 模块

要开始使用 Http 服务,您需要安装前面提到的功能之一。本食谱将指导您安装 Pax Web 的不同扩展以及它是如何工作的。

如何操作…

要安装基本 Http 服务,启动您的 Apache Karaf 服务器,并通过控制台使用以下命令安装 Http 服务功能:

karaf@root()> feature:install http

它是如何工作的…

安装此功能后,使用la命令列出捆绑包。这将向您显示以下附加捆绑包到您的 Karaf 实例:

78 | Active   |  30 | 2.2.0           | Apache ServiceMix :: Specs :: Activation API 1.4
79 | Active   |  30 | 1.0            | Servlet 3.0
80 | Active   |  30 | 1.4.4          | JavaMail API (compat)
81 | Active   |  30 | 1.1.1          | geronimo-jta_1.1_spec
82 | Active   |  30 | 1.0.1          | Annotation 1.1
83 | Active   |  30 | 1.1            | Java Authentication SPI for Containers
84 | Active   |  30 | 8.1.14         | Jetty :: Aggregate :: All Server
85 | Active   |  30 | 1.6.0          | OPS4J Pax Swissbox :: OSGi Core
86 | Active   |  30 | 1.6.0          | OPS4J Pax Swissbox :: Optional JCL
87 | Active   |  20 | 3.16.0         | Apache XBean OSGI Bundle Utilities
88 | Active   |  20 | 3.16.0         | Apache XBean :: ASM 4 shaded (repackaged)
89 | Active   |  20 | 3.16           | Apache XBean :: Reflect
90 | Active   |  20 | 3.16.0         | Apache XBean :: Finder shaded (repackaged)
91 | Active   |  30 | 3.1.0          | OPS4J Pax Web - API
92 | Active   |  30 | 3.1.0          | OPS4J Pax Web - Service SPI
93 | Active   |  30 | 3.1.0          | OPS4J Pax Web - Runtime
94 | Active   |  30 | 3.1.0          | OPS4J Pax Web - Jetty
95 | Active   |  30 | 3.0.1          | Apache Karaf :: HTTP :: Core
96 | Active   |  30 | 3.0.1          | Apache Karaf :: HTTP :: Commands

当然,它将安装所需的 Jetty 服务器以服务 Web 内容,以及四个基本的 Pax Web 捆绑包,以拥有最小的 Http 服务。这四个捆绑包包含 API、服务 SPI、运行时和 Jetty 服务器包装器,它负责启动底层的 Jetty 实例。所有这些由 HTTP 功能安装的捆绑包为您提供了使用 Http 服务的一些可能方式,但没有其他。这种场景通常足够用于运行简单的 servlet 和 Felix Web 控制台。

在此基本安装之上,Karaf 已经提供了一个简单的命令来检查当前安装的 servlet。它将为您提供 servlet 和注册别名的概览。以下是该命令:

karaf@root()> http:list
ID | Servlet | Servlet-Name | State | Alias | Url
-------------------------------------------------

参考以下内容

  • 在 Apache Karaf 中托管 Http Service 项目的构建配方

在 Apache Karaf 中安装扩展的 Http 服务

通常,仅仅使用基本的 Http 服务已经不再足够,尤其是在需要服务 JSP 或完整的 Web 应用程序时。因此,需要一个更好用的容器。

如何操作...

  1. 要安装 HTTP 白板功能,启动您的 Apache Karaf 服务器,并通过控制台使用以下命令安装 HTTP 白板功能:

    karaf@root()> feature:install http-whiteboard
    
    
  2. 使用以下命令将 Apache Karaf 服务器转换为一个功能齐全的 Web 容器:

    karaf@root()> feature:install war
    
    

它是如何工作的...

白板功能安装了另外两个 Pax Web bundle。这两个 bundle 为您提供了 JSP 和白板支持。这两个 bundle 在以下命令行输出中显示:

97 | Active   |  30 | 3.1.0       | OPS4J Pax Web - Jsp Support
98 | Active   |  30 | 3.1.0       | OPS4J Pax Web - Extender - Whiteboard

这使得 Pax Web 能够部署并服务于现在可用的 WebContainer 接口注册的 JSP。该接口是标准化 Http Service 的扩展。白板扩展器是另一种在 OSGi 中注册服务的方法。

小贴士

更多关于白板模式的详细信息可以在www.osgi.org/wiki/uploads/Links/whiteboard.pdf找到。

使用 WAR 功能,Pax Web 的安装就完成了。这包括用于安装 OSGi WAB 文件的 WAR 扩展器,以及负责将 WAR 存档转换为 WAB 文件的 Pax URL WAR 处理器。以下列出了这些附加的 bundle:

99  | Active   |  30 | 3.1.0       | OPS4J Pax Web - Extender - WAR
100 | Active   |  30 | 3.1.0       | OPS4J Pax Web - FileInstall Deployer
101 | Active   |  30 | 1.4.2       | OPS4J Pax Url - war
102 | Active   |  30 | 1.4.2       | OPS4J Pax Url - Commons
103 | Active   |  30 | 1.6.0       | OPS4J Pax Swissbox :: Bnd Utils
104 | Active   |  30 | 1.6.0       | OPS4J Pax Swissbox :: Property
105 | Active   |  30 | 1.43.0      | aQute Bundle Tool Library
106 | Active   |  30 | 3.0.1       | Apache Karaf :: Web :: Core
107 | Active   |  30 | 3.0.1       | Apache Karaf :: Web :: Commands

Karaf WAR 功能带来了另一个命令;web:*命令有助于分析已安装 WAR 的状态。以下是一个示例命令:

karaf@root()> web:list
ID | State | Web-State | Level | Web-ContextPath | Name
-------------------------------------------------------

这些命令也有助于控制 web bundle 的状态。考虑以下命令:

karaf@root()> web:
web:list     web:start    web:stop

web:list命令显示已安装 WAR 文件列表,并列出状态和Web-ContextPath

参考以下内容

配置在 Apache Karaf 中部署的 Pax Web 模块

Pax Web 使用 Jetty 作为底层的 Web 容器。OSGi Http Service 规范定义了一组用于配置 Http 服务的参数。除了这些标准配置参数之外,Pax Web 特定的参数也是可配置的。在这些配置参数之上,还可以根据进一步的需求配置 Jetty 本身。

如何操作...

Http Service 的配置是通过配置管理服务完成的。在安装 Http Service 期间,配置也会为服务 PID org.ops4j.pax.web 设置,如下代码片段所示:

javax.servlet.context.tempdir = ${karaf.data}/pax-web-jsp
org.ops4j.pax.web.config.file = ${karaf.home}/etc/jetty.xml
org.osgi.service.http.port = 8181

它是如何工作的...

这个基本的配置集定义了 Jetty 服务器监听的 HTTP 端口,创建 JSP 服务器文件的 temp 目录,以及可选的 jetty.xml 文件的位置,用于增强 Jetty 配置。

小贴士

文档说明如何配置 jetty.xml 文件,可以在 wiki.eclipse.org/Jetty/Reference/jetty.xml 找到。请确保您还查看使用 Pax Web 运行 Jetty 的特性,请参阅 ops4j1.jira.com/wiki/display/paxweb/Advanced+Jetty+Configuration

还有更多...

此外,可以将所有配置参数的配置文件放置在 etc 文件夹中。文件名类似于服务 PID,后缀为 .cfg。以下是从 org.ops4j.pax.web.cfg 配置文件中的摘录。完整的配置文件和 jetty.xml 文件可以在 github.com/jgoodyear/ApacheKarafCookbook/blob/master/chapter4/chapter4-recipe2 找到。

org.osgi.service.http.enabled = true
org.osgi.service.http.port = 8181
org.osgi.service.http.connector.name = default
org.osgi.service.http.useNIO = true
org.osgi.service.http.secure.enabled = true
org.osgi.service.http.port.secure = 8443
org.osgi.service.http.secure.connector.name = secureDefault
javax.servlet.context.tempdir = ${karaf.data}/pax-web-jsp
org.ops4j.pax.web.config.file=${karaf.base}/etc/jetty.xml
…

以下代码配置了额外的 JSP 参数:

org.ops4j.pax.web.jsp.scratch.dir = 
org.ops4j.pax.web.jsp.check.interval = 300
…
org.ops4j.pax.web.jsp.precompilation = false
…

以下是为 NCSA 日志格式设置的配置:

org.ops4j.pax.web.log.ncsa.enabled = false
org.ops4j.pax.web.log.ncsa.format = yyyy_mm_dd.request.log
org.ops4j.pax.web.log.ncsa.retaindays = 90
org.ops4j.pax.web.log.ncsa.append = true
org.ops4j.pax.web.log.ncsa.extended = true
org.ops4j.pax.web.log.ncsa.dispatch = false
org.ops4j.pax.web.log.ncsa.logtimezone = GMT
org.ops4j.pax.web.log.ncsa.directory = 
org.ops4j.pax.web.log.ncsa.latency = false
org.ops4j.pax.web.log.ncsa.cookies = false
org.ops4j.pax.web.log.ncsa.server = false

注意

关于 NCSA 日志格式的详细信息,请参阅 en.wikipedia.org/wiki/Common_Log_Format

以下配置用于拥有不同的虚拟主机和连接器:

org.ops4j.pax.web.default.virtualhosts = 
org.ops4j.pax.web.default.connectors = 

更多详细信息,请参阅 《在 Apache Karaf 中将 Web 项目绑定到特定主机》 菜谱。

它是如何工作的...

此配置摘录显示了 Pax Web 内部已使用的默认配置。如果需要,可以在 Karaf shell 中设置一些或所有这些配置参数,或者将它们放置在 etc 文件夹中的 org.ops4j.pax.web.cfg 文件中。以下是如何通过 shell 命令设置 HTTP 端口的快速示例:

karaf@root()> config:edit org.ops4j.pax.web
karaf@root()> config:property-set org.osgi.service.http.port 8080
karaf@root()> config:update

关于如何使用 config 命令的更多详细信息,请参阅 《学习 Apache Karaf》,作者:Jamie Goodyear, Johan Edstrom, 和 Heath KeslerPackt Publishing

当引用外部目录时,最好使用 karaf.data 环境变量,例如,通过切换 org.ops4j.pax.web.log.ncsa.enabled 选项到 true 来启用 NCSA 记录器。还需要将目录配置到特定的文件夹中,如下代码行所述:

org.ops4j.pax.web.log.ncsa.directory = ${karaf.data}/ncsa-log/

对于 JSP 编译的 scratch 目录也是如此——最好配置如下:

org.ops4j.pax.web.jsp.scratch.dir = ${karaf.data}/jsp-compile

构建 Http Service 项目以在 Apache Karaf 中托管

仅使用 Http 服务构建 Web 应用程序也意味着将服务减少到仅 servlet,因为 Http 服务只支持 servlet,不支持其他 Web 元素,如过滤器 JSP。在这个简化的集合中,仍然可以构建现代 Web 应用程序。例如,只需要一些 JavaScript 代码和一个生成 JSON 的 servlet 就可以构建一个现代 Web 应用程序。使用这些元素,结合 OSGi,你就可以得到 µ 服务完美的混合体。本食谱和以下食谱的重点仅在于 Http 服务的使用;因此,不要期望创建一个花哨的 Web 应用程序。完成之后,你将能够构建一个像下面截图所示的单一 servlet 应用程序:

构建一个 Http 服务项目以在 Apache Karaf 中托管

准备工作

要预配置 Apache Karaf 中安装的 Http 服务,请参阅 在 Apache Karaf 中安装 Pax 模块 食谱。源代码可以在 github.com/jgoodyear/ApacheKarafCookbook/tree/master/chapter4/chapter4-recipe3 找到。

由于本书页面有限,最好总是查看完整的源代码,以避免样板代码。这就是为什么在这里只引用了本食谱中的关键部分。本食谱中使用的示例应用程序可以使用以下命令安装和启动:

install -s mvn:com.packt/chapter4-recipe3/1.0.0-SNAPSHOT/jar

如何操作…

由于我们在这个食谱中使用 Http 服务,我们需要获取 HttpService 服务。为此,通常使用 ServiceTracker 机制。步骤如下:

  1. 第一步是引用 Http 服务。在我们的示例应用程序的激活器中,创建了一个 ServiceTracker 对象,该对象引用创建此跟踪器的包,并声明要跟踪的服务类。第三个参数是 ServiceTrackerCustomizer 接口的实现。ServiceTracker 本身提供了一个默认实现,因此我们直接使用它。所有这些都在包激活器代码中完成,如下所示:

    public void start(BundleContext bc) throws Exception {
      bundleContext = bc;
      tracker = new ServiceTracker<HttpService, HttpService>(bc, HttpService.class, this);
      tracker.open();
    }
    
  2. 第二步是注册 servlet 作为服务。当服务可用时,ServiceTracker 将启动,并调用包激活器的 addingService 方法。在这里,与 servlet 注册一起创建了 HttpContext 函数和 servlet 的 init 参数,如下面的代码所示:

    HttpContext httpContext = httpService.createDefaultHttpContext();
    Dictionary<String, Object> initParams = new Hashtable<String, Object>();
    
  3. 在此之后,我们就可以使用 Http 服务注册 servlet 作为服务了。这通过以下简单的 API 调用完成:

    httpService.registerServlet("/hello",
    new HelloServlet(), initParams, httpContext);
    

Http 服务也支持资源的注册,例如图像和 CSS 文件。Felix 网络控制台基于这两种机制。

为了注册资源,Http 服务 API 提供了另一种注册方法,如下面的代码所示:

httpService.registerResources("/images", "/images", httpContext);

图片应包含在images文件夹中的 JAR 文件中(如下面的截图所示),在注册图片别名时使用:

如何操作…

小贴士

相应的 API 可以在www.osgi.org/javadoc/r4v42/org/osgi/service/http/HttpService.html找到。

要使用此图片,创建一个使用此图片的第二个 servlet,并使用不同的别名注册它,如下面的代码所示:

httpService.registerServlet("/hello/logo", new HelloImage(), initParams, httpContext);

servlet 将图片作为根上下文中的资源引用,如下面的代码所示:

out.println("<img src='/images/karaf-logo.png' border='0'/>");

它是如何工作的...

Http Service 配置并注册 HelloServlet 到底层 Jetty 服务器。从这一点开始,它被视为一个经典的 servlet 并相应地处理。资源需要额外的处理,因为 ResourceServlet 需要了解 OSGi 类加载,因此需要知道在哪里查找包的类路径中的资源。

参见

  • Http Service 仅提供基本的 HTTP 服务,例如服务 servlet 和资源。如果您想服务动态内容,如 JSPs,则需要 Pax Web 扩展 OSGi Http Service——Pax Web web 容器。这可以与 Whiteboard 扩展一起使用,并在 Apache Karaf 中使用Building a Http Service with the Whiteboard pattern配方。

在 Apache Karaf 中使用 Whiteboard 模式构建 Http Service

Whiteboard 模式是一种注册 servlet、资源、JSP 或过滤器的方法。使用 Whiteboard 扩展,服务的注册方式与上一个配方相反。bundle activator 不需要等待 Http Service 出现;它只是注册 servlet 和其他资源作为服务,同时 Whiteboard 扩展拾取这些服务并确保一切准备就绪以供服务。使用 Whiteboard 功能,可以使用底层 Jetty 服务器提供的所有 Web 技术,因为我们不再受限于受限的 Http Service 接口。本配方将通过使用两种不同的技术来指导您完成这个过程;首先,标准方式,即通过 bundle activator,其次,通过 Blueprint。

如何操作…

  1. 第一步是注册一个 servlet 作为服务。首先,通过 bundle 上下文使用 activator 注册您的 servlet。由于 servlet 被注册为 OSGi 服务,别名需要放在服务属性中。这些属性被解释并部分用于 servlet 的init参数。

    Hashtable<String, String> props = new Hashtable<String, String>();
    props.put("alias", "/whiteboard");
    

    servlet 本身被注册为任何常规的 OSGi 服务,如下面的代码所示:

    servletReg = bundleContext.registerService(Servlet.class, new HelloServlet(), props);
    
  2. 第二步是注册资源。由于这不是一个标准化的 OSGi Http Service,需要一个特殊的 Pax Web ResourceMapping类来将资源注册为服务,如下面的代码所示:

    DefaultResourceMapping resourceMapping = new DefaultResourceMapping();
    resourceMapping.setAlias("/whiteboardresources");
    resourceMapping.setPath("/images");
    resourcesReg = bundleContext.registerService(ResourceMapping.class, resourceMapping, null);
    

    注册后,可以通过 servlet 使用这些资源,如下所示:

    out.println("<img src='/whiteboardresources/karaf-logo.png' border='0'/>");
    

    将 Web 组件注册为服务的这种模式会重复出现。

  3. 下一步是使用 Blueprint 注册 servlet。与使用激活器进行注册不同,Whiteboard 模式也允许您使用其他注册服务的方式,例如 Blueprint 或 DS。使用 Blueprint XML 文件,现在只需配置服务的连接,而不是创建激活器中包含的样板代码:

    <service id="filteredServletService" ref="filteredServlet" interface="javax.servlet.Servlet">
      <service-properties>
        <entry key="alias" value="/filtered"/>
      </service-properties>
      <bean class="com.packt.HelloServlet"/>
    </service>
    

    这将 servlet 注册为别名/filtered,该别名用作匹配 filter 的 URL 模式。

  4. 最后一步是使用 Blueprint 注册 filter。注册 filter 与注册 servlet 一样简单。使用 Blueprint 进行此类注册使用的样板代码更少。这可以通过以下代码完成:

    <service id="servletFilterService" interface="javax.servlet.Filter">
      <service-properties>
        <entry key="urlPatterns" value="/filtered/*"/>
      </service-properties>
      <bean class="com.packt.ServletFilter"/>
    </service>
    

    因此,当导航到/filtered时,可以看到 Hello Servlet 的过滤调用,如下面的截图所示:

    如何做…

Whiteboard 方法还有更多内容。只需注册服务即可配置完整的 Web 应用程序。

注册错误页面

例如,注册DefaultErrorPageMapping可以轻松配置错误页面。相应的类由 Pax Web 提供,并通过 Apache Karaf Http Whiteboard 功能可用。考虑以下代码:

<service interface=" org.ops4j.pax.web.extender.whiteboard.ErrorPageMapping">
  <bean class=" org.ops4j.pax.web.extender.whiteboard.runtime.DefaultErrorPageMapping">
    <property name="error" value="java.lang.Exception"/>
    <property name="location" value="/uncaughtException.html"/>
  </bean>
</service>

此错误页面映射定义了在容器中抛出的任何异常都将导致服务uncaughtException.html页面,如下面的截图所示:

注册错误页面

定义错误页面映射

同时,可以通过额外的映射注册自定义 404 错误代码处理页面,该映射再次作为服务注册,如下面的代码所示:

<!-- 404 mapping -->
<service id="errorPageMapping" interface="org.ops4j.pax.web.extender.whiteboard.ErrorPageMapping">
  <bean class="org.ops4j.pax.web.extender.whiteboard.runtime.DefaultErrorPageMapping">
    <property name="error" value="404"/>
    <property name="location" value="/404.html"/>
  </bean>
</service>

Pax Web 提供的DefaultErrorPageMapping只需要 HTTP 错误代码和自定义错误代码页面的位置。

注册欢迎页面

为欢迎页面配置和注册服务的配置和注册与之前的注册一样简单。考虑以下代码:

<service id="welcomeFileService" interface="org.ops4j.pax.web.extender.whiteboard.WelcomeFileMapping">
  <bean class="org.ops4j.pax.web.extender.whiteboard.runtime.DefaultWelcomeFileMapping">
    <property name="redirect" value="true" />
    <property name="welcomeFiles">
      <array>
        <value>index.html</value>
        <value>welcome.html</value>
      </array>
    </property>
  </bean>
</service>

Whiteboard 扩展器 bundle 提供了WelcomeFileMapping的默认实现。以下截图显示了欢迎页面:

注册欢迎页面

注册 JSP

JSP 的注册与所有之前的食谱一样简单。您只需将DefaultJspMapping与相应的 URL 模式注册即可。完成此操作后,您就可以立即提供 JSP 服务。考虑以下代码:

<service id="jspMapping" interface="org.ops4j.pax.web.extender.whiteboard.JspMapping">
  <bean class="org.ops4j.pax.web.extender.whiteboard.runtime.DefaultJspMapping">
    <property name="urlPatterns">
      <array>
        <value type="java.lang.String">/jsp</value>
      </array>
    </property>
  </bean>
</service>

当您准备好提供 JSP 时,将出现以下屏幕:

注册 JSPs

它是如何工作的…

现在可用的 Whiteboard 扩展器启动一个 ServiceListener,该 Listener 注册任何传入的新服务,如 servlet、filter 和 JSP,并将它们交给标准 Http Service 或扩展的 Http Service(WebContainer 接口)。

使用直接服务方法只有一个缺点——所有注册的 servlet 服务都使用相同的ServletContextPath。这是因为注册的 Http Service servlet 忽略了注册 servlet 时使用专门的 ServletContextPath 的方法,因此绑定到了/,并为 servlet 添加了额外的 URL 路径。例如,这个配方中的前两个 servlet 注册为/whiteboard/whiteboard/logo,其中 ServletContextPath 是/。为了区分不同的 ServletContextPaths,需要一个 WAB。

所有之前的示例都有一个共同点:它们都是在同一个 bundle 中注册的。为了将 servlet 与过滤器分开,你需要确保使用相同的 HttpContext;这将在后面的配方中处理。

使用 Apache Karaf 构建具有自定义 HttpContext 的应用程序

所有之前的配方都涵盖了如何注册 servlet 和过滤器,并且都是在绑定到默认 HttpContext 的同一个个 bundle 中完成的。如果没有定义其他 HttpContext,则在注册 servlet 或资源时创建 DefaultHttpContext。在这个配方中,我们将使用自定义的 HttpContext。

准备中

由于这个配方是之前配方的特殊化,你可以在在 Apache Karaf 中使用 Whiteboard 模式构建 Http 服务的配方中找到它的源代码。像往常一样,这个配方要求成功安装http-whiteboard功能;如何实现这一点在在 Apache Karaf 中安装扩展 Http 服务的配方中有解释。这个配方中的源代码被缩减到了重要的部分。完整的源代码可以在github.com/jgoodyear/ApacheKarafCookbook/tree/master/chapter4/chapter4-recipe4找到。

如何操作…

  1. 第一步是定义属性。与 servlet 类似,注册 HttpContext 需要为 HttpContext 配置一个名称,如下面的代码所示:

    props = new Hashtable<String, String>();
    props.put(ExtenderConstants.PROPERTY_HTTP_CONTEXT_ID, "forbidden");
    
  2. 下一步是注册,HttpContext。这将使用专门的 ID forbidden 将自定义的 WhiteboardContext 注册为 HttpContext。这样,其他服务就可以选择这个 HttpContext。这在上面的代码中有所展示:

    httpContextReg = bundleContext.registerService(HttpContext.class, new WhiteboardContext(), props);
    
  3. 下一步是注册 servlet。使用这个 HttpContext 的 servlet 通过使用相同的 HttpContext ID 进行注册来引用 HttpContext。考虑以下代码:

    props = new Hashtable<String, String>();
    props.put(ExtenderConstants.PROPERTY_ALIAS, "/forbidden");
    props.put(ExtenderConstants.PROPERTY_HTTP_CONTEXT_ID, "forbidden");
    forbiddenServletReg = bundleContext.registerService(Servlet.class, new HelloServlet(), props);
    

    自定义 HttpContext 对handleSecurity方法返回false。因此,请求将返回 401 错误作为 HTTP 返回代码。考虑以下代码:

    public boolean handleSecurity(final HttpServletRequest request,final HttpServletResponse response) throws IOException {
      // Forbidden access!
      return false;
    }
    

下面的截图显示了 HTTP 错误窗口:

如何操作…

更多内容…

到目前为止,servlet、过滤器和其他资源的注册总是来自同一个包。这在 OSGi 世界中是如何适应的?这不是将它们分开更有意义吗?实际上确实如此,但这并不容易,OSGi 规范也没有要求它必须可行。使用 Pax Web 3,可以实现这一点,但需要一些特殊处理。首先,它需要一个共享 HttpContext(其描述和如何使用它将在下一步骤中找到)。以下是一个组件图,以帮助您了解设置:

还有更多…

可以这样做:

  1. 第一步是注册 SharedWebContainerContext。对于共享 HttpContext,拥有 SharedWebContainerContext(这是一个专门的 HttpContext)是至关重要的。在此阶段,WebContainer 接口是我们的朋友,并通过提供创建 SharedWebContainerContext 的方法来帮助我们,即 getDefaultSharedHttpContext 方法。这将在以下代码中描述:

    WebContainer service = (WebContainer) context.getService(serviceReference);
    HttpContext httpContext = service.getDefaultSharedHttpContext();
    

    这个新创建的 SharedWebContainerContext 需要注册为服务,因为来自其他包的过滤器需要引用它。考虑以下代码:

    props = new Hashtable<String, String>();
    props.put(ExtenderConstants.PROPERTY_HTTP_CONTEXT_ID, "shared");
    httpContextReg = context.registerService(HttpContext.class,httpContext, props);
    
  2. 下一步是注册与共享上下文关联的 servlet。由于我们想要添加来自不同包的过滤器,我们需要先添加一个 servlet。我们将使用以下代码来完成:

    props = new Hashtable<String, String>();
    props.put( ExtenderConstants.PROPERTY_ALIAS, "/extfilter" );
    props.put("servlet-name", "FilteredServlet");
    props.put(ExtenderConstants.PROPERTY_HTTP_CONTEXT_ID, "shared");
    registerService = context.registerService( Servlet.class, new HelloServlet(), props );
    

    在这一点上,我们的配方中已经设置了第一个包,我们需要注意只包含过滤器的第二个包。

  3. 下一步是引用共享 HttpContext。我们需要首先获取 shared HttpContext,尽管这次我们不创建一个新的,我们只需要引用第一个包注册的那个。

    查找与给定 LDAP 过滤器 (httpContext.id=shared) 匹配的 HttpContext 服务的服务引用。同时,参见步骤 1,我们在那里使用此属性注册了服务。考虑以下代码:

    Collection<ServiceReference<HttpContext>> serviceReferences = context.getServiceReferences(HttpContext.class, "(httpContext.id=shared)");
    
    if (serviceReferences.size() > 1) {
      throw new RuntimeException("should only be one http shared context");
    }
    
    HttpContext httpContext = context.getService(serviceReferences.iterator().next());
    

    从这个服务引用中,我们获取到 HttpContext 实例,在我们的例子中,此时是 SharedWebContainerContext。

  4. 下一步是注册共享上下文的过滤器。从现在开始,这相当简单。servlet 过滤器的注册是以通常的方式进行。

    Dictionary<String, String> props;
    props = new Hashtable<String, String>();
    props.put("pattern", ".*");
    props.put(ExtenderConstants.PROPERTY_HTTP_CONTEXT_ID, "shared");
    
    service.registerFilter(new ServletFilter(), new String[] { "/*" }, null, props, httpContext);
    

    创建注册所需的属性,并将过滤器与 WebContainer 服务注册。重要的是属性中包含对正确 HttpContext 的引用,此时为 shared。最重要的是,它需要实际上将过滤器与已预注册的 HttpContext 注册。

小贴士

这难道不能更简单吗?

Pax Web 4 的尚未发布版本有助于共享上下文。它将特别有助于使用 Whiteboard 模式。自从 Karaf 3(使用 Pax Web 3)发布以来,Pax Web 的 features.xml 文件在 Pax Web 发布周期中维护和发布。这将有助于仅升级此功能,而 Karaf 本身可能停留在另一个版本上。

在 Apache Karaf 中托管标准 Web 项目

由于构建标准 Web 应用程序和 WAR 并不在本书的范围内,本菜谱的重点在于将标准 WAR 转换为 WAB。

准备工作

本菜谱的示例代码可在 github.com/jgoodyear/ApacheKarafCookbook/tree/master/chapter4/chapter4-recipe6 找到。

如何做到这一点...

将标准 WAR 项目转换为 WAB 简单。所以,让我们看看 WAR 和 WAB 文件的特殊之处。为了简单起见,从现在起让我们称这个项目为 Web 项目。

与直接位于 JAR 文件中的类的标准包项目相比,Web 项目的类应包含在 WEB-INF 文件夹下的 classes 文件夹中。对于嵌入式库也是如此;在 Web 项目中,这些库需要放置在 WEB-INF 文件夹下的 lib 文件夹中。

注意

警告——仅当需要内部使用 JAR 文件时才应嵌入其他 JAR 文件。引用其他包应该是你的首选。

好消息是 maven-bundle-plugin 能够处理这种特殊放置的类和库。<_wab> 部分负责这一点。在这里,你定义你的 Web 应用程序文件夹的基本路径。由于这是一个 Maven 项目,Web 应用程序路径位于 src/main 文件夹中的 webapp 文件夹。考虑以下代码:

<plugin>
  <groupId>org.apache.felix</groupId>
  <artifactId>maven-bundle-plugin</artifactId>
  <version>2.4.0</version>
  <extensions>true</extensions>
  <configuration>
    <instructions>
 <_wab>src/main/webapp</_wab>
 <Web-ContextPath>packt-sample</Web-ContextPath>
    </instructions>
  </configuration>
</plugin>

由于需要为 Web 应用程序包有一个 Web-ContextPath 清单条目,因此在本菜谱中将其设置为 packt-sample

它是如何工作的...

WAR 功能安装了 Pax Web War 扩展器。WAR 扩展器等待包含 Web-ContextPath 标头的包,并将扫描此包中的 web.xmljetty-web.xml 配置文件。它还负责注解的 servlet 类。对于每个 WAR 文件,将有一个唯一的 servlet 上下文,其路径由 Web-ContextPath 清单头中定义。这与通过 Http 服务(带或不带 Whiteboard 扩展器)注册 servlet 不同,那里一切都关于别名。

还有更多...

之前创建的 WAB 文件唯一的缺点是它不再是 WAR 文件了。由于 POM 文件声明生成的包类型为 bundle,因此该工件被打包为 JAR 文件。这无疑会在需要在 Apache Karaf 或非 OSGi 容器上运行的 WAR 文件中引起问题。

要使这生效,项目的 Maven POM 文件需要做出调整。首先,将打包设置为 WAR。这将使用 maven-war-plugin 打包此包,而 WAR 文件不再是 WAB 文件,因为它缺少有效的 OSGi 清单。

必须结合两个插件来创建一个有效的 OSGi WAB 文件。为此,我们需要以下方式配置 maven-bundle-plugin

…
<executions>
  <execution>
    <id>bundle-manifest</id>
    <phase>process-classes</phase>
    <goals>
 <goal>manifest</goal>
    </goals>
  </execution>
</executions>
<configuration>
  <supportedProjectTypes>
    <supportedProjectType>jar</supportedProjectType>
    <supportedProjectType>bundle</supportedProjectType>
 <supportedProjectType>war</supportedProjectType>
  </supportedProjectTypes>
…

插件配置仅为生成 manifest 文件,由于打包类型为 WAR,因此需要配置maven-bundle-plugin以支持 WAR 格式作为有效的打包格式。

上述代码生成的 manifest 文件将显式地使用manifestFile属性合并到 WAR 包中,如下面的代码所示:

<configuration>
  <archive>
    <manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
  </archive>
</configuration>

使用这些配置,您将生成一个可以在 Apache Karaf 和 OSGi 外部运行的网络应用程序。

对于一个完整的示例,请查看github.com/jgoodyear/ApacheKarafCookbook/tree/master/chapter4/chapter4-recipe6中的源代码。在这里,您将找到一个特殊的 POM 文件,pom.war_xml

在 Apache Karaf 中配置网络应用程序的安全

此配方将处理如何构建启用身份验证的网络应用程序。由于我们在 Apache Karaf 中运行,并且 Karaf 默认支持Java 身份验证和授权服务JAAS),我们将向您展示运行 Karaf 上基本身份验证所需的一切。

准备工作

先决条件是安装 WAR 功能。此配方的源代码可在github.com/jgoodyear/ApacheKarafCookbook/tree/master/chapter4/chapter4-recipe7中找到。

如何操作...

  1. 第一步是在 Karaf 中配置用户/密码组合。让我们从用户配置开始。让我们确保/etc文件夹中的users.properties文件包含以下设置(这是默认配置):

    karaf = karaf,_g_:admingroup
    _g_\:admingroup = group,admin,manager,viewer
    

    users.properties文件遵循以下语法:

    USER=PASSWORD, ROLE1, ROLE2, …
    

    它也可以有以下的语法:

    USER=PASSWORD, _g_:GROUP, …
    _g_\:GROUP=ROLE1,ROLE2, …
    
  2. 下一步是配置 Jetty 中的 JAAS。Pax Web 使用的jetty.xml文件需要包含一个有效的认证领域。此领域需要配置为使用 JAAS。JAASLoginService类的配置如下所示:

    <Call name="addBean">
      <Arg>
     <New class="org.eclipse.jetty.plus.jaas.JAASLoginService">
          <Set name="name">default</Set>
          <Set name="loginModuleName">karaf</Set>
          <Set name="roleClassNames">
            <Array type="java.lang.String">
              <Item>org.apache.karaf.jaas.boot.principal.RolePrincipal
              </Item>
            </Array>
          </Set>
        </New>
      </Arg>
    

    访问 Karaf 领域的关键是定义loginModuleName值为karaf并定义正确的主体。由于我们在 Karaf 中运行,我们需要配置roleClassNames值为org.apache.karaf.jaas.boot.principal.RolePrincipal。这样,Jetty 和 Karaf 之间的安全握手配置就完成了。

  3. 最后一步是将网络应用程序配置为使用 JAAS。要在网络应用程序内部使用它,web.xml文件需要启用并配置安全设置,如下面的代码所示:

    <security-constraint>
      <web-resource-collection>
        <web-resource-name>Protected Area</web-resource-name>
        <description>Protect the Example Servlet</description>
     <url-pattern>/secured/*</url-pattern>
        <http-method>GET</http-method>
        <http-method>POST</http-method>
      </web-resource-collection>
      <auth-constraint>
        <description>Authorized Users Group</description>
     <role-name>admin</role-name>
      </auth-constraint>
    </security-constraint>
    

    此应用程序的安全设置是为了保护GETPOST方法的安全 URL。所需的角色名称必须是admin,正如步骤 1 中配置的那样。请考虑以下代码:

    <login-config>
      <auth-method>BASIC</auth-method>
      <realm-name>default</realm-name>
    </login-config>
    

    登录配置设置为BASIC类型,因此容器负责身份验证,并配置为使用默认域。此配置指向已定义的JAASLoginService类,其名称为default(在第 2 步中配置)。

如何工作…

在 Web 应用程序中进行身份验证的密钥是 Karaf 提供的 JAAS 安全机制。这只是一个配置问题,以确保所有部分都适当地连接。

当与 Karaf 和 JAAS 安全域一起工作时,了解当前可用的域可能会有所帮助。有一个 shell 命令可以列出所有域——jaas:realm-list命令。

此命令将显示可用的域,如下面的屏幕截图所示:

如何工作…

还有更多…

现在我们已经在 Web 应用程序中配置了安全设置,唯一缺少的是一种安全地访问应用程序的方法。为了启用 SSL,我们需要从配置在 Apache Karaf 中部署的 Pax Web 模块的配置中提取部分内容,并启用默认情况下关闭的某些值。所有这些操作都是在org.ops4j.pax.web.cfg配置文件中完成的,如下面的代码片段所示:

org.osgi.service.http.secure.enabled = true
org.osgi.service.http.port.secure = 8443
org.osgi.service.http.secure.connector.name = secureDefault

org.ops4j.pax.web.ssl.keystore = ${karaf.base}/etc/keystore/.keystore
org.ops4j.pax.web.ssl.password = password
org.ops4j.pax.web.ssl.keypassword = password
org.ops4j.pax.web.ssl.clientauthwanted = false
org.ops4j.pax.web.ssl.clientauthneeded = false

使用此配置,我们启用了对安全端口的监听,在我们的例子中是8443keystore值存储在相对于 Karaf 基本目录的位置。

如果客户端应发送证书进行身份验证而不是登录凭据,则将clientauthwantedclientauthneeded属性设置为true

保存配置后,它们将被 FileInstaller 包获取并应用于通过 Pax Web 运行的 Jetty 服务器。

为了实现完全工作的 SSL,你需要一个证书,因此我们需要创建一个。以下步骤将在 Linux 和 Mac 环境中工作,并且很可能会在 Windows 上工作:

  1. 第一步是keystore设置。首先,创建用于包含与 Karaf 一起使用的 keystore 的目录。它必须与org.ops4j.pax.web.ssl.keystore属性在org.ops4j.pax.web.cfg文件中配置的值相匹配。如下面的屏幕截图所示:还有更多…

    在系统 shell 中,而不是 Karaf shell 中,你需要使用工具来创建 SSH 密钥。我们将使用名为keytool的 Java 工具来创建密钥和证书。首先,创建用于签名证书的密钥。确保你在keystore目录(etc/keystore)中执行此操作。可以按照以下方式完成:

    keytool -genkey -keyalg RSA -validity 1024 -alias serverkey -keypass password -storepass password -keystore server.jks
    
    

    由于这是一个示例,我们使用简单的密码作为密码,但在生产环境中实际上不应该这样做。我们将使用server作为密码。请查看以下屏幕截图:

    还有更多…

    一旦您看到前面的截图,您就已经设置好可以在应用程序中使用 SSL 了。您需要将浏览器导航到配置了 SSL 端口的先前应用程序;在我们的例子中,它是8443。您的浏览器将抱怨来自未知来源的未签名证书。接受它后,您将收到来自您的认证 WAR 文件的登录提示。

  2. 下一步是导入客户端证书。也可以使用带有签名的客户端证书连接到服务器。客户端需要执行与服务器相同的事情——创建一个自签名证书。此证书需要导入到服务器的密钥库中,以便服务器知道要接受哪个证书。可以按照以下方式操作:

    keytool -import -trustcacerts -keystore server.jks -storepass password -alias clientkey -file client.cer
    
    

    此客户端证书需要通过 HTTP 客户端软件传输,这可能是一个浏览器或其他通信软件的方式。

在 Apache Karaf 中将 Web 项目绑定到特定主机

使用 Karaf 3 和 Pax Web 3,可以将 Web 应用程序绑定到特定的 HTTP 连接器。这是在相同服务器上分离内部和外部应用程序的可行解决方案。

准备工作

此菜谱的示例代码可在github.com/jgoodyear/ApacheKarafCookbook/tree/master/chapter4/chapter4-recipe8找到。在这里,您将在src/main/etc位置找到一个jetty.xml文件。它可以用来添加额外的连接器。有两个额外的菜谱可以展示如何使用标准 WAB 文件或 Whiteboard 扩展器。

如何操作...

首先,服务器需要配置以支持不同的 HTTP 连接器。因此,有必要通过编辑etc文件夹中找到的jetty.xml文件来配置 Jetty 服务器,如下面的代码所示:

<Call name="addConnector">
  <Arg>
    <New class="org.eclipse.jetty.server.nio.SelectChannelConnector">
      <Set name="host"><Property name="jetty.host" /></Set>
 <Set name="port"><Property name="jetty.port" default="8282"/></Set>
      <Set name="maxIdleTime">300000</Set>
      <Set name="Acceptors">2</Set>
      <Set name="statsOn">false</Set>
      <Set name="confidentialPort">8443</Set>
 <Set name="name">alternateConnector</Set>
      <Set name="lowResourcesConnections">20000</Set>
      <Set name="lowResourcesMaxIdleTime">5000</Set>
    </New>
  </Arg>

jetty.xml文件的更改仅在服务器重启时生效,并且不会被 FileInstaller 拾取并在运行时应用。

第二个连接器绑定到端口8282,并命名为alternateConnector。这将由绑定到此连接器的应用程序引用。

要这样做,WAB 文件需要两个额外的清单条目,如下所示:

<plugin>
  <groupId>org.apache.felix</groupId>
  <artifactId>maven-bundle-plugin</artifactId>
  <version>2.4.0</version>
  <extensions>true</extensions>
  <configuration>
    <instructions>
      <_wab>src/main/webapp</_wab>
      <Web-ContextPath>packt-sample</Web-ContextPath>
 <Web-Connectors>alternateConnector</Web-Connectors>
 <Web-VirtualHosts>localhost</Web-VirtualHosts>
    </instructions>
  </configuration>
</plugin>

它是如何工作的...

jetty.xml文件中配置的额外连接器被 Pax Web 解释并添加到 Jetty 中。通过特殊的清单条目,Web 应用程序包绑定到配置的 HTTP 连接器。连接器的数量以及绑定到它们的应用程序数量没有限制。

还有更多...

使用 Pax Web 3,因此使用 Apache Karaf 3,不仅可以将 WAB 文件绑定到 Web 连接器,还可以绑定到普通包。这需要一些特殊处理,因为没有Web-ContextPath清单头可以定义上下文路径。

实现这一点的最佳方式是使用 Whiteboard 扩展器。为此,我们需要注册一个专门的 HttpContext,如下面的代码所示:

//preparing special HTTP Context with HTTP Connector
Hashtable<String, String> props = new Hashtable<String, String>();
props.put( ExtenderConstants.PROPERTY_HTTP_CONTEXT_ID, "httpConnector" );
HashMap<String,String> contextMappingParams = new HashMap<String,String>();
contextMappingParams.put(ExtenderConstants.PROPERTY_HTTP_VIRTUAL_HOSTS, "localhost");
contextMappingParams.put(ExtenderConstants.PROPERTY_HTTP_CONNECTORS, "alternateConnector");
contextMappingReg = bundleContext.registerService( HttpContextMapping.class, new WhiteboardHttpContextMapping("httpConnector", "whiteboard", contextMappingParams), props );

注册的 servlet 只需使用以下 HttpContext:

props.put(ExtenderConstants.PROPERTY_ALIAS, "/connector");
props.put( ExtenderConstants.PROPERTY_HTTP_CONTEXT_ID, "httpConnector" );
servletReg = bundleContext.registerService(Servlet.class, new HelloServlet(), props);

就这些了。现在你需要使用 http://localhost:8282/whiteboard/connector URL 调用 servlet。

Servlet 使用别名/连接器进行注册,但 HttpContext 负责处理 Whiteboard 上下文路径。

使用 Apache Karaf 构建 Servlet 3.0 注解 Web 应用程序

使用 Servlet 3.0 API,可以只使用注解的 servlet 而不包含 web.xml 文件,或者至少在 web.xml 文件中省略应用程序的配置。

准备工作

如同往常,你可以在 GitHub 上的位置找到这个菜谱的代码:github.com/jgoodyear/ApacheKarafCookbook/tree/master/chapter4/chapter4-recipe9

如何操作...

与在 Apache Karaf 中托管的标准 Web 项目菜谱一样,我们只是构建另一个 WAB 包。但这一次,我们有一个只包含 welcome-file-list 方法定义和注解 servlet 的 web.xml 文件:

  1. 第一步是定义 web.xml 文件。可以按照以下方式完成:

    <web-app  
        xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
        version="3.0">
    
        <welcome-file-list>
          <welcome-file>welcome.html</welcome-file>
        </welcome-file-list>
    
    </web-app>
    
  2. 下一步是注解 servlet。servlet 简单,只包含常规的 servlet 代码和注解。考虑以下代码:

    @WebServlet (value="/test", name="test")
    public class HelloServlet extends HttpServlet {
    
  3. 下一步是注解过滤器。过滤器也包含声明它为过滤器的注解以及 init 参数,如下面的代码所示:

    @WebFilter(urlPatterns={"/*"}, servletNames = {"test"})
    public class ServletFilter implements Filter {
    

使用这个组件,你就可以在 Apache Karaf 中运行 Servlet 3.0 API 应用程序了。

还有更多...

Pax Web 还更进一步。只要包在其 MANIFEST.MF 文件中包含 Web-ContextPath,它就被视为 WAB 文件,因此 Pax Web WAR 扩展器会将其作为此类文件处理。

确保你的清单中有一个 Web-ContextPath,如下面的代码行所示:

<Web-ContextPath>packt-sample</Web-ContextPath>

接下来,确保你的包中包含注解的 servlet,如下面的代码行所示:

@WebServlet (value="/test", name="test")
public class HelloServlet extends HttpServlet {

将浏览器指向 http://localhost:8181/packt-sample/test 将返回所需的 Web 内容。

它是如何工作的...

Pax Web WAR 扩展器通常会查找包含在清单中的 Web-ContextPath 以及 web.xml 文件以发布 Web 存档。从 Pax Web 3.0 开始,WAR 扩展器也接受只包含其清单中的 Web-ContextPath 的包。

使用 Apache Karaf 创建 CDI Web 应用程序

现在,现代 Web 应用程序使用 上下文和依赖注入CDI)来连接应用程序。在 OSGi 的环境中,能够与 OSGi 服务一起工作会很好。Pax Web 与 Pax CDI 一起处理这种情况。

准备工作

除了安装 WAR 功能外,还需要安装 Pax CDI。使用以下命令安装它们:

feature:install war
feature:install pax-cdi-web-openwebbeans

或者,你可以使用以下 weld 命令:

feature:install pax-cdi-web-weld

这个菜谱的源代码可以在github.com/jgoodyear/ApacheKarafCookbook/tree/master/chapter4/chapter4-recipe10找到。

这个菜谱需要包和 Web 子模块。包子模块包含 OSGi 服务,而 Web 子模块包含 CDI Web 应用程序。

如何操作…

让我们从 Web 应用程序开始。web.xml文件可以是空的,也可以包含welcome-file-list方法的条目,因为该应用程序是一个 Servlet 3.0 应用程序。对于 CDI 应用程序,需要在类路径中有一个beans.xml定义。由于这是一个 Web 应用程序,beans.xml文件预计将位于WEB-INF文件夹中。对于我们的使用,在这个目录中保留一个空的beans.xml文件就足够了。

Servlet 需要使用@WebServlet注解来被 Pax Web 拾取。如下代码所示:

@WebServlet(urlPatterns = "/sample")
public class OsgiServiceServlet  extends HttpServlet {

Servlet 使用 OSGi 服务来检索简单的引语。这个服务通过注入引用,如下代码所示:

@Inject
@OsgiService (dynamic = true)
private MessageService messageService;

Pax CDI 的特色是@OsgiService注解。这个注解会将相应的 OSGi 服务连接到这个 Servlet。dynamic = true属性确保使用 OSGi 服务的动态性。

为了 Pax Web 和 Pax CDI 之间顺畅的交互,还需要一些额外的配置。这些配置在以下代码中的 POM 文件中处理:

<instructions>
  <_wab>src/main/webapp/</_wab>
  <Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName>
  <Bundle-Version>${project.version}</Bundle-Version>
  <Web-ContextPath>cdi-sample</Web-ContextPath>
  <Pax-ManagedBeans>WEB-INF/beans.xml</Pax-ManagedBeans>
 <Require-Capability>
 org.ops4j.pax.cdi.extension;
 </Require-Capability>
</instructions>

除了已知指令如<_wab><Web-ContextPath>之外,CDI 包还需要<Require-Capability>指令。有了这个指令,解析器会被告知 Web 包需要 CDI 包的能力。

它是如何工作的…

Pax Web 注册了 CDI Web 应用程序的所有 Servlet。这些 Servlet 通过 Pax CDI 等待上下文的初始化。服务由另一个包提供。只要这个服务不可用,Servlet 就不会启动。另一个重要部分是<Require-Capability>清单条目。它帮助解析器将 WAR 包连接到 Pax CDI 扩展包。这样,Pax CDI 扩展器就能够处理注入。

这个<Require-Capability>头也可以绑定到特定版本。为此,需要配置以下内容:

<Require-Capability>
  org.ops4j.pax.cdi.extension;
  filter:="(&amp;(extension=pax-cdi-extension)(version&gt;=${version;==;${pax.cdi.osgi.version.clean}})(!(version&gt;=${version;=+;${pax.cdi.osgi.version.clean}})))",osgi.extender; filter:="(osgi.extender=pax.cdi)"
</Require-Capability>

参见

第五章。使用 Apache CXF 托管 Web 服务

在本章中,我们将介绍以下配方:

  • 在 Apache Karaf 中安装 Apache CXF 模块

  • 使用 CXF 列出端点命令

  • 使用 CXF 停止/启动端点命令

  • 在 Karaf 中构建和部署 RESTful 服务

  • 在 Karaf 中构建和部署 Camel CXF Web 服务

简介

在第三章中,使用 Apache ActiveMQ 部署消息代理,我们讨论了如何以及何时设置 JMS 系统。另一种在系统或应用程序之间进行通信的方式是提供 Web 服务或 RESTful 端点。Apache CXF 提供了一种轻松设置和发布 Web 服务端点的方法。在 Apache Karaf 中发布 Web 服务提供了控制端点生命周期和监控已部署内容的命令。

在 Apache Karaf 中安装 Apache CXF 模块

在我们能够部署任何 Web 服务或 RESTful 服务之前,我们需要在 Karaf 容器中安装 CXF。就像其他框架一样,我们需要安装支持所需服务的特性。

如何做…

要安装 CXF 框架,只需从默认的 Karaf 实例安装 CXF 特性。如果没有指定版本,它将使用最新版本。在这个例子中,我们使用version 3.0.0-milestone2。我们可以使用以下命令完成此操作:

karaf@root()> feature:repo-add cxf <version>

一旦添加了特性 URL,我们就可以看到所有提供的 CXF 特性。这可以通过以下命令完成:

karaf@root()> feature:list | grep cxf

CXF 特性的列表非常广泛,但对我们所有的配方来说,我们只需使用以下命令简单地安装 CXF 特性。这将安装本书中将使用的所有所需特性。

karaf@root()> feature:install cxf

我们可以看到,此时已经安装了大量所需特性。以下列表是从feature:list命令的一个子集:

cxf-specs                     | 3.0.0-milestone2 | x
cxf-jaxb                      | 3.0.0-milestone2 | x
wss4j                         | 2.0.0-rc1        | x
cxf-core                      | 3.0.0-milestone2 | x
cxf-wsdl                      | 3.0.0-milestone2 | x
cxf-ws-policy                 | 3.0.0-milestone2 | x
cxf-ws-addr                   | 3.0.0-milestone2 | x
cxf-ws-rm                     | 3.0.0-milestone2 | x
cxf-ws-mex                    | 3.0.0-milestone2 | x
cxf-ws-security               | 3.0.0-milestone2 | x
cxf-http                      | 3.0.0-milestone2 | x
cxf-http-jetty                | 3.0.0-milestone2 | x
cxf-bindings-soap             | 3.0.0-milestone2 | x
cxf-jaxws                     | 3.0.0-milestone2 | x
cxf-jaxrs                     | 3.0.0-milestone2 | x
cxf-databinding-aegis         | 3.0.0-milestone2 | x
cxf-databinding-jaxb          | 3.0.0-milestone2 | x
cxf-databinding-xmlbeans      | 3.0.0-milestone2 | x
cxf-features-clustering       | 3.0.0-milestone2 | x
cxf-bindings-corba            | 3.0.0-milestone2 | x
cxf-bindings-coloc            | 3.0.0-milestone2 | x
cxf-bindings-object           | 3.0.0-milestone2 | x
cxf-transports-local          | 3.0.0-milestone2 | x
cxf-transports-jms            | 3.0.0-milestone2 | x
cxf-transports-udp            | 3.0.0-milestone2 | x
cxf-javascript                | 3.0.0-milestone2 | x
cxf-frontend-javascript       | 3.0.0-milestone2 | x
cxf-xjc-runtime               | 3.0.0-milestone2 | x
cxf                           | 3.0.0-milestone2 | x

它是如何工作的…

如果我们查看 CXF 代码库中的features.xml文件,我们可以看到cxf特性只是一个安装所有在 Karaf 中部署 CXF 所需特性的特性。这如下面的代码所示:

<feature name="cxf" version="${version}" resolver='(obr)'>
  <feature version="[3,4)">spring</feature>
  <feature version="[1.2,2)">spring-dm</feature>
  <feature version="${version}">cxf-core</feature>
  <feature version="${version}">cxf-jaxws</feature>
  <feature version="${version}">cxf-jaxrs</feature>

  <feature version="${version}">cxf-databinding-jaxb</feature>
  <feature version="${version}">cxf-databinding-aegis</feature>
  <feature version="${version}">cxf-databinding-xmlbeans</feature>
  <feature version="${version}">cxf-bindings-corba</feature>
  <feature version="${version}">cxf-bindings-coloc</feature>
  <feature version="${version}">cxf-bindings-object</feature>

  <feature version="${version}">cxf-http-jetty</feature>

  <feature version="${version}">cxf-transports-local</feature>
  <feature version="${version}">cxf-transports-jms</feature>
  <feature version="${version}">cxf-transports-udp</feature>

  <feature version="${version}">cxf-xjc-runtime</feature>
  <feature version="${version}">cxf-ws-security</feature>
  <feature version="${version}">cxf-ws-rm</feature>
  <feature version="${version}">cxf-ws-mex</feature>
  <feature version="${version}">cxf-javascript</feature>
  <feature version="${version}">cxf-frontend-javascript</feature>
  <feature version="${version}">cxf-features-clustering</feature>

  <bundle start-level='50'>mvn:org.apache.cxf/cxf-bundle-compatible/${version}</bundle>
</feature>

cxf特性为所需的包添加了一个兼容性包,如features.xml文件中所述。

相关内容

  • 使用 CXF 列出端点命令配方

  • 使用 CXF 停止和启动命令配方

使用 CXF 列出端点命令

为了查看部署到 CXF 实例中的内容,我们可以使用list-endpoints命令。这将列出在 Karaf 运行时当前部署的所有总线。

注意

这个配方只是为了演示命令的使用;我们将在后面的配方中介绍如何创建不同的 CXF 包。

准备工作

在完成在 Apache Karaf 中安装 Apache CXF 模块配方后,我们现在需要构建和部署一个示例 CXF 应用程序到 Karaf。

前往本章的代码包,并使用以下命令运行 Maven 构建:

mvn clean install

这将在 Maven 仓库中构建和安装本章的示例应用程序,以便我们可以在 Karaf 实例中轻松安装它们。

在 Karaf 实例的命令行中,使用以下命令安装此配方的 CXF 模块:

Install -s mvn:com.packt/chapter5-recipe2/1.0.0-SNAPSHOT

如何操作...

我们可以运行以下命令来列出在此 Karaf 实例中发布的 CXF 端点:

karaf@root()> cxf:list-endpoints

这将为我们提供一个列表,其中包含从我们的示例包中启动的 CXF 总线。以下命令行输出显示了这一点:

Name           State      Address   BusID 
[StringRestSe…][Started][/chapter5] [chapter5-recipe2-cxf1752581114 ]

从结果中我们可以看到,我们的端点已发布并可用。实现类的名称是StringRestServiceImpl。此外,请注意它处于Started状态,以及我们可以找到 REST 服务的地址。

小贴士

使用list-endpoints命令的-f参数来获取端点的完整地址。

因此,让我们试一试。当你访问http://localhost:8181/cxf/chapter5/recipeTwo时,它将显示配方代码返回的消息。

注意

浏览器的类型可能会影响您看到的内容;在这个例子中,我们使用 Chrome。

以下截图显示了在浏览器中将会看到的内容:

如何操作...

如您所见,我们能够访问端点并看到实现类中的消息。

工作原理...

就像list-busses命令一样,此命令使用 CXF 控制器来获取控制台显示所需的信息。此代码位于ListEndpointsCommand类中。这将使用 CXF 控制器获取总线,然后遍历列表并获取 ServiceRegistry 以获取服务器列表。从服务器,我们可以获取列表的其余详细信息,包括名称、状态和地址。

参见

  • 使用 CXF 停止和启动命令配方

使用 CXF 停止和启动命令

在某些情况下,可能需要手动控制端点的状态。使用stop-endpointstart-endpoint命令,我们可以从命令行关闭和启动已发布的端点。这在测试客户端代码以确保正确处理不可用端点时非常有用。

准备工作

按照从使用 CXF list-endpoints 命令配方中的步骤操作。

如何操作...

让我们看看如何使用stop-endpointstart-endpoint命令。这两个命令都需要提供总线参数和端点参数,以便知道要启动或停止什么。这些命令的语法如下所示:

cxf:stop-endpoint <bus> <endpoint name>
cxf:start-endpoint <bus> <endpoint name>

要停止一个端点,请按照以下步骤操作:

  1. 使用list-endpoints命令获取总线 ID 和我们的已发布端点的名称。以下将是输出:

    Name           State      Address      BusID 
    [StringRestSe…][Started][/chapter5] [chapter5-recipe2-cxf1752581114   ].
    
    
  2. 在前一步骤的输出中的信息现在可以用来启动和停止在此 Karaf 实例中发布的端点。考虑以下命令:

    cxf:stop-endpoint chapter5-recipe2-<cxf identifier> StringRestServiceImpl
    
    
  3. 前面的命令将停止在准备工作部分发布的端点。重新运行list-endpoints命令以查看状态变化如下:

    Name           State      Address   BusID 
    [StringRestSe…][Stopped][/string] [chapter5-recipe2-<cxf identifier>   ].
    
    

注意,状态已更改为Stopped。这表示我们的 REST 服务端点不再从我们的浏览器中可用。使用浏览器访问此端点地址:http://localhost:8181/cxf/chapter5/recipeTwo

浏览器找不到指定的地址,这正是我们期望发生的。以下截图是你应该看到的:

如何操作…

现在,执行以下步骤重新启动端点:

  1. 使用list-endpoints命令中列出的端点如下:

    cxf:start-endpoint chapter5-recipe2-<cxf identifier> StringRestServiceImpl
    
    
  2. 这将启动我们在准备工作部分发布的端点。重新运行list-endpoints命令,以查看状态变化如下:

    Name           State      Address      BusID 
    [StringRestSe…][Started  ][/chapter5] [chapter5-recipe2-<cxf identifier> ].
    
    

它是如何工作的…

命令由StopEndpointCommand类(start-endpoint命令非常相似)实现的cxf功能提供。这如下面的代码行所示:

@Command(scope = "cxf", name = "stop-endpoint", description = "Stops a CXF Endpoint on a Bus.")

命令注释显示 CXF 定义了子 shell,命令名称为stop-endpoint。此命令还定义了以下代码中定义的几个必需参数:

@Argument(index = 0, name = "bus", description = "The CXF bus name where to look for the Endpoint", required = true, multiValued = false)
String busName;

@Argument(index = 1, name = "endpoint", description = "The Endpoint name to stop", required = true, multiValued = false)
String endpoint;

有两个参数是必需的(如Argument注释中的required=true参数所示)。

参见

  • 使用 CXF list-endpoints 命令配方

在 Karaf 中构建和部署 RESTful 服务

现在,让我们看看代码是如何组装的,以便仅使用 CXF 在 Karaf 中发布 CXF 端点。

准备工作

在这个配方中,我们将使用代码包中的chapter5-recipe3示例。在继续此配方之前,请先卸载recipe2代码。

首先,按照在 Apache Karaf 中安装 Apache CXF 模块配方所示安装 CXF,然后构建并部署一个示例 CXF 应用程序,作为 Karaf 中的recipe3

从代码包中的chapter5-recipe3示例运行 Maven 构建,使用以下命令:

mvn clean install

这将在 Maven 仓库中构建和安装chapter5-recipe3示例的样本应用程序,这样我们就可以轻松地在 Karaf 实例中安装它们。

如何操作…

一旦部署了包,我们就可以查看部署和启动 CXF 端点。

在 Karaf 实例的命令行中,安装chapter5-recipe3示例的 CXF 模块,启动包,并使用以下命令发布端点:

install -s mvn:com.packt/chapter5-recipe3/1.0.0-SNAPSHOT

list-endpoints命令将显示我们的端点已启动且可用,如下面的命令行片段所示:

karaf@root()> cxf:list-endpoints
Name         State    Address    BusID 
[StringRes…][Started][/chapter5]chapter5-recipe3-<cxf identifier>

现在我们已经验证了我们的端点已发布,让我们通过访问此端点地址来测试它:http://localhost:8181/cxf/chapter5/recipeThree。输出如下所示截图:

![如何操作…

如何操作…

让我们检查这个拼图中不同的部分,以了解我们是如何发布端点的。

首先,我们需要使用一个接口定义端点。在示例代码中,查看以下所示的StringRestService接口类:

@Path("/")
public interface StringRestService {
    @GET
    @Path("/recipeThree")
    @Produces("application/xml")
    public String getRecipeThree() throws Exception;
}

@Path("/")注解被设置为 null。这意味着没有定义地址位置来访问此 RESTful 端点。

此接口仅定义了 RESTful 端点的@GET值(而不是@POST值)。为了访问此端点,我们只需在默认值之后使用/recipeThree位置即可访问getRecipeThree()方法。

@Produces注解定义了将返回给调用者的数据类型。在这种情况下,我们通过application/xml定义了 XML。

然后,我们必须实现接口以处理对端点的请求。在我们的简单示例中,我们只是返回一个带有 XML 的字符串值。实现类如下所示:

public class StringRestServiceImpl implements StringRestService {
  @Override
  public String getRecipeThree() throws Exception {
    System.out.println("RECIPE 3 :: restful endpoint hit.");
    return "<packt>
      <chapter5>
        <recipe3>THIS IS A COOK BOOK RECIPE THREE....BABY!!!</recipe3>
      </chapter5>
    </packt>";
  }
}

实现类很简单——我们正在创建getRecipeThree方法并返回一个 XML 字符串。

现在我们已经定义了接口和实现类,我们需要配置端点。考虑以下代码:

<cxf:bus>
  <cxf:features>
    <cxf:logging/>
  </cxf:features>
</cxf:bus>

<jaxrs:server id="stringRestService" address="/chapter5">
  <jaxrs:serviceBeans>
    <ref component-id="stringRestServiceBean"/>
  </jaxrs:serviceBeans>
</jaxrs:server>

<bean id="stringRestServiceBean" class="com.example.test.cxf.StringRestServiceImpl"/>

在 OSGi 环境中工作时,建议您使用 Blueprint。前面的 XML 代码来自一个 Blueprint XML 文件。

在此配置中,我们正在定义cxf总线并添加logging功能。由于所有日志信息都嵌入在底层模式定义中,因此没有必要指定总线的实现类。

然后,我们需要将实现类实例化为一个 Bean。在这里,我们给它一个 ID 为stringRestServiceBean

此 Bean ID 用于服务器配置中定义 ServiceBean。这将使用实现类实现的接口类在/chapter5位置发布端点。由于我们没有定义一个具有基本位置的类,因此一旦部署,地址将看起来像http://localhost:8181/cxf/chapter5/recipeThree

注意,在前面的 URL 中,/chapter5位置首先出现,这是 JAX-RS 服务器的地址,然后是接口类中定义的路径。

参见

  • 在 Karaf 中构建和部署 Camel CXF web 服务的配方。

在 Karaf 中构建和部署 Camel CXF web 服务

在前面的配方中,我们看到了如何使用 CXF 和 Karaf 轻松部署 RESTful 服务。现在,我们将探讨如何使用 Camel 部署 WSDL 首先的 CXF 端点。这是实现暴露 Web 服务的集成路由的好方法。在继续此配方之前,请先卸载recipe2代码。

准备工作

此配方的示例代码位于代码包的chapter5-recipe4示例中。很可能您已经构建了代码,但以防万一,请运行针对chapter5文件夹的mvn clean install命令。这将构建并将包移动到 Maven 仓库以在 Karaf 中部署。

注意,在构建控制台中有以下行:

[INFO] --- cxf-codegen-plugin:2.7.4:wsdl2java (generate-sources) @ chapter5-recipe4 ---

前面的行表示 CXF 的 codegen 插件已运行,并已从pom.xml文件中定义的 WSDL 生成了代码。以下是指示 Maven 从 WSDL 构建代码的插件定义:

<plugin>
  <groupId>org.apache.cxf</groupId>
  <artifactId>cxf-codegen-plugin</artifactId>
  <version>2.7.4</version>
  <executions>
    <execution>
      <id>generate-sources</id>
      <phase>generate-sources</phase>
      <configuration>
        <sourceRoot>
          ${basedir}/target/generated/src/main/java
        </sourceRoot>
        <wsdlOptions>
          <wsdlOption>
            <wsdl>
              ${basedir}/src/main/resources/META-INF/wsdl/report_domain.wsdl
            </wsdl>
          </wsdlOption>
        </wsdlOptions>
      </configuration>
      <goals>
        <goal>wsdl2java</goal>
      </goals>
    </execution>
  </executions>
</plugin>

WSDL 由<wsdl>标签标识,因此我们可以看到report_domain.wsdl文件位于${basedir}/src/main/resources/META-INF/wsdl/目录中。

wsdl2java目标表示我们希望插件从 WSDL 生成必要的文件。

如何做…

首先,我们需要设置环境以运行我们的 Camel CXF 端点。这可以按以下方式完成:

  1. 我们需要使用以下 Camel 功能命令安装 Apache Camel:

    feature:repo-add camel 2.12.3
    
    

    这将为 Camel 的功能定义添加到 Karaf 实例中。

  2. 现在,我们需要实际使用以下命令安装camelcamel-cxf功能:

    feature:install camel
    feature:install camel-cxf
    
    
  3. 一旦安装了camelcamel-cxf功能,我们可以使用以下命令安装此菜谱的包:

    install –s mvn:com.packt/chapter5-recipe4/1.0.0-SNAPSHOT
    
    

我们可以使用bundle:list命令列出包,以确保包已成功安装。输出将如下所示:

START LEVEL 100 , List Threshold: 50
 ID | State  | Lvl | Version  | Name 
---------------------------------------------------------
 85 | Active |  50 | 2.12.3   | camel-core 
 86 | Active |  50 | 2.12.3   | camel-karaf-commands 
101 | Active |  50 | 1.1.1    | geronimo-jta_1.1_spec 
102 | Active |  50 | 2.12.3   | camel-spring 
103 | Active |  50 | 2.12.3   | camel-blueprint 
179 | Active |  50 | 2.7.10   | Apache CXF Compatibility Bundle Jar
180 | Active |  50 | 2.12.3   | camel-cxf-transport 
181 | Active |  50 | 2.12.3   | camel-cxf 
182 | Active |  80 | 1.0.0.SNA| Chapter 5 :: Recipe 4 ::

我们可以看到最后一个包是我们的菜谱包。它已启动并处于活动状态,这表明端点已发布。

为了验证这一点,我们可以使用 CXF 命令。当我们使用karaf@root()> cxf:list-busses命令时,输出将如下所示:

Name                             State 
[chapter5-recipe4-<id>         ] [RUNNING           ]

当我们使用karaf@root()> cxf:list-endpoints -f命令时,输出将如下所示:

Name                      State
[ReportDomainService    ] [Started ]
Address
[http://localhost:9080/webservices/domain  ]
BusID
[chapter5-recipe4-<id>        ]

这表明我们的 Camel CXF Web 服务已部署并可在http://localhost:9080/webservices/domain处访问。

您还可以查看端点http://localhost:9080/webservices/domain?wsdl上可用的 WSDL 代码。

将上述地址放入您喜欢的网页浏览器中,我们将看到 WSDL 代码。这应该看起来像以下截图:

如何做…

我们看到端点通过多个途径发布并可用。让我们使用 SoapUI 来测试它,这是一个免费的 Web 服务测试工具,可在www.soapui.org/找到。步骤如下:

  1. 将 WSDL 代码作为项目加载到 SoapUI 中,它将根据 WSDL 定义自动为我们设置请求。以下截图显示了请求应该看起来是什么样子:如何做…

    这也将自动加载请求的地址。

  2. 我们可以点击播放按钮向端点提交请求。它将简单地响应OK,如下面的截图所示:如何做…

这表明我们能够向端点提交请求并检索响应。响应是一个简单的硬编码的OK,但展示了端点的可用性。

它是如何工作的…

首先,让我们看看 Camel 组件的端点配置。在 Blueprint 文件中,我们定义服务端点,如下面的代码所示:

<blueprint

       xsi:schemaLocation="http://www.osgi.org/xmlns/blueprint/v1.0.0 http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd
       ">

  <camelcxf:cxfEndpoint id="reportDomain"
          address="http://localhost:9080/webservices/domain"
          wsdlURL="META-INF/wsdl/report_domain.wsdl"
          serviceClass="com.example.test.cxf.reportdomain.ReportDomainEndpoint"/>

  <bean id="domainRoutes" class="com.example.test.cxf.routes.DomainRoutes"/>

  <camelContext id="camel">
    <routeBuilder ref="domainRoutes"/>
  </camelContext>

</blueprint>

上述代码是 CXF 端点配置;请注意,我们指定了发布的 Web 服务的地址和端口,以及 WSDL 文件的位置。

路由通过一个带有domainRoutes bean ID 的 Blueprint 实例化。这将允许我们从 CamelContext 配置中引用它。

CamelContext 定义了在此捆绑包中要使用的所有 Camel 路由。

现在,让我们看看路由的实际代码:

public class DomainRoutes extends RouteBuilder {
    OutputReportDomain ok = new OutputReportDomain();

    public void configure() throws Exception {
        // webservice response for OK
        ok.setCode("OK");

        from("cxf:bean:reportDomain").id("domainService").choice().when().simple("${in.header.operationName} == 'ReportDomain'").convertBodyTo(InputReportDomain.class).to("log:dummy").process(new Processor() {
                    public void process(Exchange exchange)throws Exception {
                        exchange.getIn().setBody(ok);
                    }
        })
        .end();
    }
}

from("cxf:bean:reportDomain")代码行是路由的开始。我们需要能够配置它,以便它可以处理对我们的 Web 服务的任何调用。URI 中的reportDomain部分是对在blueprint.xml文件中配置的cxfEndpoint对象的引用。

小贴士

注意,当使用 Camel CXF 端点时,不需要实现代码,因为所有请求都被路由拦截并处理。

剩余的路由只是为了展示。我们在 WSDL 中将主体转换为定义的数据类型。在这种情况下,它被转换为InputReportDomain.class。然后,我们使用.to("log:dummy")将其记录到karaf.log文件中。最后,我们返回带有OK代码的OutputReportDomain.class

参见

  • 在 Karaf 中构建和部署 RESTful 服务的方法

第六章:使用 Apache Karaf Cellar 分发集群容器

在本章中,我们将涵盖以下主题:

  • 在 Apache Karaf 中安装 Apache Karaf Cellar 模块

  • 使用 Apache Karaf Cellar 命令

  • 使用 Cellar 构建和部署分布式架构

简介

本章展示了如何使用 Apache Karaf 安装和使用 Apache Karaf Cellar。Apache Karaf Cellar 的主要目标是实现 Apache Karaf 的集群化供应。这种供应可以通过捆绑包、功能和配置的白名单和黑名单进行控制。除了这些核心功能之外,还可以使用 Cellar 在云中或与云进行集群,并通过集群发送事件。使用 Cellar 还可以拥有分布式 OSGi 服务。

小贴士

如果你想要更深入地了解 Apache Karaf Cellar 的工作原理,请参阅Learning Karaf Cellar,作者Jean-Baptiste Onofré,出版社Packt Publishing

在 Apache Karaf 中安装 Apache Karaf Cellar 模块

首先,我们需要添加集群多个 Karaf 实例的能力。为此,Cellar 需要首先安装在 Apache Karaf 上。为此,需要添加所需的功能仓库 URL,这可以通过以下便捷方法完成:

karaf@root()> feature:repo-add cellar

在此之后,最新版本的 Cellar 的所有可能功能都可以用于安装。在我们的案例中,这是 Cellar 3.0.0。除了安装 Cellar,还需要运行多个 Karaf 实例以验证这些配方。

如何做到这一点...

按照以下步骤设置 Cellar 并在同一台机器上创建多个 Karaf 实例:

  1. 使用以下命令安装所需的cellar功能:

    karaf@root()> feature:install cellar
    
    

    这将安装所有所需的 Cellar 捆绑包,以便我们有基本的设置来运行 Karaf 集群。

  2. 接下来,拥有另一个用于集群的节点非常重要。为此,我们将在同一台机器上设置第二个 Karaf 实例。为此,以下命令将很有用。因此,使用以下命令创建另一个 Karaf 实例:

    karaf@root()> instance:create second
    
    

    这将创建一个新的 Karaf 实例,看起来就像一个新提取的 Apache Karaf ZIP 存档。

  3. 第二个实例已创建;现在,我们需要启动这个第二个实例。Karaf 有一个用于此目的的命令,如下所示:

    karaf@root()> instance:start second
    
    
  4. 这刚刚启动了第二个实例,现在可以通过外部 SSH 客户端或通过 Karaf 连接到它。这可以通过以下命令完成:

    karaf@root()> instance:connect second
    
    
  5. 使用这种方式,你将连接到第二个 Karaf 实例。我们还需要将cellar仓库添加到这个实例中。这可以通过以下命令完成:

    karaf@second()> feature:repo-add cellar
    
    
  6. cellar功能仓库对第二个实例可用时,cellar功能就准备好安装了。

    karaf@second()> feature:install cellar
    
    

这将安装所需的捆绑包;因此,我们有两个节点准备好进行集群。由于 Cellar 集群通过多播进行自动发现,两个节点都应该能够找到对方。

注意

由于 Cellar 使用 Hazelcast 作为其底层集群技术,多播由 Hazelcast 处理。有关 Hazelcast 的更多信息,请参阅www.hazelcast.org/docs/3.0/manual/html/ch12s02.html

可以使用以下命令列出可用的群组来检查这些节点:

karaf@second()> cluster:group-list

这将列出可用的群组以及每个群组中包含的节点。由于尚未对任何群组进行配置,默认群组包含两个节点——根 Karaf 实例和第二个实例。以下屏幕截图显示了group-list命令的执行,该命令是cluster:group-list命令的别名:

如何操作…

它是如何工作的…

在两个节点上安装 Cellar 后,Cellar 在两个节点上都创建了一个默认群组,因此这两个节点自动添加到这个默认群组中。Cellar 的基础是 Hazelcast 内存集群配置,它会将自己复制到集群中所有已知的节点。Hazelcast 本身通过多播扫描网络以查找其他实例。Hazelcast 可以被配置为使用其他网络连接技术。Apache Karaf Cellar 使用这种行为来在运行中的节点上同步集群配置。

Cellar 集群由一个群组定义,其中每个群组包含一个节点列表。对于每个集群群组,都有一个 Hazelcast 主题,它是通信后端。通过这个主题,同一群组内的所有节点都会在包、功能或配置中通信更改。此外,服务和事件也是通过这个主题进行通信的。

由于这个主题,所有节点的配置被组合成一个共享的集群配置,如下面的屏幕截图所示:

它是如何工作的…

使用 Apache Karaf Cellar 命令

与 Apache Karaf 的许多其他功能一样,Cellar 提供了一些命令来管理 Apache Karaf Cellar 集群。

准备工作

确保您遵循了在 Apache Karaf 中安装 Apache Karaf Cellar 模块的基本设置说明来运行一个简单的 Apache Karaf 集群。

如何操作…

我们有两种不同类型的命令来处理 Apache Karaf Cellar 集群:用于管理和配置集群的基本命令以及一些增强的命令用于额外管理。

群组命令

安装 Cellar 后,已经有一个默认群组可用,该群组默认包含所有未来可用的节点。可以使用以下命令执行不同的任务:

  1. 现在,让我们使用以下命令创建一个新的群组并将两个节点添加到其中:

    karaf@root()> cluster:group-create main-cluster
    
    

    之前的命令创建了一个名为main-cluster的新集群群组。这个群组中没有节点。使用group-list命令检查集群现在的样子。该命令的结果可以在以下屏幕截图中看到:

    群组命令

  2. 新创建的组仍然是空的。因此,我们需要添加一些节点。向集群中添加节点可以通过不同的方式完成。以下命令是一个示例:

    karaf@root()> cluster:group-pick default main-cluster
    
    

    以下命令的完整语法如下:

    cluster:group-pick source-group destination-group number-of-nodes
    
    

    它从给定的源组中选择一些节点并将这些节点转移到目标组。如果没有指定数量,则从源组中选择一个。执行和外观如下面的截图所示:

    组命令

  3. 向集群组添加节点的另一种方法是使用join命令。使用此命令,您还可以将节点添加到多个组中。

    karaf@root()> cluster:group-join main-cluster achims-macbook-pro.fritz.box:5702
    
    

    上述命令的语法如下:

    cluster:group-join group-destination node-id
    
    

    命令及其输出如下面的截图所示:

    组命令

  4. 如您所见,第二个节点现在包含在两个集群组中,尽管我们不再希望这个节点保留在默认组中。为此,我们需要使用以下命令将其从该组中移除:

    karaf@root()> cluster:group-quit default achims-macbook-pro.fritz.box:5702
    
    

    上述命令的语法如下:

    cluster:group-quit group-id node-id
    
    

    命令返回当前组的状态列表,如下面的截图所示:

    组命令

  5. 可以使用以下命令删除此空组:

    karaf@root()> cluster:group-delete default
    
    

当使用cluster:group-list命令检查时,您会看到默认组无法删除。这是因为其默认特性。因此,如果您创建一个新的测试组并立即删除它,您将看到测试组已通过命令创建和删除。

节点命令

以下是对节点命令的简要介绍。

  1. 类似于group-list命令,node-list命令将列出所有可用的节点:

    karaf@root()> cluster:node-list
    
    

    上述命令的输出如下面的截图所示:

    节点命令

  2. 类似于网络 ping,可以 ping 一个集群节点来测试该节点的网络可达性。这可以通过以下命令完成:

    karaf@root()> cluster:node-ping achims-macbook-pro:5702
    
    

    输出将如下面的截图所示:

    节点命令

集群配置命令

到目前为止,之前的命令已用于集群本身的管理,通过添加或删除集群组以及配置一个集群组的节点。

  1. 最后一个管理命令是cluster:sync命令:

    karaf@root()> cluster:sync
    
    

    这将强制同步集群中的所有节点,如下面的截图所示:

    集群配置命令

  2. 现在,以下命令用于配置集群的内容。例如,可以在集群中安装功能和包。要列出一个集群组的当前可用功能,只需发出以下命令:

    karaf@root()> cluster:feature-list main-cluster
    
    

    集群配置命令

  3. 上述命令同样适用于列出集群中安装的包:

    karaf@root()> cluster:bundle-list main-cluster
    
    

    这将生成集群组可用的包列表。输出将如下所示:

    karaf@root()> cluster:bundle-list main-cluster
    Bundles in cluster group main-cluster
     ID     State        Name
    [0   ] [Active     ] OPS4J Base - Lang (1.4.0)
    [1   ] [Active     ] Apache Karaf :: Cellar :: Hazelcast (3.0.0.SNAPSHOT)
    [2   ] [Active     ] Apache Karaf :: Cellar :: Features (3.0.0.SNAPSHOT)
    [3   ] [Active     ] Apache Karaf :: Cellar :: Core (3.0.0.SNAPSHOT)
    [4   ] [Active     ] JLEdit :: Core (0.2.1)
    [5   ] [Active     ] Apache Karaf :: JAAS :: Command (3.0.1)
    [6   ] [Active     ] Apache Mina SSHD :: Core (0.9.0)
    [7   ] [Active     ] Apache Karaf :: Cellar :: Management (3.0.0.SNAPSHOT)
    [8   ] [Active     ] jansi (1.11.0)
    
    
  4. 为了在集群中进行进一步的包交互,请使用以下特殊的集群包命令:

    cluster:bundle-* <groupId>
    
    

    这些包命令与标准的 Apache Karaf 包命令类似。这些命令的范围是在集群内分发命令。

  5. 对于功能,命令与cluster:bundle命令类似。适用于功能的命令也可以在集群范围内运行:

    cluster:feature-* <groupId>
    
    

    cluster:feature命令将在集群中像独立 Karaf 实例一样工作。

  6. 与包和功能命令类似,配置命令也可以更改、设置或取消设置在整个集群中有效的配置。考虑以下命令:

    karaf@root()> cluster:config-list main-cluster
    
    

    前面命令的输出与config:list命令的输出相同,但仅限于对集群组有效的配置,如下面的截图所示:

    集群配置命令

  7. 通常,集群配置命令具有以下模式:

    cluster:config-* <groupId>
    
    

前面的命令提供了编辑、删除或添加配置的可能性,然后这些配置会自动在整个集群中共享。

它是如何工作的…

如所见,Apache Karaf Cellar 提供了一些专门的集群命令。这种功能基于这样一个事实:所有配置、安装的包和安装的功能在整个集群中都是共享的。为了对这部分信息进行专门的控制,可以白名单或黑名单某些信息。这些信息定义在两个配置文件中,org.apache.karaf.cellar.node.cfg用于节点配置,org.apache.karaf.cellar.groups.cfg包含组配置信息。

通常,在集群中部署应用程序时,使用功能而不是包会更好。这使得过滤白名单和黑名单中的包或功能以适应特定的集群节点变得更容易。

参见

  • 学习 Karaf Cellar》,Jean-Baptiste OnofréPackt Publishing,比本食谱中可以做到的更详细地介绍了 Cellar 的见解。

使用 Cellar 构建和部署分布式架构

本食谱描述了如何构建集群应用程序以及在整个集群中透明使用服务的方法。我们将向您展示如何创建基于 Cellar 的分布式 OSGiDOSGi)应用程序,以及您如何从 Apache Karaf Cellar 中获益。

准备工作

至关重要的是,您至少完成在 Apache Karaf 中安装 Apache Karaf Cellar 模块配方,以便在测试环境中有一个集群环境。您还应该熟悉使用 Apache Karaf Cellar 命令配方中向您展示的集群命令。本配方中使用的源代码可以在github.com/jgoodyear/ApacheKarafCookbook/tree/master/chapter6/chapter6-recipe3找到。

如何操作…

首先,我们需要一个额外的集群组,其中包含一个额外的节点。为此,我们首先创建一个额外的实例,使用以下命令:

karaf@root()> instance:create sender

启动此实例,并按照在 Apache Karaf 中安装 Apache Karaf Cellar 模块配方中的说明安装cellar功能。此实例将被添加到一个新组中。首先,使用以下命令创建一个新的集群组:

karaf@root()> cluster:group-create second-cluster

现在,使用以下命令将新实例添加到新创建的集群组:

karaf@root()> cluster:group-pick default second-cluster

在完成此设置后,我们需要注意分布式 OSGi 服务。为此,您将创建三个不同的包,其中第一个将包含服务接口,第二个将包含位于一个节点上的服务实现,第三个将包含消费包。

以下图显示了此设置:

如何操作…

首先,我们需要安装所需的功能。为此,我们将使用以下命令在每个节点上安装cellar-dosgi功能。默认情况下,所有 Cellar 功能都被列入黑名单。

karaf@root()> feature:install cellar-dosgi

在集群中安装此功能后,您将有一个新的命令可以使用。cluster:service-list命令将列出集群中分布的所有服务。在安装功能后立即调用的命令将给出以下输出:

如何操作…

生成服务包

在此设置完成后,我们需要一个提供远程客户端可用服务的包。为此,我们创建一个简单的服务,该服务返回一个随机消息。可以按照以下方式完成:

  1. 要注册此服务,我们将使用以下blueprint.xml文件:

    <!-- Service Implementation -->
    <bean id="messageService" class="com.packt.impl.MessageServiceImpl"/>
    
    <!-- Registering the Service -->
    <service ref="messageService" interface="com.packt.MessageService">
      <service-properties>
        <entry key="service.exported.interfaces" value="*"/>
      </service-properties>
    </service>
    
  2. 使用以下命令从代码包中安装chapter6-recipe3-interface包到两个集群组:

    karaf@root()> cluster:bundle-install master mvn:com.packt/chapter6-recipe3-interface/1.0.0-SNAPSHOT
    karaf@root()> cluster:bundle-install second-cluster mvn:com.packt/chapter6-recipe3-interface/1.0.0-SNAPSHOT
    
    
  3. 然后,使用以下命令从代码包中在第二个组second-cluster上安装chapter6-recipe3-producer包:

    karaf@root()> cluster:bundle-install second-cluster mvn:com.packt/chapter6-recipe3-producer/1.0.0-SNAPSHOT
    
    
  4. 新安装的包需要启动。为了方便找到这些已安装的包,使用带有grep命令的bundle-list命令,如下所示:

    karaf@root()> cluster:bundle-list main-cluster | grep -i chapter
    
    

    这将产生以下输出:

    karaf@root()> cluster:bundle-list main-cluster | grep chapter6
    [36  ] [Installed  ] chapter6-recipe3-interface (1.0.0.SNAPSHOT)
    
    
  5. 现在您已经找到了相应的包 ID,只需为它发出一个启动命令:

    karaf@root()> cluster:bundle-start main-cluster <bundle id>
    
    

    使用list命令的快速使用将为您提供节点捆绑包列表,显示新安装并运行的捆绑包chapter6-recipe3-interface。如下截图所示:

    生成服务捆绑包

现在我们已经检查了消费端,我们需要确保发送端正在运行。我们需要查找我们刚刚安装的捆绑包。这可以通过以下命令完成:

karaf@root()> cluster:bundle-list second-cluster | grep -i chapter

这将产生以下输出:

karaf@root()> cluster:bundle-list second-cluster | grep chapter6
[38  ] [Installed  ] chapter6-recipe3-interface (1.0.0.SNAPSHOT)
[39  ] [Installed  ] chapter6-recipe3-producer (1.0.0.SNAPSHOT)

我们可以使用以下命令启动捆绑包:

karaf@root()> cluster:bundle-start main-cluster <bundle id>

在启动此捆绑包之后,我们可以通过从根捆绑包调用的cluster:service-list命令来检查集群中可用的服务。此操作的结果如下所示:

生成服务捆绑包

服务消费捆绑包

由于服务现在在整个集群中可用,消费捆绑包可以安装到主集群并消费此服务。考虑以下代码:

<!-- reference service -->
<reference id="messageService" interface="com.packt.MessageService" />

<!-- Service Implementation -->
<bean class="com.packt.consumer.MessageConsumer" init-method="doRun">
  <property name="messageService" ref="messageService"/>
</bean>

消费捆绑包从注册表中检索服务引用并使用它。消费类通过init-method调用启动。

接口捆绑包已经安装,因为我们已经为它发出了cluster:bundle-install命令。现在,只需使用以下命令在根节点上安装consumer捆绑包:

karaf@root()>cluster:bundle-install main-cluster mvn:com.packt/chapter6-recipe3-consumer/1.0.0-SNAPSHOT

现在,通过以下命令启动consumer捆绑包;您可能需要更改捆绑包 ID:

karaf@root()> cluster:bundle-list master | grep -i chapter
[36  ] [Active     ] chapter6-recipe3-interface (1.0.0.SNAPSHOT)
[52  ] [Installed  ] chapter6-recipe3-consumer (1.0.0.SNAPSHOT)

您可以使用以下命令启动捆绑包:

karaf@root()> cluster:bundle-start <bundle-id>

注意

注意,如果您在单独的终端中启动了第二个节点,这将使您从中获得最大收益,因为您将看到两个节点都从发送者那里接收消息。

它是如何工作的……

默认情况下,Cellar DOSGi 不会在整个集群中共享任何服务。一个打算在集群环境中使用并绑定到专用集群节点的服务需要在服务上进行特殊处理。您可能已经注意到了MessageService注册上的额外服务属性。属性条目service.exported.interfaces标记此服务将在整个集群中导出。Cellar 将在不包含实际服务的节点上为远程服务创建代理。

为什么我们需要额外的节点和组?因为 Apache Karaf Cellar 是为了在集群组中提供捆绑包,这将导致所有捆绑包都在同一组中分散。而实际上,在集群中运行服务需要将捆绑包在集群中分离,这又导致节点通过显式组再次分离。

查看以下截图,它总结了集群组内的通信方式。在整个集群中安装一个包,可以通过之前提到的命令实现,或者只需在节点 1上安装此包。新包的此状态将在整个集群组中同步,除非此包被明确列入黑名单。另一种在整个集群中禁用此自动同步的方法是使用管理节点。管理节点需要在集群组内有一个主节点来管理整个集群的功能安装。这与默认设置和“无所不知”的集群大脑背后的理念相反。更多详情,请参阅《学习 Karaf Cellar》,作者让-巴蒂斯特·奥诺弗雷Packt 出版社

如何工作…

第七章. 使用 Apache Aries 和 OpenJPA 提供持久层

在本章中,我们将涵盖以下食谱:

  • 在 Apache Karaf 中安装 OpenJPA 模块

  • 在 Apache Karaf 中安装 Apache Aries JTA 模块

  • 构建一个具有持久层以在 Karaf 中部署的项目

  • 构建一个具有持久层和事务支持的项目以在 Karaf 中部署

简介

您的应用程序通常需要安全地持久化数据并利用事务行为。对于 Java 开发者来说,完成此任务的一个首选方法是使用Java 持久化 APIJPA)和Java 事务 APIJTA)。在 Apache Karaf 的上下文中,开发者将使用一个 OSGi 兼容的 JPA 实现,例如Apache OpenJPAEclipseLinkHibernate以及Apache Aries JTA

本书将使用 OpenJPA 作为 JPA 规范的实现,以实现 Java 对象的透明持久化。与 OSGi 容器相关联时,它为 Blueprint 容器提供容器管理的持久化。

Apache Aries JTA 为容器提供事务管理服务。使用此服务,开发者可以构建需要以下事务流程的应用程序:

getTransaction()
begin()  // demark beginning of transaction
doWork() // your business logic
if (rollback) then rollback() else commit()

之前的伪代码概述了事务的一般形式;开发者从容器中获取事务会话,标记事务的开始,执行他们的业务逻辑,然后必须决定他们是否可以提交事务或回滚资源。

本章中的食谱将帮助您将 JPA 和 JTA 资源部署到 Karaf 中,并通过示例指导您如何在您的捆绑包中使用这些 API。

在 Apache Karaf 中安装 OpenJPA 模块

在我们开始探索如何构建由 OpenJPA 支持的应用程序之前,我们必须首先将所有必需的 JPA 模块安装到 Karaf 容器中。

准备工作

本食谱的成分包括 Apache Karaf 发行套件、JDK 访问权限和互联网连接。

如何做到这一点...

多亏了 Apache Karaf 的功能系统,安装 OpenJPA 是一个非常简单的两步过程:在 Karaf 中安装 JPA 和 OpenJPA 功能。步骤如下:

注意

为什么我们不需要添加功能 URL?这是因为 Apache Karaf 的标准发行版默认包含 JPA 和 OpenJPA 功能 URL。

  1. 我们可以通过执行带有功能名称的feature:install命令来安装一个功能,如下所示:

    karaf@root()>  feature:install jpa
    
    

    我们可以通过执行list –t 0 | grep -i JPA命令来验证安装,该命令将在 Karaf 中列出所有安装的 OpenJPA 组件和依赖项(Geronimo-jta_1.1_spec、Geronimo-jpa_2.0_spec、Aries JPA API、Aries JPA Blueprint、Aries JPA Container 和 Aries JPA Container Context)。

  2. 与 JPA 的安装类似,我们使用功能名称来安装 OpenJPA 引擎,如下所示。

    karaf@root()>  feature:install openjpa/2.2.2
    
    

    我们可以通过执行 list –t 0 | grep -i OpenJPA 命令来验证安装,该命令将列出 Karaf 中所有已安装的 OpenJPA 组件和依赖项(其中核心是 OpenJPA 聚合 JAR)。

如何工作…

Apache Karaf 社区维护了一个用于 JPA 和 OpenJPA 的 Apache Karaf 功能描述符。这些功能描述符文件包含了安装这些 API 和提供者所需的全部基本包和依赖项,如下面的图示所示:

如何工作…

当安装 JPA 或 OpenJPA 功能(使用 feature:install 命令)时,Karaf 将使用适当的 URL 处理程序来获取所需资源并将它们安装到容器中。接下来,它尝试将它们启动。如果你在 Karaf 控制台中执行 list –t 0 命令,你将看到部署到容器中的 JPA 和所有其他工件。我们可以通过展示关键工件被部署在标准 Karaf 安装之上,更简单地描述 OpenJPA 组件与 Karaf 的集成。

参见

  • 第一章中“将应用程序作为功能部署”的食谱,Apache Karaf for System Builders

在 Apache Karaf 中安装 Apache Aries JTA 模块

应用程序通常需要与 JPA 一起进行事务管理。这是通过将 JTA 包含到 Karaf 容器中实现的。

准备工作

本食谱的成分包括 Apache Karaf 分发套件、对 JDK 的访问以及互联网连接。通常,你还需要执行“在 Apache Karaf 中安装 OpenJPA 模块”食谱中概述的步骤。

如何操作…

感谢 Apache Karaf 的功能系统,安装 JTA 是一个非常简单的单步过程。

注意

为什么我们不需要添加功能 URL?这是因为 Apache Karaf 的标准分发版默认包含了 JTA 功能 URL。

将 JTA 功能安装到 Karaf 中

我们通过执行以下命令来安装功能:feature:install 命令,其中包含功能的名称:

karaf@root()>  feature:install transaction

我们可以通过执行 list –t 0 | grep -i transaction 命令来验证安装,该命令将列出 Karaf 中所有已安装的事务组件和依赖项(例如 Apache Aries 事务蓝图和 Apache Aries 事务管理器)。此外,我们还可以通过在 jta 上进行 grep 操作来验证 geronimo_jta_1.1_spec 是否已安装。

如何工作…

Apache Karaf 社区维护了一个用于 JTA 的 Apache Karaf 功能描述符。功能描述符文件包含了安装事务管理器所需的所有基本包和依赖项,如下面的图示所示:

如何工作…

当 JTA 功能被安装(使用feature:install命令)时,Karaf 将使用适当的 URL 处理程序来获取所需资源并将它们安装到容器中。接下来,它尝试将它们带到启动状态。如果您在 Karaf 控制台中执行list –t 0命令,您将看到部署到容器中的 JTA 和所有其他工件。我们可以通过展示关键工件被部署在标准 Karaf 安装之上来更简单地描述 JTA 组件与 Karaf 的集成。在这个部署中,我们看到事务管理器(JTA)被部署在“在 Apache Karaf 中安装 OpenJPA 模块”食谱中的各种 JPA 模块之间。

参见

  • “第一章”中关于将应用程序作为功能部署的食谱,Apache Karaf for System Builders

  • “在 Apache Karaf 中安装 OpenJPA 模块”食谱

在 Karaf 中构建具有持久层的项目

应用程序开发人员通常需要在他们的项目中使用持久层;在 Karaf 中执行此操作的一种首选方法是使用 Java 持久性 API 和 OpenJPA。

在“在 Apache Karaf 中安装 OpenJPA 模块”食谱中,我们学习了如何在 Karaf 中安装 OpenJPA;在本食谱中,我们将使用 JPA 和 OpenJPA 来构建一个简单的应用程序,该应用程序使用RecipeBookService类将食谱持久化到数据库中,这将隐藏数据存储和检索的复杂性,使其用户难以察觉。

准备工作

本食谱的成分包括 Apache Karaf 分发套件、对 JDK 的访问以及互联网连接。本食谱的示例代码可在github.com/jgoodyear/ApacheKarafCookbook/tree/master/chapter7/chapter7-recipe3找到。通常,您还需要执行“在 Apache Karaf 中安装 OpenJPA 模块”食谱中概述的步骤。

小贴士

在尝试此食谱之前,请先卸载“在 Karaf 中构建具有持久层和事务支持的部署项目”食谱的内容,因为可能会发生冲突。本食谱在本章的后面部分。

如何操作...

在 Karaf 中构建具有 JPA 持久层的项目需要以下九个步骤:

  1. 第一步是生成基于 Maven 的包项目。创建一个空的基于 Maven 的项目。一个包含基本 Maven 坐标信息和包打包指令的pom.xml文件就足够了。

  2. 下一步是在 POM 文件中添加依赖项,如下面的代码所示:

    <dependencies>
      <dependency>
        <groupId>org.apache.servicemix.bundles</groupId>
        <artifactId>
          org.apache.servicemix.bundles.commons-dbcp
        </artifactId>
        <version>1.4_3</version>
      </dependency>
      <dependency>
        <groupId>org.osgi</groupId>
        <artifactId>org.osgi.core</artifactId>
        <version>5.0.0</version>
      </dependency>
      <dependency>
        <groupId>org.osgi</groupId>
        <artifactId>org.osgi.compendium</artifactId>
        <version>5.0.0</version>
      </dependency>
      <dependency>
        <groupId>org.osgi</groupId>
        <artifactId>org.osgi.enterprise</artifactId>
        <version>5.0.0</version>
      </dependency>
      <dependency>
        <groupId>org.apache.geronimo.specs</groupId>
        <artifactId>geronimo-jpa_2.0_spec</artifactId>
        <version>1.1</version>
      </dependency>
      <dependency>
        <groupId>org.apache.openjpa</groupId>
        <artifactId>openjpa</artifactId>
        <version>2.2.2</version>
        <scope>test</scope>
      </dependency>
      <dependency>
        <groupId>org.apache.derby</groupId>
        <artifactId>derby</artifactId>
        <version>10.8.1.2</version>
        <scope>provided</scope>
      </dependency>
    <!-- custom felix gogo command -->
      <dependency>
        <groupId>org.apache.karaf.shell</groupId>
        <artifactId>
          org.apache.karaf.shell.console
        </artifactId>
        <version>3.0.0</version>
      </dependency>
    </dependencies>
    

    对于 Karaf 3.0.0,我们使用 OpenJPA 2.2.2 和 OSGi 版本 5.0.0。

  3. 下一步是添加构建插件。我们的食谱需要配置两个构建插件:OpenJPA 和 bundle。

    1. 首先,我们配置openjpa-maven-pluginopenjpa-maven-plugin为基于 OpenJPA 的项目提供构建和维护的任务。我们在 POM 文件中添加以下插件配置:

      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>openjpa-maven-plugin</artifactId>
        <configuration>
          <addDefaultConstructor>
            true
          </addDefaultConstructor>
          <enforcePropertyRestriction>
            true
          </enforcePropertyRestriction>
        </configuration>
        <executions>
          <execution>
            <id>enhancer</id>
            <phase>process-classes</phase>
            <goals>
              <goal>enhance</goal>
            </goals>
          </execution>
        </executions>
        <dependencies>
          <dependency>
            <groupId>org.apache.openjpa</groupId>
            <artifactId>openjpa</artifactId>
            <version>2.2.2</version>
          </dependency>
          <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.6.1</version>
          </dependency>
        </dependencies>
      </plugin>
      

      在前面的代码块中给出的 openjpa-maven-plugin 中,我们指示插件在实体类上执行增强过程以提供持久化功能。正如 OpenJPA 项目所述:“构建时增强是使用 OpenJPA 的推荐方法,因为它是最快且最可靠的方法”。

    2. 接下来,我们配置 maven-bundle-plugin。我们将 maven-bundle-plugin 配置为将我们的项目代码组装成一个 OSGi 包。我们向我们的 POM 文件中添加以下插件配置:

      <plugin>
        <groupId>org.apache.felix</groupId>
        <artifactId>maven-bundle-plugin</artifactId>
        <version>2.4.0</version>
        <extensions>true</extensions>
        <configuration>
          <instructions>
            <Bundle-SymbolicName>
              ${project.artifactId}
            </Bundle-SymbolicName>
            <Meta-Persistence>
              META-INF/persistence.xml
            </Meta-Persistence>
            <Bundle-Activator>
              com.packt.jpa.demo.Activator
            </Bundle-Activator>
            <Export-Package>
              com.packt.jpa.demo.api.*
            </Export-Package>
            <Import-Package>
              org.osgi.service.blueprint;resolution:=optional,
              javax.persistence;version="[1.1,2)",
              javax.persistence.criteria;version="[1.1,2)",
              javax.sql,
              org.apache.commons.dbcp;version="[1.4,2)",
              org.apache.derby.jdbc,
              org.apache.felix.service.command,
              org.apache.felix.gogo.commands,
              org.apache.karaf.shell.console,
              *
            </Import-Package>
          </instructions>
        </configuration>
      </plugin>
      

      如前一个代码片段中突出显示的,Meta-Persistence 标签将向我们的包的清单文件中添加一个条目,指向我们的 persistence.xml 文件位置(我们将在下一步中创建此资源)。对于我们的示例项目,javax.persistencedbcpderby 包的导入语句至关重要。Felix 和 Karaf 的导入是可选的 Karaf 命令所必需的。

  4. 下一步是创建持久化描述符文件。在你的项目中创建一个名为 src/main/resources/META-INF 的目录树。然后,我们将在这个文件夹中创建一个名为 persistence.xml 的文件。此文件如下代码片段所示:

    <persistence 
    
                 version="1.0">
    
      <persistence-unit name="recipe" transaction-type="RESOURCE_LOCAL">
        <provider>
          org.apache.openjpa.persistence.PersistenceProviderImpl
        </provider>
    
        <non-jta-data-source>
          osgi:service/javax.sql.DataSource/ (osgi.jndi.service.name=jdbc/demo)
        </non-jta-data-source>
    
        <class>com.packt.jpa.demo.entity.Recipe</class>
        <exclude-unlisted-classes>
          true
        </exclude-unlisted-classes>
    
        <properties>
    
          <!-- OpenJPA Properties -->
          <property name="openjpa.ConnectionDriverName" value="org.apache.derby.jdbc.ClientDriver.class"/>
          <property name="openjpa.ConnectionURL" value="jdbc:derby://localhost:1527/demo;create=true"/>
          <property name="openjpa.Multithreaded" value="true"/>
          <property name="openjpa.TransactionMode" value="managed"/>
          <property name="openjpa.ConnectionFactoryMode" value="managed"/>
          <property name="openjpa.LockManager" value="pessimistic(VersionCheckOnReadLock=true,VersionUpdateOnWriteLock=true)"/>
          <property name="openjpa.LockTimeout" value="30000"/>
          <property name="openjpa.jdbc.MappingDefaults" value="ForeignKeyDeleteAction=restrict, JoinForeignKeyDeleteAction=restrict"/>
          <property name="openjpa.LockManager" value="pessimistic(VersionCheckOnReadLock=true,VersionUpdateOnWriteLock=true)"/>
          <property name="openjpa.Log" value="DefaultLevel=INFO, Runtime=INFO, Tool=INFO, SQL=INFO"/>
          <property name="openjpa.jdbc.SynchronizeMappings" value="buildSchema"/>
          <property name="openjpa.jdbc.DBDictionary" value="derby"/>
        </properties>
      </persistence-unit>
    </persistence>
    

    持久化描述符文件包含我们应用程序所需的大量配置条目。其中最重要的是 persistence-unitnon-jta-data-source 对象的定义;前者将数据持久化设置为 RESOURCE_LOCAL,后者设置使用 JDBC 服务进行非事务性数据存储。各种 OpenJPA 属性超出了本书的范围,但它们被包含在这里以提供示例配置。上一个示例中更有趣的部分是数据源的引用。由于 persistence.xml 文件只知道资源的 JNDI 查询语法,因此需要以 JNDI 方式引用数据源的 OSGi 服务。这导致 osgi:service 是一个提供 javax.sql.DataSource 接口的 OSGi 服务的 JNDI 查询,其中过滤器匹配 osgi.jndi.service.name 值,相当于 jdbc/demo

  5. 下一步是创建 Blueprint 描述符文件。在你的项目中,创建 src/main/resources/OSGI-INF 目录树。然后,我们将在这个文件夹中创建一个名为 blueprint.xml 的文件,如下所示:

    <blueprint default-activation="eager"
    
          >
    
      <!-- Define RecipeBookService Services, and expose them. -->
      <bean id="recipeBookService" class="com.packt.jpa.demo.dao.RecipeBookServiceDAOImpl">
        <jpa:unit property="entityManagerFactory" unitname="recipe" />
      </bean>
    
      <service ref="recipeBookService" interface="com.packt.jpa.demo.api.RecipeBookService" />
    
      <bean id="dataSource" class="org.apache.derby.jdbc.ClientDataSource" >
        <property name="databaseName" value="demo"/>
        <property name="createDatabase" value="create"/>
      </bean>
    
      <service id="demoDataSource" ref="dataSource" interface="javax.sql.DataSource">
        <service-properties>
          <entry key="osgi.jndi.service.name" value="jdbc/demo"/>
          <entry key="transactional" value="false"/>
        </service-properties>
      </service>
    </blueprint>
    

    你应该注意在 blueprint.xml 文件中将 DataSource 服务作为 OSGi 服务包含在内。它包含服务属性 osgi.jndi.service.name,其值为 jdbc/demo。这是 persistence.xml 文件所引用的 DataSource 服务。

  6. 下一步是开发具有 JPA 后端的 OSGi 服务。我们已经创建了基本的项目结构,并配置了持久化和 Blueprint 描述符的配置;现在我们将专注于我们 JPA 支持的应用程序的底层 Java 代码。我们将此过程分解为以下三个步骤:

    1. 第一步是定义服务接口。服务接口将定义我们项目的用户 API。在我们的示例代码中,我们实现了一个 RecipeBookService 类,它提供了与一系列食谱交互所需的方法。这在上面的代码中显示:

      package com.packt.jpa.demo.api;
      
      import java.util.Collection;
      import com.packt.jpa.demo.entity.Recipe;
      
      public interface RecipeBookService {
      
        public Collection<Recipe> getRecipes();
      
        public void addRecipe(String title, String ingredients);
      
        public void deleteRecipe(String title);
      
      }
      

      接口的实现遵循标准的 Java 规范,不需要特殊的 OSGi 包。

    2. 下一步是实现服务 DAO。现在我们已经定义了服务接口,我们将提供一个作为 DAO 的实现。考虑以下代码:

      public class RecipeBookServiceDAOImpl implements RecipeBookService {
      
        @PersistenceUnit(unitName="recipe")
        private EntityManagerFactory factory;
      
        public void setEntityManagerFactory(EntityManagerFactory factory) {
          this.factory = factory;
        }
      
        @Override
        public List<Recipe> getRecipes() {
          List<Recipe> result = new ArrayList<Recipe>();
          EntityManager entityManager = factory.createEntityManager();
          EntityTransaction entityTransaction = entityManager.getTransaction();
          entityTransaction.begin();
          result = entityManager.createQuery("select r from RECIPE r", Recipe.class).getResultList();
          entityTransaction.commit();
          return result;
        }
      
        @Override
        public void addRecipe(String title, String ingredients) {
          EntityManager entityManager = factory.createEntityManager();
          EntityTransaction entityTransaction = entityManager.getTransaction();
          entityTransaction.begin();
          entityManager.persist(new Recipe(title, ingredients));
          entityTransaction.commit();
      }
      
        @Override
        public void deleteRecipe(String title) {
          EntityManager entityManager = factory.createEntityManager();
          EntityTransaction entityTransaction = entityManager.getTransaction();
          entityTransaction.begin();
          entityManager.remove(entityManager.getReference(Recipe.class, title));
          entityTransaction.commit();
        }
      }
      

      EntityManagerFactory 函数将通过 Blueprint 连接到我们的 DAO。我们 DAO 服务实现中实现的每个服务方法都需要管理 JPA 风格的事务元素。

    3. 下一步是实现实体。最后,我们按照以下代码实现我们的实体:

      package com.packt.jpa.demo.entity;
      
      import javax.persistence.Column;
      import javax.persistence.Entity;
      import javax.persistence.Id;
      import javax.persistence.Table;
      
      @Entity( name = "RECIPE" )
      @Table( name = "RECIPES" )
      public class Recipe {
      
        @Id
        @Column(nullable = false)
        private String title;
      
        @Column(length=10000)
        private String ingredients;
      
        public Recipe() {
        }
      
        public Recipe(String title, String ingredients) {
          super();
          this.title = title;
          this.ingredients = ingredients;
        }
      
        public String getTitle() {
          return title;
        }
        public void setTitle(String title) {
          this.title = title;
        }
        public String getIngredients() {
          return ingredients;
        }
        public void setIngredients(String ingredients) {
          this.ingredients = ingredients;
        }
      
        public String toString() {
          return "" + this.title + " " + this.ingredients;
        }
      }
      

      我们的 Entity 类是我们对象满足其持久化存储定义要求的地方。要将对象存储到数据库中,我们必须用表、列等术语描述其存储,并重写 equalshashCode 方法。

  7. 下一步是可选的创建 Karaf 命令以直接测试持久化服务。为了简化对 RecipeBookService 类的手动测试,我们可以创建一组自定义 Karaf 命令,这些命令将测试我们的基于 JPA 的数据存储和检索操作。这些命令的示例实现可在本书的网站上找到。特别值得注意的是,它们如何获取 RecipeBookService 类的引用并对服务进行调用。现在,我们必须通过 Blueprint 将命令实现连接到 Karaf,如下所示:

    <!-- Apache Karaf Commands -->
    <command-bundle >
      <command>
        <action class="com.packt.jpa.demo.commands.AddRecipe">
          <property name="recipeBookService" ref="recipeBookService"/>
        </action>
      </command>
      <command>
        <action class="com.packt.jpa.demo.commands.RemoveRecipe">
          <property name="recipeBookService" ref="recipeBookService"/>
        </action>
      </command>
      <command>
        <action class="com.packt.jpa.demo.commands.ListRecipes">
          <property name="recipeBookService" ref="recipeBookService"/>
        </action>
      </command>
    </command-bundle>
    

    我们每个自定义命令的实现类都连接到我们的 recipeBookService 实例。

  8. 下一步是将项目部署到 Karaf。在 Karaf 中部署我们的应用程序需要以下三个步骤:为后端数据库安装 JDBC 驱动程序、安装 JNDI 以及将我们的项目包添加到容器中。对于我们的示例项目,我们将使用 Apache Derby 作为我们的 JDBC 提供者。

    1. 我们可以使用以下命令安装 JDBC 驱动程序:

      karaf@root()> install -s mvn:org.apache.derby/derbyclient/10.8.1.2
      
      

      安装完成后,您可以通过执行类命令并 grep ClientDataSource 实现来检查客户端驱动程序是否可用。

      小贴士

      此演示需要一个正在运行的 Derby 数据库实例。请参阅 db.apache.org/derby/papers/DerbyTut/install_software.html 以获取安装 Apache Derby 的简短教程。

    2. 我们需要使用以下命令安装 jndi 功能:

      karaf@root()> feature:install jndi
      
      

      在 Karaf 中安装 jndi 功能后,我们将能够使用 jndi:names 命令查看容器中配置的数据源。

    3. 我们通过在其 Maven 坐标上执行 install 命令来安装我们的项目包,如下所示:

      karaf@root()> install –s mvn:com.packt/jpa-only/1.0.0-SNAPSHOT
      
      

      我们可以通过执行list –t 0 | grep -i JPA-only命令来验证安装,这将列出我们项目的捆绑包状态。

  9. 最后一步是测试项目。我们现在已将大量捆绑包部署到 Karaf 容器中;我们可以使用提供的 Karaf 测试命令如下进行集成测试:

    karaf@root()> test:addrecipe "Simple Chocolate Chip Cookies" "2/3 cup butter, 1 cup brown sugar, 2 eggs, 2 tbsp milk, 2 cups flour, 1 tsp baking powder, 1/4 tsp baking soda, 1/2 tsp vanilla, 1 cup chocolate chips. Whip the butter and sugar together, then add in the eggs and beat well. In a second bowl combine the dry ingredients. Make sure to thoroughly mix together the flour, baking soda and powder. Add the dry ingredients, milk, and vanilla into the butter , sugar, and egg mixture. Beat until dough is consistent. You may now preheat your oven to 375F. Drop teaspoon full amounts of dough onto greased or lined cookie sheets. Bake for 10 to 12 minutes. This recipe should yield between three to four dozen cookies."
    Executing command addrecipe
    Recipe added!
    karaf@root()>
    
    

    使用提供的自定义 Karaf 命令,我们可以将食谱条目添加到我们的数据库中。在先前的示例中,我们添加了一个巧克力曲奇饼干的食谱。考虑以下命令行片段:

    karaf@root()> test:listrecipes
    Executing command list recipes
     Simple Chocolate Chip Cookies 2/3 cup butter, 1 cup brown sugar, 2 eggs, 2 tbsp milk, 2 cups flour, 1 tsp baking powder, 1/4 tsp baking soda, 1/2 tsp vanilla, 1 cup chocolate chips. Whip the butter and sugar together, then add in the eggs and beat well. In a second bowl combine the dry ingredients. Make sure to thoroughly mix together the flour, baking soda and powder. Add the dry ingredients, milk, and vanilla into the butter , sugar, and egg mixture. Beat until dough is consistent. You may now preheat your oven to 375F. Drop teaspoon full amounts of dough onto greased or lined cookie sheets. Bake for 10 to 12 minutes. This recipe should yield between three to four dozen cookies.
    karaf@root()>
    
    

    listrecipes自定义命令在我们的食谱数据存储上执行选择所有功能,在控制台上显示所有条目。或者,您可以使用您选择的任何 JDBC 工具来验证您的食谱条目是否已持久化到磁盘。

    小贴士

    使用 JDBC 功能简化数据源管理

    Apache Karaf 3.0 包含一个 JDBC 功能,它提供了许多与数据源交互的有用命令。尝试安装jdbc功能,然后执行以下命令:

    • karaf@root()> feature:install jdbc

    • karaf@root()> jdbc:datasources

    • karaf@root()> jdbc:tables jdbc/demo

    • karaf@root()> jdbc:query jdbc/demo "select * from RECIPES"

      这些命令的输出未格式化为小控制台,因此您可能需要扩展您的终端以舒适地显示结果。

最后,即使在重启 Karaf 之后,您的所有食谱条目也将可用。

如何工作…

从一个高级的角度观察,我们的持久化层通过在 Karaf 容器中集成几个 Apache Aries 库和 OpenJPA 来工作。以下图表显示了这一高级视图:

如何工作…

此设计中的关键交互是持久化单元和 Blueprint 描述符的发现和连接,以及实体和数据访问对象的连接。这导致我们的持久化捆绑包与 JPA 服务实现(OpenJPA)交互,反过来,JPA 服务实现与持久化单元中连接到我们的 DAO 服务实现的 EntityManagerFactory 接口交互。以下图表展示了这些关键部署工件:

如何工作…

从我们的持久化功能捆绑包的架构角度来看,其操作取决于捆绑包清单、持久化配置和 Blueprint 连接的交互。

Meta-Persistence清单头触发捆绑包持久化配置的处理,反过来配置 JNDI 数据源声明、实体发现和 JDBC 集成。

Blueprint 描述符将我们的 DAO 服务接口和实现连接起来,并进一步定义数据源。此服务连接的关键特性是将持久化单元连接到 DAO 服务实现。DAO 服务使用持久化单元的EntityManagerFactory功能来启用实体访问、操作和事务。

参见

  • 在 Karaf 中构建具有持久化层和交易支持的部署项目 菜谱

在 Karaf 中构建具有持久化层和交易支持的部署项目

通过引入 Apache Aries JTA 模块到您的项目中,可以简单地添加对您的 JPA 持久化数据的交易支持。JTA 组件提供了一个事务管理器,用于协调数据存储和检索。

在 Karaf 中部署项目时使用持久化层的构建 菜谱中,我们学习了如何使用 JPA 和 OpenJPA 通过RecipeBookService类构建一个简单的应用程序,将菜谱持久化到数据库。在这个菜谱中,我们将通过 JTA 添加基于容器的交易管理。

准备工作

此菜谱的成分包括 Apache Karaf 发行套件、对 JDK 的访问和互联网连接。此菜谱的示例代码可在github.com/jgoodyear/ApacheKarafCookbook/tree/master/chapter7/chapter7-recipe4找到。通常,您还需要执行 在 Karaf 中部署项目时使用持久化层的构建 菜谱和 在 Apache Karaf 中安装 Apache Aries JTA 模块 菜谱中概述的步骤。

注意

在尝试此菜谱之前,请先卸载 在 Karaf 中部署项目时使用持久化层的构建 菜谱的内容,因为可能会发生冲突。

如何操作…

给定一个基于 JPA 的项目,如 在 Karaf 中部署项目时使用持久化层的构建 菜谱中概述的,我们可以通过以下五个步骤添加 Java 交易支持:

  1. 第一步是更新持久化到 JTA。首先,我们将交易类型从RESOURCE_LOCAL更改为JTA;这将项目从自我管理持久化转换为容器管理。以下代码展示了这一点:

    <persistence-unit name="recipe" transaction-type="JTA">
    
      <provider>org.apache.openjpa.persistence.PersistenceProviderImpl</provider>
    
      <jta-data-source>
        osgi:service/javax.sql.XADataSource/(osgi.jndi.service.name=jdbc/demoxa)
      </jta-data-source>
      <non-jta-data-source>
        osgi:service/javax.sql.DataSource/(transactional=false)
      </non-jta-data-source>
    
      <class>com.packt.jpa.demo.entity.Recipe</class>
      <exclude-unlisted-classes>true</exclude-unlisted-classes>
    

    在更改交易类型后,我们需要添加一个jta-data-source。上一个示例将jdbc/demoxa注册为 JNDI 上的服务。在先前的菜谱中使用的non-jta-data-source被保留,以帮助展示从基本设计到容器管理的交易的发展;在开发过程中可能被删除。

  2. 下一步是更新 Blueprint 描述符。将我们的交易类型从RESOURCE_LOCAL更改为JTA,这将改变我们连接RecipeBookService类的方式。以下代码展示了这一点:

    <!-- Define RecipeBookService Service, and expose them. -->
    <bean id="recipeBookService" class="com.packt.jpa.demo.dao.RecipeBookServiceDAOImpl">
      <tx:transaction method="*" value="Required" />
      <jpa:context property="entityManager" unitname="recipe" />
    </bean>
    

    我们将jpa属性更改为引用上下文而不是单元,并现在添加对容器提供的entityManager接口的引用。JTA 风格的交易的存在还意味着我们现在可以包括对 XA 数据源的定义。考虑以下代码:

    <bean id="xaDataSource" class="org.apache.derby.jdbc.ClientXADataSource">
      <property name="databaseName" value="demo"/>
      <property name="createDatabase" value="create" />
    </bean>
    

    我们将数据源特定的 XA 驱动程序与XADataSource服务实例连接起来,如下所示:

    <service ref="xaDataSource" 
             interface="javax.sql.XADataSource">
      <service-properties>
        <entry key="osgi.jndi.service.name" value="jdbc/demoxa"/>
        <entry key="transactional" value="true"/>
      </service-properties>
    </service>
    
    <!-- JTA Transaction Manager setup. -->
    <reference id="txManager" 
               interface="javax.transaction.TransactionManager" 
               availability="mandatory"/>
    

    最后,我们添加了对容器提供的TransactionManager接口的服务引用,并将其存在性设置为mandatory

  3. 下一步是更新服务 DAO。我们对持久性和蓝图配置进行 JTA 风格事务的更改,需要更改我们的 DAO 服务实现。我们的服务不需要创建和管理自己的事务,因为实体现在是通过容器提供的 EntityManager 函数访问的。考虑以下代码:

    public class RecipeBookServiceDAOImpl implements RecipeBookService {
    
      private EntityManager em;
    
      public void setEntityManager(EntityManager em) {
        this.em = em;
      }
    
      @Override
      public List<Recipe> getRecipes() {
        List<Recipe> result = new ArrayList<Recipe>();
        result = em.createQuery("select r from RECIPE r", Recipe.class).getResultList();
        return result;
      }
    
      @Override
      public void addRecipe(String title, String ingredients) {
        em.persist(new Recipe(title, ingredients));
      }
    
      @Override
      public void deleteRecipe(String title) {
        em.remove(em.getReference(Recipe.class, title));
      }
    }
    

    这个微妙变化的后果是,我们的服务方法得到了简化。

  4. 下一步是将项目部署到 Karaf。将我们的应用程序部署到 Karaf 需要两个步骤(除了在 在 Karaf 中部署具有持久化层的项目 配方中概述的步骤):安装事务支持和将我们的项目包添加到容器中。JTA 需要基于容器的交易管理器;我们可以使用以下命令安装一个:

    karaf@root()> feature:install transaction
    
    

    安装完成后,我们现在可以使用以下命令部署我们的项目包:

    karaf@root()>  install –s mvn:com.packt/jpa-jta/1.0.0-SNAPSHOT
    
    

    我们可以通过执行 list –t 0 | grep -i JPA-JTA 命令来验证安装,这将列出我们项目的包状态。

  5. 最后一步是测试项目。我们现在已经将大量包部署到 Karaf 容器中;我们可以使用提供的 Karaf 测试命令来测试我们的集成,如下所示:

    karaf@root()> test:addrecipe "Recipe-Title" "Ingredients."
    Executing command addrecipe
    Recipe added!
    karaf@root()>
    
    

    在之前的代码中,我们已经在我们的配方数据存储中添加了一个简单的占位符条目。现在,考虑以下命令行片段:

    karaf@root()> test:listrecipes
    Executing command list recipes
    Recipe-Title Ingredients.
    karaf@root()>
    
    

    发出 listrecipes 命令,我们可以检索到之前的条目。

    小贴士

    在 Karaf 中部署具有持久化层的项目 配方的 JDBC 功能说明中查看,以验证数据源信息。

它是如何工作的...

这个启用持久性的包的操作与我们之前的配方非常相似。关键变化是引入了 JTA 事务管理器,将持久化单元更新为 JTA,添加 XA 数据源,然后将 EntityManager 函数连接到 DAO 服务实现,并要求事务管理器的可用性。

在操作上,事务是由容器的交易管理器协调的——这允许比标准 JPA 风格事务更复杂的事务,并涉及更多种类的资源。

参见

  • 对于另一种持久化层方法,请参阅第八章,使用 Apache Cassandra 提供大数据集成层

第八章。使用 Apache Cassandra 提供大数据集成层

在本章中,我们将涵盖以下主题:

  • 在 Apache Karaf 中安装 Cassandra 客户端包。

  • 使用 Apache Cassandra 建模数据。

  • 在 Karaf 中构建具有持久化层的项目以进行部署。

简介

如前几章所示,持久性是大多数部署和应用程序的重要组成部分。到目前为止,我们一直专注于关系数据库。让我们从一些历史开始。

在 1970 年,IBM 发表了一篇名为大型共享数据银行的关系数据模型的论文。这篇论文成为了关系数据库管理系统(RDBMS)和现代关系数据库的基础,因为它描述了实体之间的连接和关系。从这个工作出发,随后出现了 SQL(1986 年)、ACID(原子性、一致性、隔离性和持久性)、模式设计和分片以实现可扩展性。

让我们快进到社交网络的兴起;一个基于 Reed 定律的术语WebScale被提出,该定律指出:

"大型网络,尤其是社交网络,其效用可以随着网络规模的指数级增长。"

这是否意味着 RDBMS 不能进行扩展?不,但它导致了 NoSQL 的发展。NoSQL 通常基于以下定义:

  • 它最初是由 Carlo Strozzi 提出的,他在 1998 年开发了 Strozzi NoSQL 数据库。

  • 它通常具有列/表中的键/值存储风格。

  • 它通常是模式无关的,或者每一行可以包含不同的结构。

  • 它不需要使用 SQL 作为语言;因此得名NoSQL

  • 许多支持 BASE 一致性。

  • 大多数都是分布式和容错的。

Apache Cassandra 最初在 Facebook 开发,于 2008 年作为开源发布,2009 年在 Apache 孵化,并于 2010 年成为 Apache 顶级项目。通过快速适应和许多用例中期望的特性,Apache Cassandra 迅速获得了牵引力和广泛分布。Cassandra 的当前版本在引入Cassandra 查询语言CQL)后,具有略微严格的模式导向,这是一种帮助推动从传统的 RDBMS 模型到更无结构的键/值对存储模型过渡的方式,同时保留了用户普遍熟悉的结构和数据模型。有关 Apache Cassandra 过渡的注释历史,请参阅www.datastax.com/documentation/articles/cassandra/cassandrathenandnow.html

CQL 是 Cassandra 数据库管理系统(DBMS)的默认和主要接口。使用 CQL 与使用 SQL 相似。CQL 和 SQL 共享相同的抽象概念,即由列和行构成的表。主要区别在于 Cassandra 不支持连接或子查询,除了通过 Apache Hive 进行批量分析。相反,Cassandra 强调通过 CQL 的集合和聚类等特性在模式级别进行去规范化。

这基本上意味着还有其他客户端 API——截至 Cassandra 发布 2.x 版本,Cassandra 社区积极不建议使用它们。关于使用模式建模与列族使用的健康辩论仍然在邮件列表和用户社区中非常活跃。

在 Apache Karaf 中安装 Cassandra 客户端包

在我们开始探索如何构建基于 Cassandra 的应用程序之前,我们必须首先将所有必需的客户端模块安装到 Karaf 容器中。

准备工作

Cassandra 社区的官方 GettingStarted 文档可以在 wiki.apache.org/cassandra/GettingStarted 找到。

这个菜谱的成分包括 Apache Karaf 分发套件、对 JDK 的访问和互联网连接。我们还假设 Apache Cassandra 数据库已下载并安装。Apache Cassandra 可以作为 RPM、Debian .deb 包或 .tar 归档下载和安装。在二进制 .tar 归档中,您需要打开并更改 conf 文件夹中的两个配置文件:cassandra.yamllog4-server.properties 文件。更改涉及数据存储的位置,默认情况下数据后端存储在 /var/lib/cassandra/* 文件夹中,系统日志存储在 /var/log/cassandra/* 文件夹中。一旦完成这些更改,您可以使用 bin/cassandra –F 命令启动 Cassandra。

如何操作...

Cassandra 的驱动程序不是标准 Karaf 功能库的一部分;因此,我们或者必须编写自己的功能,或者手动安装客户端运行所需的必要包。

我们使用以下命令将 Apache Cassandra 的驱动程序和补充包安装到 Karaf 中:

karaf@root()>install -s mvn:io.netty/netty/3.9.0.Final
karaf@root()>install -s mvn:com.google.guava/guava/16.0.1
karaf@root()>install -s mvn:com.codahale.metrics/metrics-core/3.0.2
karaf@root()>install -s mvn:com.datastax.cassandra/cassandra-driver-core/2.0.2

我们可以通过执行 list -t 0 | grep -i cass 命令来验证安装,该命令将列出 DataStax 驱动程序包。

通过这种方式,我们可以从自己的包中访问 Cassandra 驱动程序。

使用 Apache Cassandra 建模数据

在我们开始使用 Apache Cassandra 编写包之前,让我们简要看看如何使用 CQL 3.x 在 Cassandra 中建模数据。

准备工作

让我们定义一个非常简单的模式,因为我们使用 CQL,从客户端的角度来看,Cassandra 即使在内部数据存储略有不同的情况下也不是无模式的。

我们可以重用前一章中的 RecipeService 类。我们只需对其进行轻微修改以适应 Cassandra 集成。原始实体(以及通过使用 JPA)提供了一个基本的表定义,如下所示:

@Id
@Column(nullable = false)
private String title;

@Column(length=10000)
private String ingredients;

因此,在这个表中我们有两个字段:一个名为 title 的 ID 字段和一个我们称为 ingredients 的数据字段,以保持一致性和简单性。

首先,我们需要一个地方来存储这些。Cassandra 在顶级键空间中分区数据。将键空间想象成一个包含表及其规则的映射。

如何操作...

我们需要执行以下两个步骤:

  1. 第一步是启动 Cassandra 客户端。Cassandra 客户端的基本创建命令cqlsh如下所示:

    ./bin/cqlsh
    Connected to Cluster at localhost:9160.
    [cqlsh 4.0.1 | Cassandra 2.0.1 | CQL spec 3.1.1 | Thrift protocol 19.37.0]
    Use HELP for help.
    
    
  2. 下一步是创建我们的数据存储。既然我们已经启动了交互式客户端会话,我们可以创建一个键空间,如下命令所示:

    cqlsh> CREATE KEYSPACE karaf_demo WITH replication = {'class':'SimpleStrategy', 'replication_factor':1};
    
    

    之前的命令行提示符没有返回响应,表明我们已经成功创建了一个新的键空间,我们可以在此存储数据。要使用此键空间,我们需要告诉 Cassandra 我们现在将在这里工作。

    小贴士

    由于我们只有一个 Cassandra 节点,我们没有定义真实集群,也没有多个数据中心,所以我们依赖 SimpleStrategy。如果情况是这样,我们可以更改复制策略类。我们还设置了replication_factor值为1;这可以设置为更多副本,并且在安全性相关的环境中,例如存储账户信息时,这肯定应该完成。

    要切换键空间,我们发出以下USE命令:

    cqlsh> USE karaf_demo;
    cqlsh:karaf_demo>
    
    

    前述命令提示符表明我们处于karaf_demo键空间。考虑以下命令:

    cqlsh:karaf_demo> DESCRIBE tables;
    
    <empty>
    
    cqlsh:karaf_demo>
    
    

    如前述命令所示,我们在该键空间中未定义任何模式,因此我们需要定义一个表。可以按照以下方式完成:

    cqlsh:karaf_demo> CREATE TABLE RECIPES (title text PRIMARY KEY,ingredients text);
    cqlsh:karaf_demo> DESCRIBE TABLES;
    
    recipes
    
    cqlsh:karaf_demo>
    
    

    现在我们已经定义了一个表,并且该表中的列被定义为存储类型text,主键和检索令牌为title

它是如何工作的…

本书范围之外,不涉及 Apache Cassandra 底层工作原理的探讨。然而,您可以在git-wip-us.apache.org/repos/asf/cassandra.git查看其源代码。

在我们的数据存储方面,我们可以让 Cassandra 精确描述正在存储的内容。考虑以下命令行片段:

cqlsh:karaf_demo> DESCRIBE TABLE recipes;

CREATE TABLE recipes (
 title text,
 ingredients text,
 PRIMARY KEY (title)
) WITH
 bloom_filter_fp_chance=0.010000 AND
 caching='KEYS_ONLY' AND
 comment='' AND
 dclocal_read_repair_chance=0.000000 AND
 gc_grace_seconds=864000 AND
 index_interval=128 AND
 read_repair_chance=0.100000 AND
 replicate_on_write='true' AND
 populate_io_cache_on_flush='false' AND
 default_time_to_live=0 AND
 speculative_retry='NONE' AND
 memtable_flush_period_in_ms=0 AND
 compaction={'class': 'SizeTieredCompactionStrategy'} AND
 compression={'sstable_compression': 'LZ4Compressor'};

如您所见,作为数据模型师,您有很多选项可以操作。这些将影响复制、缓存、生命周期、压缩以及许多其他因素。

在 Karaf 中构建具有持久化层的项目以进行部署

应用程序开发人员通常需要在他们的项目中使用持久化层;在 Karaf 中执行此操作的一种首选方法是使用 Java Persistence API。由于我们基于现有的 JPA 项目构建,我们将尝试设置一个新的服务层并重用(复制)相同的项目模型,同时将存储后端迁移到 Apache Cassandra。这既不是完全重构也不是代码重用。技术上,我们可以将 API 部分从第七章 使用 Apache Aries 和 OpenJPA 提供持久化层 移动到一个新模块,然后重构章节以包含与 Cassandra 相关的依赖项和一组略有不同的导入。这实际上并不在 Cookbook 的范围内,因此保留了复制的结构。

在 Apache Karaf 中安装 Cassandra 客户端捆绑包的配方中,我们学习了如何将必要的 JAR 文件安装到 Karaf 中。继续这个配方,我们将使用驱动程序构建一个简单的应用程序,使用RecipeBookService类将配方持久化到数据库中,这将隐藏数据存储和检索的复杂性,使其用户难以察觉。

准备工作

这个配方的成分包括 Apache Karaf 发行套件、对 JDK 的访问和互联网连接。这个配方的示例代码可在github.com/jgoodyear/ApacheKarafCookbook/tree/master/chapter8/chapter8-recipe1找到。记住,为了使这些配方工作,你需要安装驱动程序并确保 Apache Cassandra 正在运行!

如何做到这一点……

使用 JPA 持久化层构建项目需要以下八个步骤:

  1. 第一步是生成一个基于 Maven 的捆绑项目。创建一个空的基于 Maven 的项目。一个包含基本 Maven 坐标信息和捆绑打包指令的pom.xml文件就足够了。

  2. 下一步是向 POM 文件中添加依赖项。这在上面的代码中显示:

    <dependencies>
      <dependency>
        <groupId>org.osgi</groupId>
        <artifactId>org.osgi.core</artifactId>
        <version>5.0.0</version>
      </dependency>
      <dependency>
        <groupId>org.osgi</groupId>
        <artifactId>org.osgi.compendium</artifactId>
        <version>5.0.0</version>
      </dependency>
      <dependency>
        <groupId>org.osgi</groupId>
        <artifactId>org.osgi.enterprise</artifactId>
        <version>5.0.0</version>
      </dependency>
      <!-- Cassandra Driver -->
      <dependency>
        <groupId>com.datastax.cassandra</groupId>
        <artifactId>cassandra-driver-core</artifactId>
        <version>2.0.1</version>
      </dependency>
      <!-- custom felix gogo command -->
      <dependency>
        <groupId>org.apache.karaf.shell</groupId>
        <artifactId>org.apache.karaf.shell.console</artifactId>
        <version>3.0.0</version>
      </dependency>
    </dependencies>
    

    对于 Karaf 3.0.0,我们使用 OSGi 版本 5.0.0。Cassandra 驱动程序只依赖于三个外部项目。我们很幸运,所有这些都可以作为捆绑包提供——在 Karaf 的任何版本中部署都不会有很大困难。我们的大多数依赖现在都与我们的命令相关。

  3. 下一步是添加构建插件。我们的配方只需要配置一个构建插件,即捆绑插件。我们配置maven-bundle-plugin将我们的项目代码组装成一个 OSGi 捆绑包。我们将以下插件配置添加到我们的 POM 文件中:

    <plugin>
      <groupId>org.apache.felix</groupId>
      <artifactId>maven-bundle-plugin</artifactId>
      <version>2.4.0</version>
      <extensions>true</extensions>
      <configuration>
        <instructions>
          <Bundle-SymbolicName>
                  ${project.artifactId}
          </Bundle-SymbolicName>
          <Bundle-Activator>
                  com.packt.cassandra.demo.Activator
          </Bundle-Activator>
          <Export-Package>
                  com.packt.cassandra.demo.api.*
          </Export-Package>
          <Import-Package>
            org.osgi.service.blueprint;resolution:=optional,
            org.apache.felix.service.command,
            org.apache.felix.gogo.commands,
            org.apache.karaf.shell.console,
            *
          </Import-Package>
        </instructions>
      </configuration>
    </plugin>
    

    Felix 和 Karaf 的导入是可选的 Karaf 命令所必需的。

    再次强调,与 JPA 项目相比,在pom.xml文件中我们的复杂性更少,因为我们依赖于更少的外部资源。我们只需确保我们有了正确的 Karaf 和 Felix 导入以激活我们的命令。

  4. 下一步是创建一个 Blueprint 描述符文件。在你的项目中创建目录树src/main/resources/OSGI-INF。然后,在这个文件夹中创建一个名为blueprint.xml的文件。考虑以下代码:

    <?xml version="1.0" encoding="UTF-8" standalone="no"?>
    <blueprint default-activation="eager"
    >
      <!-- Define RecipeBookService Services, and expose them. -->
      <bean id="recipeBookService" class="com.packt.cassandra.demo.dao.RecipeBookServiceDAOImpl" init-method="init" destroy-method="destroy"/>
    
      <service ref="recipeBookService" interface="com.packt.cassandra.demo.api.RecipeBookService"/>
    
      <!-- Apache Karaf Commands -->
      <command-bundle >
        <command>
          <action class="com.packt.cassandra.demo.commands.AddRecipe">
            <property name="recipeBookService" ref="recipeBookService"/>
          </action>
        </command>
        <command>
          <action class="com.packt.cassandra.demo.commands.RemoveRecipe">
            <property name="recipeBookService" ref="recipeBookService"/>
          </action>
        </command>
        <command>
          <action class="com.packt.cassandra.demo.commands.ListRecipes">
            <property name="recipeBookService" ref="recipeBookService"/>
          </action>
        </command>
      </command-bundle>
    </blueprint>
    

    在前面的 Blueprint 结构中,我们最终移除了事务管理器。记住,Cassandra 的工作方式与传统的关系型数据库不同。

  5. 下一步是开发一个带有新 Cassandra 后端的 OSGi 服务。我们已经创建了基本的项目结构,并配置了 Blueprint 描述符的配置。现在,我们将专注于我们 Cassandra 支持应用程序的底层 Java 代码。我们将这个过程分解为三个步骤:定义服务接口、实现服务 DAO 以及实现一个非常简单的类,我们将将其用作实体。

    在这个菜谱中,我们将在一个更广泛的项目中使用原始的 CQL 驱动程序。很可能像 DataMapper 或另一个 ORM-like 解决方案将会有所帮助。一个更高层次的库将有助于隐藏 Cassandra 和 Java 之间的类型转换,管理关系,并帮助通用数据访问。

    以下是一个简短的 CQL-friendly 库列表,作为起点:

    1. 第一步是定义服务接口。服务接口将定义我们的项目对用户的 API。在我们的示例代码中,我们实现了RecipeBookService类,它提供了与一系列菜谱交互所需的方法。考虑以下代码:

      package com.packt.cassandra.demo.api;
      
      import java.util.Collection;
      import com.packt.jpa.demo.entity.Recipe;
      
      public interface RecipeBookService {
      
         public Collection<Recipe> getRecipes();
      
         public void addRecipe(String title, String ingredients);
      
         public void deleteRecipe(String title);
      
      }
      

      接口的实现遵循标准的 Java 约定,不需要特殊的 OSGi 包。

    2. 下一步是实现服务 DAO。现在我们已经定义了服务接口,我们将提供一个作为 DAO 的实现。虽然使用 Cassandra 不一定需要遵循 DAO 模式,但这并不是一个坏主意,因为这将使重构现有的 JPA 代码变得相对简单和可行。我们使用相同的接口,并调整实现以与 Cassandra CQL 语法一起工作。考虑以下代码:

      public class RecipeBookServiceDAOImpl implements RecipeBookService {
      
        private Cluster cluster;
        private Session session;
      
        public void connect(String node) {
          cluster = Cluster.builder().addContactPoint(node).build();
          Metadata metadata = cluster.getMetadata();
          System.out.printf("Connected to cluster: %s\n", metadata.getClusterName());
          for (Host host : metadata.getAllHosts()) {
            System.out.printf("Datatacenter: %s; Host: %s; Rack: %s\n", host.getDatacenter(), host.getAddress(), host.getRack());
          }
          session = cluster.connect("karaf_demo");
        }
      
        public void destroy() {
          cluster.close();
        }
      
        public void init() {
          connect("127.0.0.1");
        }
      
        @Override
        public List<Recipe> getRecipes() {
          List<Recipe> result = new ArrayList<Recipe>();
          ResultSet results = session.execute("SELECT * FROM karaf_demo.recipes;");
      
          for (Row row : results) {
            Recipe recipe = new Recipe();
            recipe.setTitle(row.getString("title"));
            recipe.setIngredients(row.getString("ingredients"));
            result.add(recipe);
          }
          return result;
        }
      
        @Override
        public void addRecipe(String title, String ingredients) {
      
          ResultSet resultSet = session.execute("INSERT INTO karaf_demo.recipes (title, ingredients) VALUES ('" + title + "', '" + ingredients + "');");
          System.out.println("Result = " + resultSet);
        }
      
        @Override
        public void deleteRecipe(String title) {
          ResultSet resultSet = session.execute("DELETE from karaf_demo.recipes where title='" + title + "';");
          System.out.println("Result = " + resultSet);
        }
      }
      

      我们现在拥有的 Cassandra 类几乎是自包含的。在启动时,我们打印出一些关于我们连接的位置以及我们的集群节点所在的机架和数据中心的信息。

    3. 下一步是实现实体。在 Cassandra 的情况下,这些实体只是普通的 POJO 类。它们不再包含持久化信息。我们利用它们来符合现有的 API,并确保我们隐藏底层的 Cassandra 实现对最终用户。考虑以下代码:

      public class Recipe {
          private String title;
          private String ingredients;
          public Recipe() {
          }
          public Recipe(String title, String ingredients) {
              super();
              this.title = title;
              this.ingredients = ingredients;
          }
          public String getTitle() {
              return title;
          }
          public void setTitle(String title) {
              this.title = title;
          }
          public String getIngredients() {
              return ingredients;
          }
          public void setIngredients(String ingredients) {
              this.ingredients = ingredients;
          }
          public String toString() {
              return "" + this.title + " " + this.ingredients;
          }
      }
      

      这个类现在只是一个普通的 POJO 类,我们用它来根据已经建立的 API 传输数据。对于更复杂的映射和 ORM-like 结构,有几个 DataMappers 是基于现有的 Cassandra 数据驱动程序构建的。由于它们没有像 JPA 那样标准化,因此推荐其中一个并不容易。它们都有自己的小怪癖。

  6. 下一步是可选的创建 Karaf 命令以直接测试持久化服务。为了简化对recipeBookService实例的手动测试,我们可以创建一组自定义 Karaf 命令,这些命令将调用我们的 Cassandra 存储和检索操作。这些可选命令的示例实现可在本书的代码包中找到。特别值得注意的是它们如何获取recipeBookService实例的引用并调用服务。

    现在,我们必须通过 Blueprint 将命令实现连接到 Karaf,如下面的代码所示:

    <!-- Apache Karaf Commands -->
    <command-bundle >
      <command>
        <action class="com.packt.cassandra.demo.commands.AddRecipe">
          <property name="recipeBookService" ref="recipeBookService"/>
        </action>
      </command>
      <command>
        <action class="com.packt.cassandra.demo.commands.RemoveRecipe">
          <property name="recipeBookService" ref="recipeBookService"/>
        </action>
      </command>
      <command>
        <action class="com.packt.cassandra.demo.commands.ListRecipes">
          <property name="recipeBookService" ref="recipeBookService"/>
        </action>
      </command>
    </command-bundle>
    

    我们每个自定义命令的实现类都连接到我们的recipeBookService实例。

  7. 下一步是将项目部署到 Karaf。我们通过在其 Maven 坐标上执行install命令来安装我们的项目包,如下所示:

    karaf@root()>  install –s mvn:com.packt/chapter8-recipe1/1.0.0-SNAPSHOT
    
    

    注意

    这个演示需要一个正在运行的 Cassandra 实例!

  8. 最后一步是测试项目。一旦部署了包,你将看到一些启动信息;它可能如下所示:

    karaf@root()> Cassandra Demo Bundle stopping...
    Cassandra Demo Bundle starting...
    Connected to cluster: Cluster
    Datatacenter: datacenter1; Host: /127.0.0.1; Rack: rack1
    
    

    考虑以下命令:

    karaf@root()> test:addrecipe "Name" "Ingredients"
    karaf@root()> test:listrecipes
    
    

    当你运行前面的命令时,你将在控制台输出中看到存储的配方!

它是如何工作的...

我们的数据持久层与官方 Apache Cassandra 驱动在 Karaf 容器中一起工作。提供对 Cassandra 的访问相当简单;我们只需要查看一个外部项目,并确保我们有两个依赖项,并且可以连接到现有的 Cassandra 集群。使用 Cassandra 的关键更多在于数据建模、集群的结构化、读写次数、集群的大小以及参与节点及其使用和存储模式。

我们连接的数据驱动将为我们提供集群意识、故障转移和高可用性,以及控制数据复制因子和集群行为所需的功能。官方 Cassandra 文档可以在cassandra.apache.org/找到。

要获取 Cassandra 演变的历史背景,一个非常有用的资源可以在www.datastax.com/documentation/articles/cassandra/cassandrathenandnow.html找到。这将展示并解释为什么事情会改变,CQL 是如何产生的,以及为什么选择了某些设计。

第九章:使用 Apache Hadoop 提供大数据集成层

在本章中,我们将涵盖以下主题:

  • 在 Apache Karaf 中安装 Hadoop 客户端包

  • 从 Karaf 访问 Apache Hadoop

  • 添加与 HDFS 通信的命令以在 Karaf 中部署

引言

为了继续构建比传统 RDBMS 结构更灵活的数据存储模型,我们将探讨 Apache Hadoop。Hadoop 是由 Doug Cutting 和 Mike Cafarella 于 2005 年创建的。当时在 Yahoo!工作的 Cutting 以他儿子的玩具大象命名了它。它最初是为了支持 Nutch 搜索引擎项目的分布式而开发的。

Hadoop 遵循了 Google 在其关于 Google 文件系统和 Google MapReduce 的论文中发表的思想。经过十多年的使用,Hadoop 已经发展成为一个非常庞大且复杂的生态系统,预计 2016 年的收入约为 230 亿美元。Hadoop 推动了从重新包装的发行版到完整的数据库实现、分析包和管理解决方案的一切。

Hadoop 也开始改变初创公司对数据模型的认识,使新公司能够将大数据作为其整体战略的一部分。

在 Hadoop 的核心,你有Hadoop 分布式文件系统HDFS)。这个机制是允许数据分布的。它从潜在的单一故障点场景发展到有来自 DataStax 和 RedHat 等公司的竞争性实现,分别是 Cassandra FS 和 RedHat Cluster File System。

在完整产品解决方案的领域,你可以找到 MapR、Cloudera、Hortonworks、Intel 和 IBM 等作为 Apache Hadoop 发行版的替代品。

如果你思考未来,似乎 SQL-like 技术与分布式存储的结合将是大多数用例的发展方向。这使用户至少能够利用已经在组合中尝试过的 RDBMS 思想,结合几乎无限的数据挖掘、社交网络、监控和决策制定的数据存储。

随着 YARN(集群资源管理)成为 Hadoop 基础设施的一部分,HDFS 不再仅仅是存储和 MapReduce,而是一个可以处理批量、交互式、流式以及应用部署的环境。

启动一个独立的 Hadoop 集群

为了开始这一过程,我们将首先设置一个简单的独立集群。我们需要下载和配置一个 Apache Hadoop 版本,并确保我们可以使用它。然后,我们将继续在 Apache Karaf 容器中配置相同的配置和访问方法。我们将利用外部集群来展示如何使用 Apache Karaf 启动一个针对大型现有集群的新作业引擎。凭借我们拥有的和将要部署的功能,您还可以从 Karaf 容器中嵌入 HDFS 文件系统。

从 Apache 的镜像之一下载 Hadoop 版本,请访问 hadoop.apache.org/releases.html#Download。在撰写本书时,最新版本是 2.4.0。设置集群的完整指南可以在 hadoop.apache.org/docs/current/hadoop-project-dist/hadoop-common/SingleCluster.html 找到。

以下是你需要做出的更改,以便让 HDFS 启动并能够与本地安装的节点、复制处理程序和作业跟踪器通信。

展开下载的存档,并修改 etc/hadoop/* 文件夹中的文件。core-site.xml 文件需要按照以下方式修改:

<configuration>
  <property>
    <name>fs.defaultFS</name>
    <value>hdfs://localhost:9000</value>
  </property>
</configuration>

需要按照以下方式修改 hdfs-site.xml 文件:

<configuration>
  <property>
    <name>dfs.replication</name>
    <value>1</value>
  </property>
</configuration>

需要按照以下方式修改 mapred-site.xml 文件:

<configuration>
  <property>
    <name>mapred.job.tracker</name>
    <value>localhost:9001</value>
  </property>
</configuration>

一旦完成,并且你可以无密码运行 SSH 到你的本地主机,你就可以启动你的守护进程。如果你不能运行 SSH,请运行以下命令:

ssh-keygen -t dsa -P '' -f ~/.ssh/id_dsa 
cat ~/.ssh/id_dsa.pub >> ~/.ssh/authorized_keys

之前的命令将创建一个用于远程登录的空 SSH 密钥。如果你已经有一个与你的账户关联的现有 SSH 密钥,你只需要运行第二个命令以确保你可以远程登录到你的本地主机。

现在,让我们验证现有的安装是否可访问,并且我们可以对其进行操作。一旦完成,我们可以使用以下命令一次性启动所有守护进程:

./sbin/start-all.sh

注意

请记住,这个命令可能会随着你未来配置更多的 YARN 选项而消失。

你应该会看到几个守护进程启动,并且希望除了可能与你缺失的本地库相关的警告之外没有错误信息。(这在本小教程中没有涉及,但与 IO 库和针对你特定平台的优化访问有关。)

当我们有一个运行的 HDFS 系统时,我们首先会使用以下命令创建一个目录:

 ./bin/hadoop fs -mkdir -p /karaf/cookbook

然后,我们使用以下命令验证我们是否可以读取:

./bin/hadoop fs -ls /karaf
Found 1 items
drwxr-xr-x   - joed supergroup          0 2014-05-15 21:47 /karaf/cookbook

我们在我们启动的简单单节点集群中创建了一个目录(我们称它为集群,因为所有必要的集群组件都在运行,我们只是在单个节点上运行它们)。我们还确保我们可以列出该目录的内容。这告诉我们 HDFS 是可访问和活跃的。

以下是我们迄今为止所取得的成果以及我们将要介绍的内容。

  • 我们现在有一个正在运行的 Karaf 外部 HDFS 系统。这可能是一个现有的部署、一个亚马逊作业或一组虚拟服务器。基本上,我们拥有了集群的基础设施,并且我们知道我们可以访问它并创建内容。

  • 下一步将深入探讨将现有的部署模型转换为 OSGi 兼容的部署。

在 Apache Karaf 中安装 Hadoop 客户端包

随着 Hadoop 的运行,我们已准备好开始利用 Apache Karaf 的资源。

准备工作

本食谱的成分包括 Apache Karaf 发行套件、对 JDK 的访问权限和互联网连接。我们还假设已经下载并安装了 Apache Hadoop 发行版。可以从hadoop.apache.org/#Download下载。

如何做到这一点…

Hadoop 的 HDFS 库不是标准 Karaf 功能库的一部分;因此,我们或者需要编写自己的功能,或者手动安装客户端运行所需的必要包。Apache Camel 确实通过 Camel 的 HDFS2 组件提供了这一功能。我们可以使用 Camel 现有的功能,或者自己构建这个功能。

在 Camel 功能中使用的当前版本的 Snappy Java,使用 Java 7 运行原生库时会出现问题。这是一个已知的问题,并且正在 Snappy Java 的 1.0.5 版本中解决(github.com/xerial/snappy-java)。为了解决所有平台上的这个问题,我们将构建自己的 Karaf 功能,其中我们可以利用 Camel 功能中的所有包以及一些额外的 JAR 文件,这将使我们能够运行最新版本。这可以通过以下方式完成:

<features name="com.packt.chapter.nine-${project.version}">

  <repository>mvn:org.apache.cxf.karaf/apache-cxf/3.0.0/xml/features</repository>

  <feature name='xml-specs-api' version='${project.version}' resolver='(obr)' start-level='10'>
    <bundle dependency='true'>mvn:org.apache.servicemix.specs/org.apache.servicemix.specs.activation-api-1.1/</bundle>
    <bundle dependency='true'>mvn:org.apache.servicemix.specs/org.apache.servicemix.specs.stax-api-1.0/</bundle>
    <bundle dependency='true'>mvn:org.apache.servicemix.specs/org.apache.servicemix.specs.jaxb-api-2.2/</bundle>
    <bundle>mvn:org.codehaus.woodstox/stax2-api/</bundle>
    <bundle>mvn:org.codehaus.woodstox/woodstox-core-asl/</bundle>
    <bundle>mvn:org.apache.servicemix.bundles/org.apache.servicemix.bundles.jaxb-impl/</bundle>
  </feature>

  <feature name='hdfs2' version='${project.version}' resolver='(obr)' start-level='50'>
    <feature>xml-specs-api</feature>
    <feature>cxf-jaxrs</feature>
    <bundle dependency='true'>mvn:commons-lang/commons-lang/2.6</bundle>
    <bundle dependency='true'>mvn:com.google.guava/guava/16.0.1</bundle>
    <bundle dependency='true'>mvn:com.google.protobuf/protobuf-java/</bundle>
    <bundle dependency='true'>mvn:org.apache.servicemix.bundles/org.apache.servicemix.bundles.guice/</bundle>
    <bundle dependency='true'>mvn:org.apache.servicemix.bundles/org.apache.servicemix.bundles.jsch/</bundle>
    <bundle dependency='true'>mvn:org.apache.servicemix.bundles/org.apache.servicemix.bundles.paranamer/</bundle>
    <bundle dependency='true'>mvn:org.apache.servicemix.bundles/org.apache.servicemix.bundles.avro/1.7.3_1</bundle>
    <bundle dependency='true'>mvn:org.apache.commons/commons-compress/</bundle>
    <bundle dependency='true'>mvn:org.apache.commons/commons-math3/3.1.1</bundle>
    <bundle dependency='true'>mvn:commons-cli/commons-cli/1.2</bundle>
    <bundle dependency='true'>mvn:commons-configuration/commons-configuration/</bundle>
    <bundle dependency='true'>mvn:org.apache.servicemix.bundles/org.apache.servicemix.bundles.commons-httpclient/</bundle>
    <bundle dependency='true'>mvn:io.netty/netty/3.9.2.Final</bundle>
    <bundle dependency='true'>mvn:org.codehaus.jackson/jackson-core-asl/1.9.12</bundle>
    <bundle dependency='true'>mvn:org.codehaus.jackson/jackson-mapper-asl/1.9.12</bundle>
    <bundle dependency="true">mvn:org.codehaus.jackson/jackson-jaxrs/1.9.12</bundle>
    <bundle dependency="true">mvn:org.codehaus.jackson/jackson-xc/1.9.12</bundle>
    <bundle dependency='true'>mvn:org.apache.servicemix.bundles/org.apache.servicemix.bundles.snappy-java</bundle>
    <bundle dependency='true'>mvn:commons-codec/commons-codec/</bundle>
    <bundle dependency='true'>mvn:commons-collections/commons-collections/3.2.1</bundle>
    <bundle dependency='true'>mvn:commons-io/commons-io/</bundle>
    <bundle dependency='true'>mvn:commons-net/commons-net/</bundle>
    <bundle dependency='true'>mvn:org.apache.zookeeper/zookeeper/</bundle>
    <bundle dependency='true'>mvn:org.apache.servicemix.bundles/org.apache.servicemix.bundles.xmlenc/0.52_1</bundle>
    <bundle dependency='true'>mvn:org.apache.servicemix.bundles/org.apache.servicemix.bundles.xerces/</bundle>
    <bundle dependency='true'>mvn:org.apache.servicemix.bundles/org.apache.servicemix.bundles.xmlresolver/</bundle>
    <bundle>mvn:org.apache.servicemix.bundles/org.apache.servicemix.bundles.hadoop-client/</bundle>
  </feature>

</features>

我们现在可以利用这个功能的文件来部署一个 OSGi 友好的 Hadoop 客户端,这将使我们能够利用 Hadoop 中的所有功能。我们通过利用另一个项目 Apache ServiceMix 来实现这一点。Apache ServiceMix 维护一个包仓库,其中常用的或寻求的资源被重新打包,并在必要时转换为工作的 OSGi 包。

我们正在使用的Apache ServiceMix :: Bundles :: hadoop-client包是一个包含核心、YARN、HDFS、MapReduce、通用客户端 JAR 文件和 Hadoop 注解的 uber 包。

我们可以通过执行以下list | grep –i hadoop命令来验证安装:

karaf@root()> list | grep -i hadoop
151 | Active |  50 | 2.4.0.1     | Apache ServiceMix :: Bundles :: hadoop-client

从 Karaf 访问 Apache Hadoop

在 Hadoop 中,集群的核心是分布式和复制的文件系统。我们已经运行了 HDFS,并且可以从我们的命令行窗口以普通用户身份访问它。实际上,从 OSGi 容器访问它将比仅仅编写 Java 组件要复杂一些。

Hadoop 要求我们为我们的集群提供配置元数据,这些元数据可以作为文件或类路径资源进行查找。在本食谱中,我们将简单地复制我们在本章早期创建的 HDFS 特定文件到我们的src/main/resources文件夹中。

我们还将通过从依赖项中复制默认元数据定义,将它们包含到我们的资源中,最后,我们将允许我们的包类加载器执行完全动态的类加载。总结一下,我们必须将core-site.xmlhdfs-site.xmlmapred-site.xml文件复制到我们的类路径中。这些文件共同描述了我们的客户端如何访问 HDFS。

当我们到达代码时,我们还将执行一个步骤来欺骗 Hadoop 类加载器利用我们的包类加载器,并通过提供特定的实现来尊重配置数据。

如何做到这一点...

我们首先要确保必要的默认值被复制到我们的教程包中。

  1. 首先,我们将修改 Felix 包插件并添加以下段:

    <Include-Resource>
      {maven-resources},
      @org.apache.servicemix.bundles.hadoop-client-2.4.0_1.jar!/core-default.xml,
      @org.apache.servicemix.bundles.hadoop-client-2.4.0_1.jar!/hdfs-default.xml,
      @org.apache.servicemix.bundles.hadoop-client-2.4.0_1.jar!/mapred-default.xml,
      @org.apache.servicemix.bundles.hadoop-client-2.4.0_1.jar!/hadoop-metrics.properties
    </Include-Resource>
    
  2. 接下来,我们将为按需动态加载类添加另一个部分。这并不一定是 OSGi 中的最佳实践,但有时,这是实现非 OSGi 设计意图的包和 JAR 文件工作的几种可能方法之一。我们通过向 Felix 包插件添加另一个小片段来实现这一点,如下所示:

    <DynamicImport-Package>*</DynamicImport-Package>
    

    通过这个添加,我们告诉我们的包,如果您以后需要某个类,只需在我们需要时查找即可。这稍微有点成本,并且绝对不推荐作为一般做法,因为它迫使包扫描类路径。

  3. 最后,我们在实现代码中使用了两个额外的技巧。我们这样做是因为 Hadoop 代码是多线程的,默认情况下,新线程的类加载器是系统类加载器。在 OSGi 环境中,系统类加载器将具有非常有限的可见性。

    1. 首先,我们按照以下方式替换ThreadContextClassLoader类:

      ClassLoader tccl = Thread.currentThread().getContextClassLoader();
        try {
          Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
          .
        } catch (IOException e) {
          e.printStackTrace();
        } finally {
          Thread.currentThread().setContextClassLoader(tccl);
        }
      
    2. 其次,由于我们正在使用 Maven,并且不幸的是,Hadoop 根据您的实现重复使用相同的文件用于 SPI,我们无法简单地复制资源。我们的聚合 JAR 文件以及我们导入的任何依赖项都将被最后导入的版本覆盖。

      为了解决这个问题,我们明确告诉我们的Configuration对象在访问我们的集群时应使用哪些实现。这在下述代码中显示:

      Configuration conf = new Configuration();
        conf.set("fs.hdfs.impl", org.apache.hadoop.hdfs.DistributedFileSystem.class.getName());
        conf.set("fs.file.impl", org.apache.hadoop.fs.LocalFileSystem.class.getName());
      

通过前面的操作,我们已经满足了所有类加载和实例化问题。现在,我们实际上已经准备好远程访问 HDFS。然而,我们还没有强迫我们的包导入和导出所有的 Hadoop,但我们可以相当容易地更改版本,如果需要,我们还可以外部化定义集群的静态 XML 文件。

它是如何工作的

我们基本上欺骗了我们的包,使其能够在一个正常的类路径提供的 JVM 中模仿一个单一的单一类加载器。我们通过创建一个动态从 Apache Hadoop 包导入所需资源的包来实现这一点,并确保我们的包可以访问所有必要的配置资源。我们还调整了类加载,以便 Hadoop 代码库使用我们的包类加载器来实例化新实例。

添加与 HDFS 通信的命令以在 Karaf 中部署

由于 HDFS 本质上是一个文件系统,让我们看看我们如何使用标准工具和我们迄今为止构建的包来访问它。

我们将做的是将运行中的 Karaf 容器中的一级配置文件存储到 HDFS 中。然后,我们将提供第二个命令来读取这些文件。

我们已经学会了如何构建一个针对 Hadoop 的功能,该功能负责处理与 HDFS 通信所需的所有各种依赖项,我们还稍微提前了一点,讨论了类加载和一些技巧,以便我们部署的 Hadoop 库能够协作。我们现在可以开始使用提供的库编写针对 Hadoop 的代码了。

准备工作

这个配方所需的原料包括 Apache Karaf 发行套件、对 JDK 的访问和互联网连接。这个配方的示例代码可在github.com/jgoodyear/ApacheKarafCookbook/tree/master/chapter9/chapter-9-recipe1找到。记住,你需要安装驱动程序,并且 Apache Hadoop 的 HDFS 必须运行,这些配方才能工作!

如何做到这一点...

构建一个可以访问 Hadoop 的项目需要以下步骤:

  1. 第一步是生成基于 Maven 的 bundle 项目。创建一个空的基于 Maven 的项目。一个包含基本 Maven 坐标信息和 bundle 打包指令的pom.xml文件就足够了。

  2. 下一步是将依赖项添加到 POM 文件中。可以按以下方式完成:

    <dependencies>
      <dependency>
        <groupId>org.osgi</groupId>
        <artifactId>org.osgi.core</artifactId>
        <version>5.0.0</version>
      </dependency>
      <dependency>
        <groupId>org.osgi</groupId>
        <artifactId>org.osgi.compendium</artifactId>
        <version>5.0.0</version>
      </dependency>
      <dependency>
        <groupId>org.osgi</groupId>
        <artifactId>org.osgi.enterprise</artifactId>
        <version>5.0.0</version>
      </dependency>
      <dependency>
        <groupId>org.apache.servicemix.bundles</groupId>
        <artifactId>org.apache.servicemix.bundles.hadoop-client</artifactId>
        <version>2.4.0_1</version>
      </dependency>
      <!-- custom felix gogo command -->
      <dependency>
        <groupId>org.apache.karaf.shell</groupId>
        <artifactId>org.apache.karaf.shell.console</artifactId>
        <version>3.0.0</version>
      </dependency>
    </dependencies>
    

    对于 Karaf 3.0.0,我们使用 OSGi 版本 5.0.0。Hadoop 库需要相当多的支持包。现有的 Camel 功能被用作起点,但实际上并不适用于所有平台,因此我们必须重写它以满足我们的需求。

  3. 下一步是添加构建插件。我们的配方只需要配置一个构建插件,即 bundle 插件。我们配置maven-bundle-plugin将我们的项目代码组装成一个 OSGi 包。我们在 POM 文件中添加以下插件配置:

    <plugin>
      <groupId>org.apache.felix</groupId>
      <artifactId>maven-bundle-plugin</artifactId>
      <version>2.4.0</version>
      <extensions>true</extensions>
      <configuration>
        <instructions>
          <Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName>
    
          <Export-Package>
            com.packt.hadoop.demo*
          </Export-Package>
          <Import-Package>
            org.osgi.service.blueprint;resolution:=optional,
            org.apache.felix.service.command,
            org.apache.felix.gogo.commands,
            org.apache.karaf.shell.console,
            org.apache.hadoop*,
            *
          </Import-Package>
    
          <Include-Resource>
            {maven-resources},
            @org.apache.servicemix.bundles.hadoop-client-2.4.0_1.jar!/core-default.xml,
            @org.apache.servicemix.bundles.hadoop-client-2.4.0_1.jar!/hdfs-default.xml,
            @org.apache.servicemix.bundles.hadoop-client-2.4.0_1.jar!/mapred-default.xml,
            @org.apache.servicemix.bundles.hadoop-client-2.4.0_1.jar!/hadoop-metrics.properties
          </Include-Resource>
    
          <DynamicImport-Package>*</DynamicImport-Package>
        </instructions>
      </configuration>
    </plugin>
    

    Felix 和 Karaf 导入是可选的 Karaf 命令所必需的。随着我们启用动态类加载并在类加载器周围复制资源,以便它们可用,我们正在开始得到一个更复杂的 bundle 插件。

  4. 下一步是创建一个 Blueprint 描述符文件。在项目中创建src/main/resources/OSGI-INF/blueprint目录树。然后,我们将在这个文件夹中创建一个名为blueprint.xml的文件。考虑以下代码:

    <?xml version="1.0" encoding="UTF-8" standalone="no"?>
    <blueprint default-activation="eager">
      <!-- Define RecipeBookService Services, and expose them. -->
      <bean id="hdfsConfigService" class="com.packt.hadoop.demo.hdfs.HdfsConfigServiceImpl" init-method="init" destroy-method="destroy"/>
    
      <service ref="hdfsConfigService" interface="com.packt.hadoop.demo.api.HdfsConfigService"/>
    
      <!-- Apache Karaf Commands -->
      <command-bundle >
        <command>
          <action class="com.packt.hadoop.demo.commands.ReadConfigs">
            <property name="hdfsConfigService" ref="hdfsConfigService"/>
          </action>
        </command>
        <command>
          <action class="com.packt.hadoop.demo.commands.StoreConfigs">
            <property name="hdfsConfigService" ref="hdfsConfigService"/>
          </action>
        </command>
    
      </command-bundle>
    </blueprint>
    
  5. 下一步是开发一个具有新 Hadoop 后端的 OSGi 服务。我们已经创建了基本的项目结构,并配置了 Blueprint 描述符的配置。现在,我们将专注于我们 Hadoop 后端应用程序的底层 Java 代码。我们将这个过程分解为两个步骤:定义服务接口和提供具体的实现。

    1. 首先,我们定义一个服务接口。服务接口将定义我们项目的用户 API。在我们的示例代码中,我们实现了HdfsConfigService接口,它提供了存储和检索我们 Karaf 实例中配置文件所需的方法。这可以按以下方式完成:

      package com.packt.hadoop.demo.api;
      
      public interface HdfsConfigService {
      
        static final String HDFS_LOCATION = "/karaf/cookbook/etc";
      
        void storeConfigs();
      
        void readConfigs();
      }
      

      接口的实现遵循标准的 Java 约定,不需要特殊的 OSGi 包。

    2. 接下来,我们实现与 HDFS 的通信。现在我们已经定义了我们的服务接口,我们将通过两个调用提供实现,以将文件数据存储和检索到 HDFS。这可以通过以下方式完成:

      public class HdfsConfigServiceImpl implements HdfsConfigService {
      
        private final static String BASE_HDFS = "hdfs://localhost:9000";
      
        @Override
        public void storeConfigs() {
          String KARAF_etc = System.getProperty("karaf.home") + "/etc";
          Collection<File> files = FileUtils.listFiles(new File(KARAF_etc), new String[]{"cfg"}, false);
      
          for (File f : files) {
            System.out.println(f.getPath());
      
            ClassLoader tccl = Thread.currentThread().getContextClassLoader();
            try {
              Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
              String cfg = FileUtils.readFileToString(f);
      
              Path pt = new Path(BASE_HDFS + HDFS_LOCATION + "/" + f.getName());
      
              Configuration conf = new Configuration();
              conf.set("fs.hdfs.impl", org.apache.hadoop.hdfs.DistributedFileSystem.class.getName());
              conf.set("fs.file.impl", org.apache.hadoop.fs.LocalFileSystem.class.getName());
      
              FileSystem fs = FileSystem.get(conf);
      
              BufferedWriter br = new BufferedWriter(new OutputStreamWriter(fs.create(pt, true)));
              // TO append data to a file, use fs.append(Path f)
      
              br.write(cfg);
              br.close();
            } catch (IOException e) {
              e.printStackTrace();
            } finally {
              Thread.currentThread().setContextClassLoader(tccl);
            }
          }
        }
      
        @Override
        public void readConfigs() {
          try {
      
            FileSystem fs = FileSystem.get(new Configuration());
            FileStatus[] status = fs.listStatus(new Path(BASE_HDFS + HDFS_LOCATION));
            for (int i = 0;i < status.length;i++) {
              BufferedReader br = new BufferedReader(new InputStreamReader(fs.open(status[i].getPath())));
              String line;
              line = br.readLine();
              while (line != null) {
                System.out.println(line);
                line = br.readLine();
              }
            }
          } catch (Exception e) {
            e.printStackTrace();
          }
        }
      
        public void init() {
      
        }
      
        public void destroy() {
      
        }
      }
      
  6. 下一步是可选地创建 Karaf 命令以直接测试持久化服务。为了简化对HdfsConfigService接口的手动测试,我们可以创建一组自定义 Karaf 命令,这些命令将测试我们的 HDFS 存储和检索操作。这些命令的示例实现可以在本书的网站上找到。特别值得注意的是,它们如何获取HdfsConfigService接口的引用并调用该服务。我们必须通过 Blueprint 将命令实现连接到 Karaf。这可以通过以下方式完成:

    <!-- Apache Karaf Commands -->
    <command-bundle >
      <command>
        <action class="com.packt.hadoop.demo.commands.ReadConfigs">
          <property name="hdfsConfigService" ref="hdfsConfigService"/>
        </action>
      </command>
      <command>
        <action class="com.packt.hadoop.demo.commands.StoreConfigs">
          <property name="hdfsConfigService" ref="hdfsConfigService"/>
        </action>
      </command>
    
    </command-bundle>
    

    我们每个自定义命令的实现类都连接到我们的hdfsConfigService实例。

  7. 下一步是将项目部署到 Karaf 中。

    注意

    这个演示需要一个正在运行的 Hadoop 集群!

    我们确保所有 Hadoop 捆绑包都已正确安装。这可以通过以下命令完成:

    karaf@root()> feature:repo-add mvn:com.packt/chapter-9-recipe1/1.0.0-SNAPSHOT/xml/features
    Adding feature url mvn:com.packt/chapter-9-recipe1/1.0.0-SNAPSHOT/xml/features
    
    karaf@root()> feature:install hdfs2
    karaf@root()>
    
    karaf@root()> list
    START LEVEL 100 , List Threshold: 50
     ID | State  | Lvl | Version        | Name
    --------------------------------------------------------------
    126 | Active |  50 | 2.6            | Commons Lang
    127 | Active |  50 | 16.0.1         | Guava: Google Core Libraries for Java
    128 | Active |  50 | 2.5.0          | Protocol Buffer Java API
    129 | Active |  50 | 3.0.0.1        | Apache ServiceMix :: Bundles :: guice 
    130 | Active |  50 | 0.1.51.1       | Apache ServiceMix :: Bundles :: jsch 
    131 | Active |  50 | 2.6.0.1        | Apache ServiceMix :: Bundles :: paranamer 
    132 | Active |  50 | 1.7.3.1        | Apache ServiceMix :: Bundles :: avro 
    133 | Active |  50 | 1.8.1          | Apache Commons Compress
    134 | Active |  50 | 3.1.1          | Commons Math
    135 | Active |  50 | 1.2            | Commons CLI
    136 | Active |  50 | 1.10.0         | Apache Commons Configuration
    137 | Active |  50 | 3.1.0.7        | Apache ServiceMix :: Bundles :: commons-httpclient
    138 | Active |  50 | 3.9.2.Final    | The Netty Project
    139 | Active |  50 | 1.9.12         | Jackson JSON processor
    140 | Active |  50 | 1.9.12         | Data mapper for Jackson JSON processor
    141 | Active |  50 | 1.9.12         | JAX-RS provider for JSON content type, using Jackson data binding
    142 | Active |  50 | 1.9.12         | XML Compatibility extensions for Jackson data binding
    143 | Active |  50 | 1.0.4.1_1      | Apache ServiceMix :: Bundles :: snappy-java
    144 | Active |  50 | 1.9.0          | Apache Commons Codec
    145 | Active |  50 | 3.2.1          | Commons Collections
    146 | Active |  50 | 2.4.0          | Commons IO
    147 | Active |  50 | 3.3.0          | Commons Net
    148 | Active |  50 | 3.4.6          | ZooKeeper Bundle
    149 | Active |  50 | 0.52.0.1       | Apache ServiceMix :: Bundles :: xmlenc
    150 | Active |  50 | 2.11.0.1       | Apache ServiceMix :: Bundles :: xercesImpl
    151 | Active |  50 | 2.4.0.1        | Apache ServiceMix :: Bundles :: hadoop-client
    152 | Active |  80 | 1.0.0.SNAPSHOT | Chapter 9 :: Manage Big Data with Apache Hadoop - HDFS Client Example.
    karaf@root()>
    
    

    我们通过在其 Maven 坐标上执行install命令来安装我们的项目捆绑包:

    karaf@root()>  install –s mvn:com.packt/chapter-9-recipe1/1.0.0-SNAPSHOT
    
    
  8. 最后一步是测试项目。我们可以使用以下命令来完成这项工作:

    karaf@root()> test:storeconfigs 
    karaf@root()> test:readconfigs 
    karaf@root()>
    
    

它是如何工作的…

我们现在有一个与外部 HDFS 文件系统通信的 Karaf 容器。我们可以从 Karaf 备份配置文件到 HDFS,并且可以读取它们。

可以启动一个新的 Karaf 实例来消费和复制这些配置,或者我们可以使用这个配方作为启动 MapReduce 作业、任务和批处理作业的基础。

第十章:测试 Apache Karaf 与 Pax Exam

在本章中,我们将介绍以下食谱:

  • 设置 Pax Exam 测试环境

  • 测试 Apache Karaf 功能

  • 使用 Apache Karaf 测试命令

  • 使用 Apache Karaf Pax exam 测试的覆盖率

  • 使用 Blueprint 和 Apache Karaf 测试 Apache Camel

简介

本章解释了如何为 Apache Karaf 设置测试环境。由于为 Apache Karaf 开发 OSGi 应用程序也需要彻底的测试,因此需要一个集成测试环境。Pax Exam 是一个强大的集成测试开发工具,可以与 Apache Karaf 结合使用。

提示

更多关于 Pax Exam 的详细信息可以在 OPS4j 社区网站上找到,网址为ops4j1.jira.com/wiki/display/PAXEXAM3/Pax+Exam。如果需要帮助,你可以在groups.google.com/forum/#!forum/ops4j找到一个活跃的社区。

设置 Pax Exam 测试环境

这个食谱将指导你使用 Felix 框架作为核心 OSGi 容器的基本设置 Pax Exam 测试环境。下一个食谱将介绍如何结合 Pax Exam 和 Apache Karaf。

准备工作

如往常一样,源代码可以在github.com/jgoodyear/ApacheKarafCookbook/tree/master/chapter10/chapter10-recipe1找到。

如何做到这一点...

要使用 Pax Exam 执行集成测试,POM 配置是必不可少的,因为它已经是构建环境的一部分。例如,使用pax-exam-container-native构件,当然,将 JUnit 与 Pax Exam 连接是强制性的。这可以通过以下方式完成:

…
<properties>
  <version.pax-exam>3.4.0</version.pax-exam>
  <junit.version>4.11</junit.version>
</properties>
…
<dependency>
  <groupId>org.ops4j.pax.exam</groupId>
  <artifactId>pax-exam-junit4</artifactId>
  <version>${version.pax-exam}</version>
  <scope>test</scope>
</dependency>

<dependency>
  <groupId>org.ops4j.pax.exam</groupId>
  <artifactId>pax-exam-invoker-junit</artifactId>
  <version>${version.pax-exam}</version>
  <scope>test</scope>
</dependency>

<dependency>
  <groupId>org.ops4j.pax.exam</groupId>
  <artifactId>pax-exam-container-native</artifactId>
  <version>${version.pax-exam}</version>
  <scope>test</scope>
</dependency>
…

除了这个,还需要定义运行此测试场景的 OSGi 框架。这可以是 Felix 框架、Equinox 或任何其他作为 Maven 依赖项可用的框架。

现在我们已经完成了 POM 配置的重要部分,让我们专注于 JUnit 集成测试。

这个测试类只包含两个主要方法:测试方法本身,其中我们有一个小的测试设置以确保容器正常工作,以及在这个阶段更为重要的方法——配置。考虑以下代码片段:

@ExamReactorStrategy(PerClass.class)
@RunWith(PaxExam.class)
public class TestOsgiServices {

  @Inject
  protected BundleContext bundleContext;

  @Configuration
  public static Option[] configuration() throws Exception {
    return options(
      workingDirectory("target/paxexam/"),
      cleanCaches(true),
      junitBundles(),
      frameworkProperty("osgi.console").value("6666"),
      frameworkProperty("osgi.console.enable.builtin").value("true"),
      frameworkProperty("felix.bootdelegation.implicit").value("false"),
      systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value("DEBUG"));
  }

  @Test
  public void test() {
    assertNotNull(bundleContext);
  }
}

单元测试类需要@RunWith(PaxExam.class)注解来明确它是一个 Pax Exam 测试。@ExamReactorStrategy注解允许你定义一个测试策略,可以是PerClassPerMethodPerSuite,其中你还需要定义要运行的测试类。在我们的测试中,使用PerClass策略就足够了,因为它只为每个类启动一次容器,而PerMethod策略则为每次方法调用启动测试及其设置。PerSuite策略为一系列测试启动和配置测试设置。

注意

有关测试策略的详细信息,请参阅 Pax Exam 文档,网址为 ops4j1.jira.com/wiki/display/PAXEXAM3/Pax+Exam

Pax Exam 测试类总是需要一个带有 @Configuration 注解的 configuration 方法。这是为了让 Pax Exam 知道运行测试所需的配置。例如,最好提供一个 workingDirectory 方法,否则工作目录将放置在系统的临时目录中。通过 junitBundles 方法,包括 JUnit 包,使 Pax Exam 能够运行 JUnit 测试。要使用 TestNG 运行测试,需要在 configuration 方法中添加 TestNG 的依赖项,如下所示:

mavenBundle("org.testng", "testng", "6.3.1")

使用 frameworkProperty("osgi.console") 属性,您可以为测试添加一个 OSGi 控制台;如果您以调试模式运行测试,您可以通过端口 6666 访问此控制台。这完成了 Pax Exam 测试的基本设置,其中我们已经有了一个测试方法,用于检查注入的(@Inject)bundle 上下文是否可用。

它是如何工作的...

由于 Pax Exam 通过 Maven 在类路径上找到自己的模块,它通过自己启动容器。这对于 Felix、Equinox 或任何其他由 POM 配置提供给 Pax Exam 的容器都是正确的。其他所有内容都需要使用 configuration 方法进行指定。如果您在测试场景中使用其他包,您还需要指定它们的 Maven 坐标。通常最好在您的 POM 配置中指定所涉及包的版本,并在配置中重用此版本。您将在即将到来的菜谱中了解更多信息。

那么 Pax Exam 与标准单元测试相比如何?在单元级别进行测试时,也许甚至使用模拟,测试仅关注单元本身。集成测试通常覆盖更广泛的范围或更大的范围,测试的单元更多。集成测试可能作为外部测试针对外部可用的 API 运行。如果您的 API 是容器内的服务,这将有点困难。Pax Exam 在容器内与容器一起工作。测试类将成为部署的工件的一部分。在 OSGi 环境中,Pax Exam 构建包含您的测试类的动态包,即使有包导入。这些包清单头也可以由测试进行操作;更多详细信息请参阅下一道菜谱。

还有更多...

集成测试通常测试已经构建的工件,但有时修改工件或测试在构建工件之后而不是在构建工件时是必要的。对于这种情况,可以通过配置 streamBundle 在线构建一个 动态 包。可以这样做:

streamBundle(bundle().add(Calculator.class)
  .add(CalculatorImpl.class)
  .add(CalcActivator.class)
  .set(Constants.BUNDLE_SYMBOLICNAME,"com.packt.IntegrationTest")
  .set(Constants.DYNAMICIMPORT_PACKAGE, "*")
  .set(Constants.BUNDLE_ACTIVATOR, CalcActivator.class.getName())
  .set(Constants.EXPORT_PACKAGE, "com.packt")
  .build()).start());

此示例展示了如何构建一个包含激活器并导出正确包和其他清单头条目的动态包。注册的服务可以直接导入到测试中并使用。

注意

您可能还对使用 Pax Exam 与其他环境感兴趣;它还支持 Tomcat、Jetty 或 JEE 服务器作为运行时的后端。

测试 Apache Karaf 功能

在完成 设置 Pax Exam 测试环境 食谱后,您应该准备好测试 OSGi 应用程序。现在,让我们更详细地看看使用 Apache Karaf 作为容器运行测试所需的条件。

准备工作

如前几章所述,源代码可在 github.com/jgoodyear/ApacheKarafCookbook/tree/master/chapter10/chapter10-recipe2 找到。为了完全理解这个食谱,最好已经完成了前面的食谱。

如何做到这一点...

设置 Pax Exam 测试环境 食谱中,我们定义了 Felix 框架作为运行时容器。现在,我们需要将其更改为 Apache Karaf,因此需要对 POM 配置进行第一次更改。Pax Exam 需要知道它需要以 Apache Karaf 作为容器运行;这是通过以下方式配置的:

  1. 作为 Felix 框架替代依赖的 pax-exam-container-karaf 依赖项。

  2. 作为运行时容器的 apache-karaf ZIP 艺术品。

  3. 安装标准功能的标准功能文件。

考虑以下代码:

…
<dependency>
  <groupId>org.ops4j.pax.exam</groupId>
 <artifactId>pax-exam-container-karaf</artifactId>
  <version>${version.pax-exam}</version>
  <scope>test</scope>
</dependency>
…
<!-- framework to test with -->
<dependency>
 <groupId>org.apache.karaf.features</groupId>
 <artifactId>standard</artifactId>
 <version>${karaf.version}</version>
 <type>xml</type>
 <classifier>features</classifier>
  <scope>test</scope>
</dependency>

<dependency>
  <groupId>org.apache.karaf.features</groupId>
  <artifactId>org.apache.karaf.features.core</artifactId>
  <version>${karaf.version}</version>
  <scope>test</scope>
</dependency>

<dependency>
  <groupId>org.apache.karaf.system</groupId>
  <artifactId>org.apache.karaf.system.core</artifactId>
  <version>${karaf.version}</version>
  <scope>test</scope>
</dependency>

<dependency>
  <groupId>org.apache.karaf</groupId>
 <artifactId>apache-karaf</artifactId>
  <version>${karaf.version}</version>
  <type>zip</type>
  <scope>test</scope>
  <exclusions>
    <exclusion>
      <groupId>org.apache.karaf.shell</groupId>
      <artifactId>org.apache.karaf.shell.dev</artifactId>
    </exclusion>
  </exclusions>
</dependency>
…

在对 POM 配置进行这些更改后,测试本身需要按照以下方式进行重新配置:

@ExamReactorStrategy(PerClass.class)
@RunWith(PaxExam.class)
public class IntegrationTestKaraf {

  @Inject
  protected BundleContext bundleContext;

  @Inject
  protected FeaturesService featuresService;

  @Configuration
  public static Option[] configuration() throws Exception {
    return new Option[] {
      karafDistributionConfiguration().frameworkUrl(maven().groupId("org.apache.karaf").artifactId("apache-karaf").type("zip").versionAsInProject()).unpackDirectory(new File("target/paxexam/unpack/")).useDeployFolder(false),configureConsole().ignoreLocalConsole(),logLevel(LogLevel.INFO),keepRuntimeFolder(),features(maven().groupId("org.apache.karaf.features").artifactId("standard").type("xml").classifier("features").versionAsInProject(),"eventadmin")
    };
  }

  @Test
  public void test() {
    assertNotNull(bundleContext);
  }

  @Test
  public void featuresAvailable() throws Exception {
    assertTrue(featuresService.isInstalled(featuresService.getFeature("eventadmin")));
  }

}

第一个主要变化应该会立即跳入你的眼帘:Apache Karaf 配置,其中包含 karafDistributionConfiguration 函数和指向 Apache Karaf ZIP 文件的 Maven 坐标。由于它已经在 POM 配置中定义,因此可以使用 versionAsInProject() 配置。除此之外,一个功能可以从配置中立即安装,并在容器启动并运行后立即可用。测试确保安装了预期的功能。

它是如何工作的...

Pax Exam 使用 Karaf 作为容器而不是给定的 OSGi 框架。由于容器是以 ZIP 文件的形式组装的,需要先解包,因此需要一些额外的配置。完成此操作后,Apache Karaf 容器将在指定位置启动,并使用配置的约束,例如关闭 Deploy 文件夹。versionAsInProject() 配置需要一些额外的处理。为了使其工作,您需要确保生成一个 Maven 依赖文件。这可以通过从 ServiceMix 项目配置 depends-maven-plugin 来完成。这将生成一个包含 Pax Exam 可读的 POM 配置中所有依赖信息的文件。考虑以下代码:

<plugin>
  <groupId>org.apache.servicemix.tooling</groupId>
  <artifactId>depends-maven-plugin</artifactId>
  <version>1.2</version>
  <executions>
    <execution>
      <id>generate-depends-file</id>
      <phase>generate-resources</phase>
      <goals>
        <goal>generate-depends-file</goal>
      </goals>
    </execution>
  </executions>
</plugin>

完成这些配置后,你的测试就设置好了。在给定的测试示例中,我们使用了内部功能描述符;如果你想测试任何其他类型的自定义功能,你只需将其添加到配置中,并告诉它应该部署哪个功能。在先前的示例中,是 eventadmin 功能。

除了已知的简单注入 bundlecontext 对象外,还可以将容器中可用的任何类型的服务注入到测试中。在先前的示例中,我们注入了 featuresService

使用 Apache Karaf 测试命令

在完成前两个配方后,你应该已经准备好对 OSGi 包进行彻底测试,无论是独立测试还是 Apache Karaf 中的测试。当与 Apache Karaf 一起工作时,有时还需要有新的命令。本配方将介绍如何使用命令进行测试,并确保这些命令在 Apache Karaf 壳中执行。

准备工作

在开始本配方之前最好已经阅读过 Testing Apache Karaf features 配方,因为本配方是一个后续配方。源代码可以在 github.com/jgoodyear/ApacheKarafCookbook/tree/master/chapter10/chapter10-recipe3 找到。

如何操作...

要测试 Apache Karaf 壳命令的执行,你需要调整测试类。首先,你需要改变测试的运行方式。为此,我们添加了探针构建器,它配置了测试包的构建方式。考虑以下代码:

@ProbeBuilder
public TestProbeBuilder probeConfiguration(TestProbeBuilder probe) {
  //make sure the needed imports are there. 
  probe.setHeader(Constants.DYNAMICIMPORT_PACKAGE, "*,org.apache.felix.service.*;status=provisional");
  return probe;
}

以下代码行确保 CommandProcessor 接口的注入工作正常:

@Inject
protected CommandProcessor commandProcessor;

对于实际测试命令,我们添加了一个方便的方法,该方法将命令发送到 Karaf 壳,并从中接收输出。这可以按以下方式完成:

protected String executeCommand(final String command) {
  String response;
  final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
  final PrintStream printStream = new PrintStream(byteArrayOutputStream);
  final CommandSession commandSession = commandProcessor.createSession(System.in, printStream, System.err);
  FutureTask<String> commandFuture = new FutureTask<String>(
    new Callable<String>() {
      public String call() {
        try {
          System.err.println(command);
          commandSession.execute(command);
        } catch (Exception e) {
          e.printStackTrace(System.err);
        }
        printStream.flush();
        return byteArrayOutputStream.toString();
      }
    });

  try {
    executor.submit(commandFuture);
    response = commandFuture.get(10000L, TimeUnit.MILLISECONDS);
  } catch (Exception e) {
    e.printStackTrace(System.err);
    response = "SHELL COMMAND TIMED OUT: ";
  }

  return response;
}

包含响应的字符串可以测试预期的输出。

它是如何工作的…

本测试的关键部分是添加 ProbeBuilder 注解。ProbeBuilder 注解改变了包含测试类的包的构建方式。在我们的例子中,它改变了生成的包的 Package-Import 标头。不仅可能改变或添加清单头,还可以添加额外的类或测试类。

使用 Apache Karaf Pax Exam 测试覆盖率

除了测试应用程序外,通常还需要知道单元和集成测试实际上覆盖了代码的程度。对于代码覆盖率,有一些技术可用。本配方将介绍如何设置测试环境以找到测试的覆盖率。

准备工作

在开始此后续配方之前最好已经阅读过 Testing Apache Karaf features 配方。本配方的源代码可以在 github.com/jgoodyear/ApacheKarafCookbook/tree/master/chapter10/chapter10-recipe4 找到。

如何操作...

要了解测试的覆盖率,需要一个代码覆盖率工具。我们将采用 Java 代码覆盖率库,因为它有一个用于自动化覆盖率分析的 Maven 插件。首先,将插件的 Maven 坐标添加到如下代码中:

<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.7.0.201403182114</version>

我们需要首先准备代码,以便代理可以按照以下方式覆盖它:

<execution>
  <id>prepare-agent-integration</id>
  <goals>
 <goal>prepare-agent-integration</goal>
  </goals>
  <phase>pre-integration-test</phase>
  <configuration>
    <propertyName>jcoverage.command</propertyName>
    <includes>
      <include>com.packt.*</include>
    </includes>
    <append>true</append>
  </configuration>
</execution>

这将包括 com.packt 包,包括子包。在集成测试完成后,需要按照以下方式生成测试报告:

<execution>
  <id>report</id>
  <goals>
    <goal>report-integration</goal>
  </goals>
</execution>

除了这些对 POM 配置的添加之外,你还需要将 VM 选项添加到 Apache Karaf 测试的配置中。如果不将这些选项设置到执行测试的虚拟机中,执行环境就不知道覆盖率,因此不会进行覆盖率测试。这可以按照以下方式完成:

private static Option addCodeCoverageOption() {
  String coverageCommand = System.getProperty(COVERAGE_COMMAND);
  if (coverageCommand != null) {
    return CoreOptions.vmOption(coverageCommand);
  }
  return null;
}

此覆盖率的报告看起来如下截图所示。它显示了 CalculatorImpl 类及其方法的覆盖率。虽然 add 方法已被测试调用,但 sub 方法没有被调用。这导致该方法的覆盖率为零。

如何做…

它是如何工作的…

首先,你需要为覆盖准备代理,这将插入到 jcoverage.command 属性中。此属性通过添加 vmOption 目录传递给测试。这样,覆盖率代理就被添加到 Java 虚拟机中,并跟踪测试执行的覆盖率。测试成功运行后,由 jacoco-maven-plugin 生成报告。所有这些都可以在单个 Maven 模块中正常工作。对于多模块项目设置,将需要额外的工作,特别是如果你想要结合单元和集成测试覆盖率。更多详细信息可以在 www.eclemma.org/jacoco/index.html 找到。

使用 Blueprint 和 Apache Karaf 测试 Apache Camel

此配方将介绍如何测试 Camel Blueprint 定义。本配方的重点是测试,以及它与 Testing Apache Karaf features 脚本的不同之处。

准备工作

在开始此后续配方之前,最好已经熟悉了 Testing Apache Karaf features 脚本和 Creating a Blueprint-based Camel Router for deployment in Apache Karaf 脚本。本章的源代码可在 github.com/jgoodyear/ApacheKarafCookbook/tree/master/chapter10/chapter10-recipe5 找到。

如何做…

由于我们基于 Testing Apache Karaf features 脚本创建了此配方,因此我们已经有了一个包含 Apache Karaf 容器设置的初步设置。此外,我们还需要 Apache Camel 的功能。这样,所有用于测试 Camel 路由所需的包都齐备了。测试本身需要将 blueprint.xml 定义中的 Camel Context 连接到其模拟对象。这可以按照以下方式完成:

  1. 测试类本身继承自CamelTestSupport类,以便更容易地对 Camel 进行测试:

    …
    @RunWith(PaxExam.class)
    public class TestCamelInKaraf extends CamelTestSupport {
    
      @Inject
      protected FeaturesService featuresService;
    
      @Inject
      protected BundleContext bundleContext;
    
      @Inject
      @Filter(value="(camel.context.name=blueprintContext)", timeout=10000)
      protected CamelContext testContext;
    …
    

    为了访问 Camel 路由中包含的模拟对象,我们确保 Camel 上下文被注入。@Filter注解确保在给定的超时时间内只注入所需的 Camel 上下文。

  2. 配置包含目标 Karaf 运行时,并安装所需的camel-blueprintcamel-test特性,如下所示:

    @Configuration
    public static Option[] configure() throws Exception {
      return new Option[] {
      karafDistributionConfiguration().frameworkUrl(
      maven().groupId("org.apache.karaf")
    .artifactId("apache-karaf")
    .type("zip").versionAsInProject()).useDeployFolder(false)
    .karafVersion("3.0.1")
      .unpackDirectory(new File("target/paxexam/unpack/")),
      logLevel(LogLevel.WARN),
      features(
    maven().groupId("org.apache.camel.karaf")
    .artifactId("apache-camel").type("xml")
    .classifier("features")
    .versionAsInProject(), 
    "camel-blueprint", "camel-test"),keepRuntimeFolder(),
      streamBundle(
        bundle().add(HelloBean.class)
      .add("OSGI-INF/blueprint/blueprint.xml",
      new File("src/main/resources/OSGI-INF/blueprint/blueprint.xml")
      .toURL())
    .set(Constants.BUNDLE_SYMBOLICNAME, "com.packt.camel-test")
      .set(Constants.DYNAMICIMPORT_PACKAGE, "*").build())
      .start() };
    }
    
  3. 此外,HelloBean类和blueprint.xml文件被封装在一个流式捆绑包中,以便测试可以在同一模块内运行。作为测试的最后一个特殊之处,我们确保每个类创建时 Camel 上下文只创建一次。这可以通过以下方式实现:

    @Override
    public boolean isCreateCamelContextPerClass() {
      return true;
    }
    

    这就完成了在 Karaf 容器内使用 Pax Exam 测试 Camel 路由的设置。

  4. Test方法中,我们确保所需的特性已安装,Camel 上下文已注入,并且模拟对象已满足。考虑以下代码:

    @Test
    public void test() throws Exception {
      assertTrue(featuresService.isInstalled(featuresService.getFeature("camel-core")));
      assertTrue(featuresService.isInstalled(featuresService.getFeature("camel-blueprint")));
    
      assertNotNull(testContext);
    
      MockEndpoint mockEndpoint = (MockEndpoint) testContext.getEndpoint("mock:result");
      mockEndpoint.expectedMessageCount(1);
    
      assertMockEndpointsSatisfied(10000l, TimeUnit.MILLISECONDS);
    
    }
    

它是如何工作的…

如前所述的食谱,Pax Exam 确保 Apache Karaf 作为基本容器运行。在@Configuration方法内的特性配置中,我们安装了camel-blueprintcamel-test特性。测试确保这些特性确实已安装并正在运行。为了验证HelloBean类是否确实向模拟端点发送了消息,测试从注入的 Camel 上下文中获取模拟端点并期望至少有一个消息计数。Pax Exam 将blueprint.xml文件使用的 Camel 上下文注入到测试类中。为了确保它是实际需要的上下文,使用了带有 LDAP 过滤器语法的@Filter注解。当包含 Camel 上下文的blueprint.xml文件启动时,Camel 上下文本身在 OSGi 注册表中注册为服务,Camel 上下文 ID 注册为camel.context.name属性。

由于继承和重写的isCreateCamelContextPerClass方法,Camel 上下文在测试执行期间只创建一次。这对于使用 Pax Exam 运行 Camel 测试是必要的,因为容器在测试中只创建一次。否则,Camel 上下文会被创建几次,导致各种错误。

为了断言测试的成功执行并验证预期的消息计数,您需要在模拟对象上调用assertIsSatisfied方法,而不是调用assertMockEndpointsSatisfied方法。后者方法断言绑定到测试本身的 Camel 上下文的所有模拟端点,而前者方法断言由 Blueprint 处理器创建的 Camel 上下文中包含的模拟对象。

posted @ 2025-09-11 09:45  绝不原创的飞龙  阅读(44)  评论(0)    收藏  举报