了解 OSGi 概念。 尝试遵循拼图方法
已发表: 2013-04-20OSGi
在今天变得非常流行,这要归功于它的模块化方法和在模块之间强制执行逻辑边界的能力。 当我们第一次发现它时,问题是从哪里开始了解它是如何工作的?
为了理解 OSGi 概念,我们将尝试遵循拼图方法,这个想法是从这项技术的琐碎部分开始,然后搜索与找到的相关的其他部分。 为了组装拼图,我们将得到 JArchitect 的协助,这将有助于检测 OSGi 内部设计。
更具体地说,我们用 JArchitect 分析使用 OSGi 技术的应用程序,它涉及使用 OSGi 容器 Equinox 的著名 eclipse IDE。
让我们从典型的 OSGi 定义开始:
OSGi 通过为当今的大型分布式系统以及小型嵌入式应用程序提供模块化架构来降低复杂性。 从内部和现成的模块构建系统显着降低了复杂性,从而降低了开发和维护费用。 OSGi 编程模型实现了基于组件的系统的承诺。
OSGi 最重要的部分是模块化,让我们来看看什么是 OSGi 模块?
OSGI 模块称为捆绑包,因此每个应用程序都至少包含一个捆绑包。
这些捆绑包在容器内执行,并且作为容器管理模块的每种模块化方法,问题是哪个合约必须实现要集成到容器中的每个模块?
让我们以包 org.eclipse.equinox.jsp.jasper 为例,搜索其所有实现的接口,为此我们可以执行以下 CQLinq 请求:
1 2 3 4 |
from t in Types where t . ParentProject . Name ==” org . eclipse . equinox . jsp . jasper_1 . 0.300.v20110502 “ let interfaces = t . InterfacesImplemented from i in interfaces select i |
实现了 OSGi 包中的 BundleActivator 接口,该接口包含两个方法 start 和 stop 用于自定义 bundle 的启动和停止。
bundle 的另一个特性是它的清单文件,这里是 org.eclipse.equinox.jsp.jasper 清单文件的一部分:
1 2 3 4 5 6 7 8 9 |
Manifest - Version : 1.0 Bundle - Localization : plugin Bundle - RequiredExecutionEnvironment : CDC - 1.0 / Foundation - 1.0 , J2SE - 1.3 Bundle - SymbolicName : org . eclipse . equinox . jsp . jasper Eclipse - LazyStart : true Eclipse - SourceReferences : scm : cvs : pserver : dev . eclipse . org : / cvsroot / rt : org . eclipse . equinox / server - side / bundles / org . eclipse . equinox . jsp . jaspe r ; tag = v20110502 Bundle - Activator : org . eclipse . equinox . internal . jsp . jasper . Activator |
正如我们所看到的,这个清单包含容器所需的一些元信息,例如指定实现 BundleActivator 接口的捆绑激活器类。
捆绑包代表我们的第一块拼图,这里是捆绑包的简化表示:
为了了解 eclipse 使用的所有包,让我们搜索所有实现 BundleActivator 接口的类。
1 2 |
from t in Types where t . Implement ( “ org . osgi . framework . BundleActivator “ ) select new { t , t . NbBCInstructions } |
谁管理捆绑包并调用 BundleActivator 方法?
要发现,让我们搜索直接或间接调用 BundleActivator.start 的方法
1 2 3 4 |
from m in Methods let depth0 = m . DepthOfIsUsing ( “ org . eclipse . osgi . framework . internal . core . BundleContextImpl . startActivator ( BundleActivator ) “ ) where depth0 > = 0 orderby depth0 select new { m , depth0 } |
捆绑包在启动时由 OSGi 框架激活。 框架类由 Equinox 容器启动。 为了更好地理解容器启动时会发生什么,以下是容器启动时执行的一些操作:
当容器启动时,它初始化 OSGi 框架,框架获取所有已安装的包,并为每个包创建一个 BundleHost 类的实例,并将找到的包存储到存储库中。
BundleHost 类实现了 Bundle 接口,其中包含启动、停止、卸载和更新等方法,这些方法是管理 Bundle 生命周期所必需的。
所以我们的第二个拼图是 OSGi 容器,它由 Equinoxlauncher 启动,它初始化框架。 框架类负责加载包并激活它们。
在了解了有关 bundle 和 container 的一些基本概念之后,让我们深入了解 bundle 并了解它们在内部是如何工作的。
让我们以 org.eclipse.equinox.http.servlet 包为例,搜索其 Activator 类的 start 方法调用的方法。

