component是什么:
- 系统的积极参与者
- 了解并适应其环境
- 环境 = 其他组件提供的服务
- 环境 = 资源、设备等
- 可能向其他组件提供服务并使用来自其他组件的服务
- 拥有生命周期
简而言之:
一个bundle只能有一个需要
BundleContext
的激活器,并且可以拥有尽可能多的活动组件。这意味着你可能会试图将几个松散相关的问题放入一个单一类中的激活器中。这就是为什么通过
SCR("Service Component Runtime",它是实现新的和改进的OSGi R4.2 DS - Declarative Service - 规范的"扩展器bundle")来管理
这些组件可能更容易。这在OSGi 4.2之后尤其正确,因为现在更容易将DS组件编写为POJO:不再需要
ComponentContext
参数的
activate
和
deactivate
方法。另请参见
Lazy Declarative Service。
注意:
在OSGi的上下文中,将这些术语替换并查看“我们是如何到达那里的”可能会有所帮助(Neil Bartlett的优秀博客文章)
以下是一些相关摘录,其中“模块”最终成为OSGi Bundle(管理声明服务的组件):
模块分离
我们的第一个要求是清晰地分离模块,使得来自一个模块的类没有无限制地查看和遮蔽来自其他模块的类的能力。
在传统的Java中,所谓的“类路径”是一个庞大的类列表,如果多个类恰好具有相同的完全限定名称,则将始终找到第一个类,并忽略第二个及所有其他类。
防止类的无限制可见性和遮蔽的方法是为每个模块创建一个类加载器。类加载器只能直接加载它知道的类,这在我们的系统中将是单个模块的内容。
模块访问级别
如果我们就此停止,那么模块将完全隔离并且无法彼此通信。为了使系统实用,我们需要以一种谨慎和受限的方式添加查看其他模块中类的能力。
在这一点上,我们输入另一个要求:模块希望隐藏一些其实现细节。
我们想要有一个“模块”访问级别,但问题是 javac 编译器今天并不知道模块边界在哪里。
我们在模块系统中选择的解决方案是允许模块仅导出其内容的部分。如果模块的某个部分未被导出,则其他模块根本无法看到它。
在导入时,我们应该导入实际需要使用的内容,而不管它来自何处,并忽略所有与其打包在一起的内容。
OSGi 选择包。Java 包的内容旨在有些连贯性,但列出包作为导入和导出并不太困难,而且将一些包放在一个模块中,将其他包放在另一个模块中也不会破坏任何内容。我们可以将应该是内部的代码放置在一个或多个未导出的包中。
包布线。
现在我们已经有了一个模块如何隔离和重新连接的模型,我们可以想象构建一个框架来构建这些模块的具体运行时实例。它将负责安装模块并构建了解其各自模块内容的类加载器。然后它会查看新安装模块的导入,并尝试找到匹配的导出。
这样做的一个意外好处是我们可以动态地安装、更新和卸载模块。安装新模块不会影响已经解析的模块,尽管它可能使一些以前无法解决的模块得到解决。在卸载或更新时,框架知道受影响的模块,并在必要时更改它们的状态。
版本
我们的模块系统看起来很好,但我们还不能处理模块随着时间的不可避免的变化而发生的变化。我们需要支持版本。
我们如何做到这一点?首先,一个导出者可以简单地声明一些关于其正在导出的包的有用信息:“这是 API 的 1.0.0 版本”。现在,导入者只能导入与其预期和已编译/测试兼容的版本,并拒绝接受不兼容的版本。
打包模块和元数据
我们的模块系统需要一种方法来打包模块内容以及描述导入和导出的元数据为可部署单元。所以唯一的问题是,我们应该把元数据放在哪里,例如导入和导出列表、版本等等?恰好在2000年之前设计的OSGi没有选择这些解决方案之一,而是回顾了JAR文件规范,在其中阐明了答案:
META-INF/MANIFEST.MF
是任意应用程序特定元数据的标准位置。
模块化谜题的最后一块是将实现与接口进行后期绑定。我认为它是模块化的一个关键特性,尽管某些模块系统完全忽略它,或者至少认为它超出了范围。我们应该寻找一种分散的方法。而不是被神级类告知要做什么,让我们假设每个模块可以简单地创建对象并将其发布到其他模块可以找到的某个地方。我们称这些已发布的对象为“服务”,它们发布的地方为“服务注册表”。最重要的有关服务的信息是它实现的接口(或接口),因此我们可以将其用作主要注册密钥。现在,需要查找特定接口实例的模块可以简单地查询注册表,找出此时可用的服务。注册表本身仍然是一个中心组件,存在于任何模块之外,但它不是“上帝”......相反,它就像一个共享白板。