本文介绍了一个采用 XML 的插件示例
以便为定义好的扩展点注册扩展
通过使插件能够感知 Extention Registry 并提供 OSGi 服务
我们可以完成这一完整的组件退耦操作
插件扩展点OSGi
如您所知Eclipse 的组件架构是基于插件 的 这意味着将一组代码组件化为单一的组件然后利用 Eclipse 框架注册为其组件之一其他组件可以绑定该组件或调用该组件扩展点 是插件允许其他插件向公开扩展点的插件提供附加功能的方法现在利用所有这些插件并将其包装到受控的运行时插件可在其中动态进出并且您可以获得 OSGi(基本上来说)
示例插件
让我们从公开扩展点的基本插件开始这样可以为同义词服务注册新的字符串映射此项服务允许其他服务注册一个词并将其映射到另一个词(同义词)基本扩展包含非常简单的元素一个词当然还有一个新的同义词此插件扩展点的基本结构如表 所示
表 示例插件的元素
我们还要将插件注册为 OSGi 服务这意味着它只在显式执行此操作时被加载并将可供其他客户声明性地使用为了使用该服务其他客户只需了解 Interface 和 OSGi 类名称在我们的示例中我们不会真正调用该服务因为扩展点是假设的我们将使用 OSGi API 以告知我们此项服务出入的时间所以我们可以正确地注册扩展点
现在这只是一个示例并且使用针对此概念的扩展点可能不是最好的方法我们用此基本示例要达到的目的是如何动态注册新的扩展同时说明使用 OSGi API 的插件生命周期事件
Mediator 插件
下一个插件是第三方插件该插件了解已知的服务和扩展点但不想绑定到此插件因为后面它将依靠该插件进行运行时解析这意味着该插件可以驻留在所引用的插件 (panySynonymRegistry) 可能不存在的机器上因为我们现在生活在 OSGi 和动态运行时世界所以我们想确保插件在不引起运行时故障或错误的情况下运行我们的 mediator 插件将接受同义词的 XML 文件并且通过使用提供的扩展点用 SynonymRegistry 插件注册每个同义词
清单 用于概念验证的示例 XML 文件
Synonymsxml
<?xml version= encoding=UTF?>
<synonyms>
<entry word=mediator synonym=broker/>
<entry word=mediator synonym=gobetween/>
<entry word=mediator synonym=interceder/>
<entry word=mediator synonym=intermediary/>
<synonyms>
Mediator 插件在其 start() 方法中做的第一件事是用 OSGi 服务注册为一个服务初始化侦听器我们要在传入 start() 方法的 BundleContext 对象上调用 OSGi 服务方法 addServiceListener()以下代码展示了一个通过传入代码和我们感兴趣的服务 ID 调用此 API 的示例
contextaddServiceListener( this panySynonymRegistry )
通过提供过滤器可以告知 OSGi 服务注册中心只需通知您指定服务中的状态更改在本例中过滤器只是 SynonymRegistry 类的类名称
您可能会寻根究底答案就在启动序列中在 OSGi 领域我们不是总知道另一服务可用的时间因此我们需要对此进行说明通过注册为服务侦听器我们可得知服务开始和停止的时间如果服务不可用则允许我们缓存同义词当服务确实可用时我们会得到通知并注册扩展
注册新扩展
下面我们将讲述本文的核心内容现在我们有了想为其提供动态扩展的数据 (Synonymsxml) 和已知的扩展点 (panySynonymRegistry Synonym)由于我们不知道何时初始化插件也不知道是否初始化 Synonym 插件所以我们只要在加载插件时尝试注册 XML 文件中的条目即可请记住这是一个展示概念的示例不应在生产代码中这样实施通常我们尽可能多地以惰性方式(延迟或在需要时)执行初始化
Eclipse V 中的新特性是能够在运行时提供扩展例如客户可以编写一个包含某个视图的应用程序该视图可以在单击按钮时创建一个透视图透视图被添加到扩展注册表然后在可用透视图的列表中显示此功能的重要好处之一是它可以减轻插件之间的硬依赖性插件 A 可供在插件 B 中定义的平台使用无需依赖插件 B而且通过将此功能与 OSGi 框架结合插件可以检查服务的存在性如果存在可从服务中定义的扩展点创建扩展这在使用面向服务架构的原则同时促进了真正动态的环境
Eclipse V 中新公开的是 addContribution() 方法该方法在 IExtensionRegistry 接口中定义清单 中的代码展示了可以通过 addContribution() API 添加扩展的方法addContribution() 方法旨在采用普通 XML 作为第一个参数中的 InputStream
清单 通过 addContribution() API 添加扩展的方法
IExtensionRegistry registry = RegistryFactorygetRegistry( )
Object key = ((ExtensionRegistry) registry)getTemporaryUserToken( )
ByteArrayInputStream is =
new ByteArrayInputStream( buffertoString()getBytes() )
try {
registryaddContribution(is bundle null null key)
}
finally {
try {
isclose( )
}catch (IOException e) {
}
}
编写本文的时候 意味着这是一个更改 Eclipse 未来版本的好机会 允许公众访问注册表的用户标记可以使用此内部 Eclipse 调用获得下面的代码展示了内部 API (getTemporaryUserToken()) 的使用
Object key = ((ExtensionRegistry)registry)getTemporaryUserToken()
但是在里程碑式的下一版本 Eclipse V 版本中此标记不能公开访问为了支持应用程序中的动态扩展启动程序必须提供以下针对虚拟机的设置
Declipseregistrynulltoken=true
此定义现在允许我们将 null 用作 addContribution() API 中的 User Token现在我们的代码看上去类似如下有关此问题中的 Bugzilla 对话请参见 Bugzilla bug 清单
清单 getTemporaryUserToken()
…
try {
registryaddContribution(is bundle null null null)
}
…
上面显示的缓沖区变量表示实际的 XML 块此 XML 是我们可以在 pluginxml 文件内看到的精确副本回到我们的 SynonymRegistry 示例此扩展的 XML 将类似清单
清单 SynonymRegistry 的 XML
<plugin>
<extension point=panysynonymregistry id=myExtension>
<synonyms
word=mediator
synonym=broker/>
</extension>
</plugin>
客户可以考虑创建一个接受以下参数的包装工厂类如扩展点 ID扩展 ID元素名称(本例中是同义词)实际属性和资源包 ID该包装类将参数格式化为类似上面代码的 XML 字符串然后将此 XML 字符串读入将被传入到 IExtensionRegistry 接口的 addContribution() 方法的 ByteArrayInputStream 中只有此方法的其他必需参数是用户标记和资源包 ID值得注意的一点是资源包 ID 应是做出该贡献的资源包的 ID不是在其中定义扩展点的资源包的 ID
警告和提示
在 M(于 年 月 日构建的 Eclipse)中引入的一个特性是对 addContributions() 的调用是异步调用这意味着该扩展不可立即使用因为 Eclipse 启动了一项执行实际注册的作业简单地说您必须开始自己的作业并与之同步以获得任何类型的同步行为
为了使此项任务更容易下面给出了三条提示
?创建一项将其本身注册为一个 RegistryChangeListener 的新作业
?该作业运行时确保您的作业代码侦听 RegistryListener 回调的 isRegistered 集合
?一旦所有注册完成即退出您的作业
当然现在我们必须将调用代码与生成的作业结合起来以获得同步调用这只有在代码要求立即使用扩展时才得到保证希望您的代码设计为惰性这样初始化就变得不重要
结束语
动态扩展的使用可以通过编程方式创建通过使用 OSGi 框架侦听服务何时可用(加载或卸载)动态扩展增强了退耦功能一起使用这些技术将允许声明性的贡献和组件之间 % 退耦