此捆绑包创建一个服务并将其注册到容器中。 OSGi 中的服务由标准的 Java 类或接口定义。 通常使用 Java 接口来定义服务接口。 服务是捆绑包应该用来在彼此之间进行通信的首选方法。
以下是一些使用服务的有用场景:
- 将功能从捆绑包导出到其他捆绑包。
- 从其他捆绑包中导入功能。
- 为来自其他包的事件注册侦听器。
上一个依赖图中的另一个注释是服务工厂用于创建服务实例。
我们的第三个难题是 OSGi 服务层,每个包都可以使用或声明一些服务,强制组件设计方法,这里是 OSGi 包的新表示。
如果 bundle 使用服务与其他 bundle 通信,它如何与其他 jar 通信?
如果我们开发一个bundle并尝试使用另一个jar中的一个类,我们会惊讶于它不会按预期工作,原因是ClassLoader被OSGi容器钩住了,我们来检查一下是哪个方法调用了java。 lang.Thread.setContextClassLoader。
1 2 |
from m in Methods where m . IsUsing ( “ java . lang . Thread . setContextClassLoader ( ClassLoader ) “ ) select new { m , m . NbBCInstructions } |
许多方法调用它,包括 EquinoxLauncher。 所以每次bundle尝试创建一个类实例时,OSGi容器都会检查代码是否被允许做这个动作,这就是manifest文件中导入和导出包的作用。
1 2 3 4 |
Export - Package : org . eclipse . equinox . http . servlet ; version =” 1.1.0 ″ Import - Package : javax . servlet ; version =” 2.3 ″ , javax . servlet . http ; version =” 2.3 ″ , org . osgi . framework ; version =” 1.3.0 ″ , org . osgi . service . http ; versi on =” [ 1.2 , 1.3 ) ” |
该包明确声明了导出和导入的包,为了检查这一点,让我们搜索 org.eclipse.equinox.http.servlet 包使用的包并验证它是否只使用导入的包。
1 2 |
from n in Packages where n . IsUsedBy ( “ org . eclipse . equinox . http . servlet_1 . 1.200.v20110502 “ ) select new { n , n . NbBCInstructions } |
正如我们所看到的,所有使用的包都在清单文件的导入包部分中指定。
然而,导出的包代表可以从其他包中使用的包。 它由 ExportedPackage 接口表示,我们可以使用该接口搜索容器类。
1 2 |
from t in Types where t . IsUsing ( “ org . osgi . service . packageadmin . ExportedPackage “ ) select new { t , t . NbBCInstructions } |
这个新功能的有趣之处在于,bundle 将有一个明确定义的边界,并且它使用什么以及它作为服务公开的内容都得到了很好的指定。
我们可以通过其他工具来强制检查是否使用了导入的包,例如使用CQLinq我们可以在每次项目使用非指定包时写一些规则警告,但是最好在执行环境中进行这个检查,所以开发者不能打破这些规则。
这种处理导入和导出包的能力由 OSGi 模块层管理,这是我们的第四块拼图。
让我们回到 OSGi 容器,发现它提供了哪些服务?
容器服务
正如我们在 EquinoxLauncher 类启动容器之前发现的那样,框架类初始化并启动捆绑包,为了检测提供了哪些服务,让我们搜索框架初始化方法中使用的所有类。
1 2 |
from t in Types where t . IsUsedBy ( “ org . eclipse . osgi . framework . internal . core . Framework . initialize ( FrameworkAdaptor ) “ ) select new { t , t . NbBCInstructions } |
之前已经发现了一些类,例如 BundleRepository、BundleHost、PackageAdminImpl 和 ServiceRegistry。
其他类呢:
- 启动级别管理器:
每个 OSGi Bundle 都与一个启动级别相关联,使服务器能够控制 bundle 的相对启动和停止顺序。 只有启动级别小于或等于服务器框架的活动启动级别的包必须处于活动状态。 通常,启动级别较小的捆绑软件往往会更早启动。 - 安全管理员:
安全层通过将捆绑功能限制为预定义的功能来处理安全方面。 - 事件管理器:
Event Admin 服务提供了一个发布-订阅模型来处理事件。 它是根据 OSGi 事件管理服务规范实现的。事件管理服务通过插入事件通道在事件发布者和事件订阅者(事件处理程序)之间调度事件。 发布者将事件发布到通道,并且事件通道定义需要通知哪些处理程序。 因此发布者和处理者之间没有直接的了解,这简化了事件管理。
OSGi全貌
让我们组装之前描述的所有拼图,我们将拥有以下 OSGi 图片:
这种架构有以下有趣的好处:
- 简单的。
- 降低复杂性。
- 易于部署。
- 安全的。
是什么让它非常有吸引力和值得绕道而行,如果你深入研究它,你不会浪费你的时间。