OSGi 中的 Akka

依赖

为了在 OSGi 中使用 Akka,你需要将以下依赖添加到你的项目中:

  1. <!-- Maven -->
  2. <dependency>
  3. <groupId>com.typesafe.akka</groupId>
  4. <artifactId>akka-osgi_2.12</artifactId>
  5. <version>2.5.23</version>
  6. </dependency>
  7. <!-- Gradle -->
  8. dependencies {
  9. compile group: 'com.typesafe.akka', name: 'akka-osgi_2.12', version: '2.5.23'
  10. }
  11. <!-- sbt -->
  12. libraryDependencies += "com.typesafe.akka" %% "akka-osgi" % "2.5.23"

示例项目

你可以查看「OSGi Dining Hakkers」示例项目,以了解其在实践中的使用情况。

背景

OSGi」是一个成熟的基于组件的系统打包和部署标准。它具有与项目 Jigsaw(最初计划为 JDK 1.8)相似的功能,但它有更强大的设施来支持遗留 Java 代码。这就是说,尽管 Jigsaw-ready 模块需要对大多数源文件进行重大更改,有时还需要对整个应用程序的结构进行修改,但 OSGi 可以用于将几乎任何 Java 代码模块化为 JDK 1.2,通常对二进制文件没有任何更改。

这些遗留功能是 OSGi 的主要优势和主要弱点。OSGi 的创建者早就意识到,实现者不太可能急于在现有 JAR 中支持 OSGi 元数据。在 JRE 中已经有一些新的概念需要学习,而直接使用 J2EE 管理良好的团队的附加价值并不明显。已经出现了“包装”二进制 JAR 的工具,因此它们可以作为捆绑包使用,但此功能仅在有限的情况下使用。这里的80/20 规则为一个应用程序可能会有「80% 的复杂性可以通过 20% 的配置解决」,但这足以给 OSGi 名声大振。

本文档旨在为大家提供使用 Akka 所需的基础知识,要获得比这里提供的更多的信息,「OSGi In Action」是值得探索的。

OSGi 应用程序的核心组件和结构

在 OSGi 中部署的基本单元是Bundle。一个Bundle是一个MANIFEST.MF文件中附加条目包括 https://www.osgi.org/bundle-headers-reference/ 的 Java JAR 包,它最小地暴露了导入和导出的包和Bundle的名称和版本。由于这些manifest条目在 OSGi 部署之外被忽略,所以Bundle可以作为 JRE 中的 JAR 互换使用。

当加载一个Bundle时,需要为每个Bundle实例化 Java ClassLoader 的专门实现。每个类加载器读取manifest条目,并在容器单例中发布功能(以Bundle-Exports的形式)和需求(作为Bundle-Imports),以供其他Bundle发现。通过这些类加载器将导入与跨Bundle导出匹配的过程是解析过程,这是 OSGi 容器中Bundle的生命周期 FSM 中六个离散步骤之一:

  1. INSTALLED:已从磁盘加载已安装的Bundle,并实例化了具有其功能的类加载器。Bundle是手动或通过特定于容器的描述符迭代安装的。对于那些熟悉遗留打包(如 EJB)的人来说,OSGi 的模块化特性意味着Bundle可以被具有重叠依赖关系的多个应用程序使用。通过从存储库中单独解析这些重叠,可以跨多个部署将这些重叠消除到同一个容器中。
  2. RESOLVED:已解决的Bundle是满足其需求(导入)的Bundle,解析意味着可以启动一个Bundle
  3. STARTING:启动的Bundle可以被其他Bundle使用。对于以其他方式完全关闭已解析Bundle的应用程序,这里的含义是必须按照深度优先搜索所有要启动的Bundle的顺序启动它们。当一个Bundle启动时,Bundle中任何暴露的生命周期接口都会被调用,从而使Bundle有机会启动自己的服务端点和线程。
  4. ACTIVE:一旦一个Bundle的生命周期接口无错误地返回,Bundle就被标记为活动的。
  5. STOPPING:正在停止的Bundle调用Bundle的停止生命周期,并在完成时转换回RESOLVED状态。在STARTING时创建的任何长时间运行的服务或线程都应在调用Bundle的停止生命周期时关闭。
  6. UNINSTALLEDBundle只能从INSTALLED状态转换到此状态,这意味着在停止之前无法卸载它。

