OSGi 中的 Akka
依赖
为了在 OSGi 中使用 Akka,你需要将以下依赖添加到你的项目中:
<!-- Maven -->
<dependency>
<groupId>com.typesafe.akka</groupId>
<artifactId>akka-osgi_2.12</artifactId>
<version>2.5.23</version>
</dependency>
<!-- Gradle -->
dependencies {
compile group: 'com.typesafe.akka', name: 'akka-osgi_2.12', version: '2.5.23'
}
<!-- sbt -->
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 中六个离散步骤之一:
INSTALLED
:已从磁盘加载已安装的Bundle
,并实例化了具有其功能的类加载器。Bundle
是手动或通过特定于容器的描述符迭代安装的。对于那些熟悉遗留打包(如 EJB)的人来说,OSGi 的模块化特性意味着Bundle
可以被具有重叠依赖关系的多个应用程序使用。通过从存储库中单独解析这些重叠,可以跨多个部署将这些重叠消除到同一个容器中。RESOLVED
:已解决的Bundle
是满足其需求(导入)的Bundle
,解析意味着可以启动一个Bundle
。STARTING
:启动的Bundle
可以被其他Bundle
使用。对于以其他方式完全关闭已解析Bundle
的应用程序,这里的含义是必须按照深度优先搜索所有要启动的Bundle
的顺序启动它们。当一个Bundle
启动时,Bundle
中任何暴露的生命周期接口都会被调用,从而使Bundle
有机会启动自己的服务端点和线程。ACTIVE
:一旦一个Bundle
的生命周期接口无错误地返回,Bundle
就被标记为活动的。STOPPING
:正在停止的Bundle
调用Bundle
的停止生命周期,并在完成时转换回RESOLVED
状态。在STARTING
时创建的任何长时间运行的服务或线程都应在调用Bundle
的停止生命周期时关闭。UNINSTALLED
:Bundle
只能从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
。
import akka.actor.{ ActorSystem, Props }
import org.osgi.framework.BundleContext
import akka.osgi.ActorSystemActivator
class Activator extends ActorSystemActivator {
def configure(context: BundleContext, system: ActorSystem): Unit = {
// optionally register the ActorSystem in the OSGi Service Registry
registerService(context, system)
val someActor = system.actorOf(Props[SomeActor], name = "someName")
someActor ! SomeMessage
}
}
这里的目标是将 OSGi 生命周期更直接地映射到 Akka 生命周期。ActorSystemActivator
使用类加载器创建 Actor 系统,该类加载器从应用程序Bundle
和所有可传递依赖项中查找资源(application.conf
和reference.conf
文件)和类。
英文原文链接:Akka in OSGi.