【目录】- 【模块可扩展支持】-【概念】
目前为止我们已经了解了模块化的隔离策略,面向服务的交互策略,现在就该来看看更高级的模块扩展策略,这里的“可扩展”在官方文档是这么介绍的
1) 扩展点:通过标准XML节点<ExtensionPoint>来定义一个模块向其它模块暴露的扩展点。暴露扩展点的模块会监听并处理其它模块对其的扩展。
2) 扩展:通过标准XML节点<Extension>来定义一个模块对暴露扩展点的模块的扩展。这个XML节点会通过扩展点变更事件传递到暴露扩展点的模块。
3) 动态扩展:模块在启动和停止时,会分别向平台注册和卸载相应的扩展点及扩展,该平台通过模块上下文暴露扩展点变更事件来处理动态的扩展信息。
4) 零耦合:模块的扩展没有任何的耦合,仅通过标准XML来配置。
为什么要引入扩展机制呢?这得从现实的使用场景来说,除了必要的逻辑模块外,其实一个程序大部分的工作都是和前台,即UI来打交道,这时候就避免不了要为每个功能模块在界面上设置一个按钮,一个下拉菜单等等的”交互点“。这个时候,对于一个插件框架来说,就必须能处理一个模块对界面的“扩展”,也就是支持逻辑模块对界面模块的扩充或改变。当然这只是一个典型的例子,扩展的功用远大于此。
我们再看一个具体的场景,一个音乐播放器,它默认只支持MP3格式,现在准备让它支持更多,比如FLAC,APE等,播放这些格式需要特定的解码模块支持,那么如何让主程序知道且能自动识别各个格式的解码模块呢?而且是动态的?而且是不需要修改主程序的?一种可行的办法就是利用上一节讲的面向服务来定义一个接口,具体的解码模块来实现它并注册到服务总线上,但服务总线在这里稍显“繁琐”。OSGi.NET提供了更简单的方式,就是这里所说的“扩展点”和“扩展”,如下图
我们还是拿主板来做比较,这里的插件A就像是主板上的USB,支持2.0规范,它的作用就是暴露出一个接口,使得一个同样支持2.0的U盘,即插件B,可以插入此接口,完成与主板的连接。
如果假设这里的USB是主程序中的一个模块,则它的功能大致是
1) 定义一个“契约”,类似这里的USB 2.0规范
2) 当一个符合定义好的契约的模块插入接口时,识别它
3) 将这个识别的模块附加信息读取出来,以供主程序或者其他模块使用
这是我们可以说这个模块具有了“扩展点”。
同理,如果U盘也是一个模块,则它需要有以下功能
1) 符合上面模块所定义的契约
2) 将自己的模块附加信息暴露给上面的模块以便识别和读取
则它是上面扩展点的一个“扩展”。
接着,我们再来看看前面提到的音乐播放器自动支持多种格式的问题,我们假设播放器的“格式识别”是一个模块,他定义了一个“AudioFormat”的扩展点,那其他解码模块只要依照这个扩展点定义扩展它并附带上自己的解码类型,如APE,和处理方法,如APE.PlayAPEFile,则格式识别模块会将这些信息收集起来并以服务的形式供主程序检索和调用。这种扩展机制明显比服务总线来说简单且没有任何模块间的依赖。
此外,OSGi.NET的扩展点和扩展都是定义在相应模块的Mainfest.xml文件中的,就非常容易后期维护和更改。模块与模块之间就可以完成“扩展点和扩展”,当然主程序中启动的运行时环境也能直接处理指定“扩展点”的“扩展”。
下一节,我们看一个如何编写这个音乐播放器。
【目录】- 【模块可扩展支持】-【实例】
同前面的实例一样
1) 第一步,我们依然通过OSGi.NET提供的项目模板来创建一个“控制台主应用程序”,这个主程序主要完成与用户的交互以及信息的输出。这里我们把这个主程序起名为OSGi.NET.AudioPlayerShell,这个Shell表示这是一个“外壳”、框架,也就是启动OSGi.NET内核的入口程序
2) 接着我们在创建两个“虚拟目录”,FormatTypes和FormatServices,用来将我们的模块进行分类,FormatTypes下还包含Lossless和Lossy,分别代表无损格式和有损格式。
3) 如前面概念所介绍的,我们这个播放器需要一个“格式识别”模块,它作为一个服务用来识别其他解码模块并向主程序暴露。这里起名为OSGi.NET.AudioFormatService,并把它放在Services虚拟目录中。注意它是一个“服务”,名称中带有Service后缀且路径是在Shell主程序下Plugins\FormatServices,和上面的实例稍有不同
4) 接下来我们为有损格式创一个OSGi.NET.MP3DecoderPlugin的“控制台插件”,为无损格式创建一个OSGi.NET.APEDecoderPlugin的“控制台插件”。注意这里我们使用了Plugin作为模块名称的后缀,代表他是一个插件,且路径是Plugins\FormatTypes\Lossy,同理OSGi.NET.APEDecoderPlugin是Plugins\FormatTypes\Lossless
5) 创建完成后,解决方案如下图所示
文件结构如下图所示
6) 编译整个解决方案后,按F5启动,只有默认输出。查看bin目录下的log.txt可以看到全部模块都正常启动并处于“激活(Active)”状态,表示我们现在使用的新的目录架构对程序没有任何影响。这点对超过一定数量,比如100个模块以上,的大型系统来说,非常方便,可按照一定的类别划分各个模块并放入相应的目录中
7) 现在我们来给OSGi.NET.AudioFormatService添加“扩展点”,扩展点可在Manifest编辑器中直接添加,“点”这里为“OSGi.NET.AudioFormat”,如下图
Manifest.xml中会添加这样一行
添加扩展点完成。
8) 接着来给OSGi.NET.MP3DecoderPlugin和OSGi.NET.APEDecoderPlugin添加具体的解码逻辑,简单的只是输出一行提示信息。这里先给OSGi.NET.AudioFormatService添加一个IAudioDecoder的接口
并实现它
此处同样需要添加依赖关系,可参考上一节“面向服务架构支持”的相关实例。
9) 然后来给OSGi.NET.MP3DecoderPlugin添加对上面扩展点的“扩展”,依然在编辑器中。这里我们只是简单的附加了两条信息,一个是格式名,还有就是格式的解码类名
Manifest.xml如下
Manifest.xml如下
完成扩展定义。
10) 接着我们来考虑一下OSGi.NET.AudioFormatService如何来响应和读取这些来自其他模块的扩展信息,并通过服务的形式暴露给主程序
a) 首先将默认的ICustomService.cs接口、Impl目录下的CustomService.cs实现以及Activator.cs中的服务注册对象,更名为IAudioFormatExtensionService和AudioFormatExtensionService
b) 接着创建一个AudioFormatExtension类,用来描述读取到的扩展信息
c) 再来定义IAudioFormatExtensionService接口
d) 准备工作完成后,现在才真正的来通过OSGi.NET提供的扩展机制来实现对扩展信息的动态捕获,在Activator,激活器,的start函数中添加如下代码,用来从运行时获取指定扩展点名的所有扩展。
ExtensionChanged事件的处理,我们放在小结中给大家演示。
e) 接着,我们创建AudioFormatExtensionContainer来处理扩展信息
f) 简单完成后,我们就可以来实现AudioFormatExtensionService了
g) OK,现在就可以在主程序中直接调用了
运行效果
h) 注意当你点击回车结束程序时,会有异常发生
这点我们下一步做演示时会解决掉它。