注意这个 FSM 中对生命周期接口的依赖性。虽然没有要求Bundle发布这些接口或接受此类回调,但生命周期接口提供main()方法的语义,并允许Bundle启动和停止长时间运行的服务,如 REST 页面服务、ActorSystem、群集等。

其次,请注意,在考虑需求和能力时,将它们与存储库依赖性等同是一个常见的误解,这可能在 Maven 或 Ivy 中发现。虽然它们提供了类似的实用功能,但是 OSGi 有几种并行类型的依赖关系(如 Blueprint Services),这些依赖关系不容易映射到存储库功能上来。实际上,核心规范将这些设施留给使用中的容器。反过来,一些容器有工具从存储库元数据生成应用程序加载描述符。

显著的行为变化

结合对Bundle生命周期的理解,OSGi 开发人员必须注意有时引入的意外行为。这些行为通常都在 JVM 规范中,但是有些意外的情况,仍然会导致失败。

Bundle不应导出重叠的包空间。对于传统的 JVM 框架来说,期望由多个 JAR 组成的应用程序中的插件驻留在一个包名称下并不少见。例如,前端应用程序可能会扫描com.example.plugins中的所有类,以查找特定的服务实现,该包存在于几个贡献的 JAR 中。虽然支持具有复杂manifest头的重叠包是可能的,但最好使用不重叠的包空间和设施(如「Akka 集群」)进行服务发现。在风格上,许多组织选择使用根包路径作为包分发文件的名称。

除非像类那样显式导出,否则资源不会在Bundle之间共享。这种情况的常见情况是,getClass().getClassLoader().getResources("foo")将返回名为foo的类路径上的所有文件。getResources()方法只从当前的类加载器返回资源,并且由于每个Bundle都有单独的类加载器,因此资源文件(如配置)不再能够以这种方式进行搜索。

配置 OSGi 框架

要在 OSGi 环境中使用 Akka,必须对容器进行配置,使org.osgi.framework.bootdelegation属性将sun.misc包委托给引导类加载器,而不是通过普通的 OSGi 类空间解析它。

预期用途

Akka 只支持严格限制在单个 OSGi 包中使用ActorSystem,其中该Bundle包含或导入所有 Actor 系统的需求。这意味着不建议将ActorSystem作为一个服务来提供,Actor 可以通过其他Bundle动态部署到该服务上,ActorSystem及其包含的 Actor 在这种方式下不应该以是动态的。ActorRef可以安全地暴露在其他Bundle中。

Activator

要在 OSGi 环境中引导 Akka,可以使用akka.osgi.ActorSystemActivator类方便地设置ActorSystem

  1. import akka.actor.{ ActorSystem, Props }
  2. import org.osgi.framework.BundleContext
  3. import akka.osgi.ActorSystemActivator
  4. class Activator extends ActorSystemActivator {
  5. def configure(context: BundleContext, system: ActorSystem): Unit = {
  6. // optionally register the ActorSystem in the OSGi Service Registry
  7. registerService(context, system)
  8. val someActor = system.actorOf(Props[SomeActor], name = "someName")
  9. someActor ! SomeMessage
  10. }
  11. }

这里的目标是将 OSGi 生命周期更直接地映射到 Akka 生命周期。ActorSystemActivator使用类加载器创建 Actor 系统,该类加载器从应用程序Bundle和所有可传递依赖项中查找资源(application.confreference.conf文件)和类。


英文原文链接Akka in OSGi.