引言
从 Eclipse 3.0 开始,Eclipse 通过选择开放服务网关协议(Open Services Gateway Initiative,OSGi)来替换先前版本中 Eclipse 插件技术,Eclipse 中的插件是通过 OSGi 框架来实现的,可以认为,一个 Eclipse 插件等同于一个 OSGi 的 Bundle。Eclipse 3.0 以后,创建一个 Eclipse 插件,既可以采用基于扩展点的方式,又可以采用基于 OSGi 的 Bundle 概念,相应地,插件间的通信方式也变得多样化。
如不做特殊说明,本文默认的开发和运行环境是:IBM JDK 1.6,Eclipse 3.4.x。
回页首
Eclipse 插件间常用通信方法
在 Eclipse 插件编程中,我们常用的插件通信方式主要有如下四种:通过包约束条件建立 Eclipse 插件之间的关联关系;使用 Eclipse 扩展点机制实现插件间通信;使用 OSGi 框架实现组件间信息通信和共享;以及使用单例模式实现插件间信息共享。
其中第一、二、四种方式必须显示建立两个插件之间类的关联关系,并且第二种方式需要通过配置文件建立扩展和扩展点之间的关系,第三种方式是 OSGi 框架提供的 Bundle 之间的通信,这种方式不需要通过任何配置文件显示指定两个插件之间的依赖,Bundle 只需要通过注册服务和查找服务便能实现相互间的功能调用和数据传递,因而也是最灵活的一种方式。
建立 Eclipse 插件之间的关联关系
从 Eclipse 3.0 开始,Eclipse 插件是通过 OSGi 框架来实现的,OSGi Bundle 的解析是通过分析定义在 MANIFEST.MF 文件中的包约束条件,查找与包约束条件相匹配的 Bundle 并建立关联关系的过程。
MANIFEST.MF 文件中的包约束条件主要是通过 Export-Package,Import-Package, Require-Bundle 和 DynamicImport-Package 四种方式来实现。
- Export-Package:定义了 Bundle 中需要被导出的包。一个 Bundle 中的包被导出之后,便可以通过 Import-Package 方式被其他 Bundle 使用。
- Import-Package:定义了需要导入的其他 Bundle 的包。所有需导入的包必须都能够找到相应的导出 Bundle,否则解析失败。
- Require-Bundle:定义了依赖的 Bundle。Require-Bundle 会对整个 Bundle 产生依赖,而 Import-Package 只会对指定的包产生依赖关系。
- DynamicImport-Package:定义需要动态导入的包。定义在运行时动态解析并加载共享包,这种方式很少使用。
当 Bundle 之间的关联关系建立起来之后,插件之间就可以实现类的互相引用。但是,通过建立插件间的关联关系,主要是实现插件之间类的互相关联和依赖,对于插件间的数据通信,可以通过下面三种方式实现。
使用 Eclipse 扩展点机制实现插件间通信
扩展和扩展点是实现 Eclipse 两个插件间通信的最常见的一种方式。每个插件都可以实现其他插件提供的扩展点,也可以提供扩展点供其他插件来扩展,这样,提供扩展点的插件和实现扩展点的插件间就建立了通信。尽管 Eclipse 3.0 以后的版本采用了 OSGi 框架,但是早期的扩展(Extension)和扩展点(Extension points)仍然保留,从用户的角度来看,并没有发生什么变化。
Eclipse 提供了图形化的方法来创建扩展点和扩展,以 Eclipse 插件工程“SamplePluginProjectA”为例,我们在该插件中创建扩展点和扩展。首先,双击该工程中的 plugin.xml 文件,便可以看到如下视图。该视图包含了所有插件相关的信息。在“Overview”页面中,选中“This Plug-in is a singleton”,将该插件设置为单例,因为提供扩展点的插件必须是一个单例。
图 1. 基于扩展点机制的 Eclipse 插件
其次,在“Extension Points”页面中,点击“Add”按钮,便可通过创建扩展点向导来创建一个新的扩展点。扩展点主要包括:扩展点 ID,扩展点名字以及扩展点 Schema 三个元素。当用户填完这三个值之后,点击“Finish”按钮,便创建完成一个扩展点。
图 2. 新建一个 Eclipse 扩展点
创建完扩展点,我们便可以在其它 Eclipse 插件工程中实现该扩展点,扩展点的一个具体实现我们便称为一个扩展,对于扩展,Eclipse 也提供了图形化的创建方式。在图 1 中的“Extension”tab 页中,单击“add”按钮,便可以通过下图中的创建扩展向导来创建扩展。
图 3. 新建一个 Eclipse 扩展
我们在插件工程根目录下的 plugin.xml 文件中,可以看到该插件提供的所有扩展点以及实现的扩展的信息。通过 Eclipse 扩展点机制,实现扩展点的插件与提供扩展点的插件,便能够进行数据的传递和方法调用,进而实现插件之间的通信。
使用 OSGi 框架实现组件间信息通信和共享
OSGi 框架提供了管理和控制组件(Bundle)、服务(Service)生命周期的机制,以及组件和服务在其生命周期内的交互。Eclipse3.0 以后版本中开发 Eclipse 插件时,只需要在创建时选择“an OSGi framework”,便可以创建一个基于 OSGi 的 Eclipse 插件。如下图所示:
图 4. 新建一个基于 OSGI 的 Eclipse 插件
这种方式生成的 Eclipse 插件中是看不到扩展和扩展点功能配置的,如下图所示,图中不再有“Extension”和“Extension Points”两个 tab 页面。
图 5. 基于 OSGI 创建的 Eclipse 插件
基于 OSGi 之后,Eclipse 插件编程变得更为灵活,Bundle 之间的通信将通过面向服务的组件编程来实现,关于过面向服务的组件编程的更多信息可以参见参考资料的《基于 OSGi 的面向服务的组件编程》一文。
使用单例模式实现插件间信息共享
另一种常见也比较简单的方法,就是使用单例模式来实现插件间的信息共享和消息通信。
对于两个 Eclipse 插件 A 和插件 B,插件 A 需要调用 B 中方法,将数据传给 B,并得到相应结果。对于这种单向的关联,使用单例模式是一种很简单的解决办法:我们可以在 B 中创建一个单例对象,单例对象中实现了 A 所需要调用的功能,同时将插件 B 的单例所在的包通过 Export-Package 导出,在 A 中通过 Import-Package 导入单例所在的包,从而建立两个插件之间的关联关系。这样,在 A 中便可以得到该单例对象,进而调用相应 B 的方法和使用插件 B 中的数据。这种方式主要用于实现插件间的数据通信,其实和第一种方式可以归为一类,第一种方式侧重建立插件间的类的关联,因而这种方式单独作为一类。
图 6. 通过事件机制实现 Eclipse 插件之间的通信
回页首
特殊情况下 Eclipse 插件间通信
在 Eclipse 插件开发中,我们曾经遇到过各种奇怪的插件间通信问题,这些问题由于受到版本,兼容性,产品维护性,遗留问题等限制,对于一些现有插件和遗留插件之间的通信,使用前面列举的插件间通信方式,往往比较麻烦或者解决不了问题。现将这些问题列举如下,并在后面给出一种通过事件机制解决问题的办法,结合前面总结的四种方式,读者可以在今后的工作中参考使用。
问题编号 |
问题描述 |
第一类问题 |
两个 Eclipse 插件 A 和 B,插件 A 能单独作为产品发布,不受 B 的约束,而插件 B 必须依赖于插件 A,作为产品发布时和 A 一起发布,数据传输的方向从 A 到 B。 |
第二类问题 |
两个 Eclipse 插件 A 和 B,插件 A 能单独作为产品发布,不受 B 的约束,而插件 B 必须依赖于插件 A,作为产品发布时要和 A 一起发布,并且当 A 和 B 同时存在时,数据传输的方向是双向的,也就是说,不单有从 A 到 B 的数据传递,还有从 B 到 A 的数据传递。 |
第三类问题 |
两个 Eclipse 插件 A 和 B,插件 A,B 均能单独作为产品发布,并且当 A 和 B 同时存在时,A 和 B 之间存在数据交换,数据传输的方向可能是单向的也可能是双向的。 |
第四类问题 |
两个 Eclipse 插件 A 和 B,插件 A 是早期产品,基于 Eclipse3.0 以前环境开发,后期因为产品功能的增强的需要,基于 Eclipse3.0 以后环境开发了插件 B,并且插件 B 需要从 A 获取数据。为了实现产品的维护,我们既要对 Eclipse3.0 以前的产品提供支持,又要对 Eclipse3.0 以后产品的支持。也就是说既要提供对 Eclipse 扩展方式插件的支持,又要实现对基于 OSGi 组件插件的支持。 |
回页首
通过事件机制实现 Eclipse 插件之间的通信
下面介绍一种通过事件机制实现插件之间的通信,这也是一类非常通用的办法。相对于前面总结的四类方式,可以作为很好的补充。
事件机制简介
事件机制,简单地说,就是当一个对象接收到外部或者自身的一些事件,将这些事件通知给另一个对象,另一个对象采取相应的处理。在 JDK 中,已经很好地对事件机制进行了抽象。
Java 事件模型由三种类型的元素组成:事件对象,事件源对象,监听器对象。
- 事件(Event)是由外部系统或者用户操作触发的一系列事情,例如外部系统调用本系统提供的一些服务,计时器触发某些操作,用户移动鼠标、单击鼠标和按下键盘等。事件对象(Event Object)是描述事件信息的对象,包括该事件所处的事件源,事件的具体内容等等。
- 事件源对象(Event Object)表示事件发生的位置。当源对象上发生一个事件时,就会创建一个该事件对应的事件对象。
- 监听器对象(Event Listener)是监听并接收该事件,并根据事件内容产生相应操作的对象。监听器对象要监听某个事件源上的事件,必须向该事件源注册。而事件源对象维护一个监听器列表,事件发生时,事件源会把事件对象发送给所有的注册监听器。
上面便是 Java 事件模型的一些基本概念,比较容易理解。在 Eclipse 插件通信中,我们可以借鉴这一模型。
使用事件机制解决 Eclipse 插件间通信
对于第二章中描述的第一、二类问题,考虑到数据从 A 向 B 传递,我们可以将 A 作为一个事件源,A 中用户的操作作为事件,B 作为事件监听者。无论插件是采用扩展点机制实现,还是采用 OSGi 技术实现的,当 B 被加载时,B 向 A 注册。注册成功之后,一旦 A 中有 B 感兴趣的事件发生,A 便会将事件通知给监听者 B。
对于第四类问题,因为既要解决早期的基于扩展机制实现的产品,又要实现对新的基于 OSGi 插件的支持,我们也可以采用事件机制来实现,这样可以避免维护两套代码。对于第三类问题,事件机制是无法实现的,这类问题要求插件间是完全可以独立存在的,因而只能使用 OSGi 框架实现组件间信息通信。
下面,我们将基于 Java 的事件机制来实现上述问题中 Eclipse 插件间的通信。
1 .首先,前两类问题中,由于插件 A 都能单独作为产品发布,因而插件 A 需要知道是否和插件 B 也存在于 Eclipse 环境中,如果存在,则启用相应的功能,否则,隐藏与插件 B 相关的功能。考虑到插件 ID 必须是唯一的,我们可以通过插件 B 的 ID 来检测 B 是否存在,使用 Bundle 类的 getSymbolicName() 即得到插件 ID。
清单 1. 检测插件是否存在
private boolean isPluginBExist(BundleContext context){
boolean isBExist = false;
Bundle[] bundles = context.getBundles();
int len = bundles.length;
for(int i = 0; i < len; i++){
Bundle bundle = bundles[i];
// 插件 B 的 ID 是 PluginProjectB
String name = (String)bundle.getSymbolicName();
if(name.equals("PluginProjectB")){
isBExist = true;
}
}
return isBExist;
}
|
2 .其次,在插件 A 中定义事件源对象,事件,事件监听器接口。
事件源对象提供对监听对象管理,包括监听器对象的注册方法,注销方法,以及将事件通知给监听器。事件源维护了一个监听者队列,所有监听者都需要向事件源注册,加入该队列,当监听者不再监听事件时,便向事件源注销,退出该队列。
清单 2. 定义事件源对象
public class PluginAEventSource {
// 事件源中维护了一个管理所有监听者的队列
private List<PluginAEventListenerInterface>
listenerList = new ArrayList<PluginAEventListenerInterface>();
public PluginAEventSource() {
}
// 将监听者对象添加到队列中
public synchronized void addMyEventListener(PluginAEventListenerInterface listener) {
listenerList.add(listener);
listener.handleEvent(null);
}
// 将监听者对象从队列中移出
public synchronized void removedMyEventListener(
PluginAEventListenerInterface listener) {
listenerList.remove(listener);
}
|
// 事件源将事件通知给监听者。在此方法中,事件源可以根据一些条件,选择将事件通知给特定的监听者
public void notifyDemoEvent(PluginAEvent pe) {
for(int i = 0; i < listenerList.size(); i++) {
PluginAEventListenerInterface listener = listenerList.get(i);
listener.handleEvent(pe);
}
}
}
|
事件对象包装了事件相关的具体信息、作为参数传递给监听器。事件对象的具体信息内容用户可以自己定制,例如包含事件发生的时间,事件源对象等等。
清单 3. 定义事件
public class PluginAEvent extends java.util.EventObject {
|
// 事件的构造方法
public PluginAEvent(Object source) {
super(source);
}
|
// 提供与事件相关的信息
public String getEventInformation() {
return "sample event information";
}
}
|
事件监听器接口,所有事件监听者都应该实现该接口,并在具体实现中提供相应的事件处理方法。事件发生时,事件源便调用监听器接口提供的事件处理方法统一通知监听者队列中的所有事件监听器对象。
清单 4. 定义事件监听器接口
public interface PluginAEventListenerInterface extends java.util.EventListener {
|
// 处理事件
public void handleEvent(PluginAEvent pe);
|
}
3 .在插件 B 中实现监听器对象,监听器对象必须实现插件 A 中的事件监听接口,提供自身的事件处理方法。并且在插件 B 被加载时,调用插件 A 中的 PluginAEventSource 类中的 addMyEventListener 方法,将该监听器对象向插件 A 的事件源注册。
清单 5. 实现监听器对象
public class PluginBEventListener implements PluginAEventListenerInterface {
// 实现事件处理接口,提供插件 B 自身的处理方式
public void handleEvent(PluginAEvent de) {
System.out.println("Plugin B operation...");
de.getEventInformation();
}
}
|
将两个插件之间的调用关系通过事件机制建立起来之后,我们便可以在监听器和事件中添加用户的逻辑功能。当然,上面是基于 Java 事件模型的一种参考实现,JDK 提供的 java.util.EventObject 和 java.util.EventListener 分别被用作事件和事件监听器接口的父类。开发人员也可以完全不依赖 Java 事件机制,所有基类都可以由用户自己定义。
回页首
小结
本文首先介绍了四种最常用的 Eclipse 插件间通信方法,其中使用 Eclipse 扩展和使用 OSGi 框架是由 Eclipse 框架本身提供的,这两种方式实现的插件也最符合 Eclipse 架构的设计理念。事件机制是一类比较通用的解决问题的方法,可以作为插件常用通信方式的一种很好的补充,将其用于解决一些特殊的插件间通信问题,往往能取得事半功倍的效果。
参考资料
作者简介
刘力,IBM 中国软件开发实验室 SOA 设计中心高级工程师,具有多年的 J2SE, J2EE 和 Web Service 开发经验,目前专注于 SOA 项目实践和相关的理论、工具的研究和开发。您可以通过以下方式与他联系 liulisd@cn.ibm.com。
杜冰冰,IBM中国软件开发实验室SOA设计中心软件开发工程师,主要从事SOA 相关技术的研究和相关项目的实施,她的主要技能和专长包括 SOA、Web 服务以及 Eclipse 开发等领域。联系方式:dubb@cn.ibm.com
相关推荐
Eclipse插件开发是扩展Eclipse功能的重要方式,开发者可以通过编写插件来增加Eclipse平台的自定义功能,从而提升开发效率,定制个性化的开发环境。Eclipse插件开发学习笔记将带领我们深入了解Eclipse插件开发的...
书中还会涉及如何设计和组织插件结构,以及如何利用Eclipse的模型和事件机制实现组件间的通信和协作。 通过阅读《Eclipse插件开发(第3版 - 高清)》,开发者不仅可以掌握Eclipse插件开发的基本技能,还能了解到...
随着对Eclipse插件开发的深入了解,开发者会遇到更多高级主题,如插件间的通信、插件性能优化、国际化支持等。 **3.1 插件间通信** 插件之间可以通过服务注册和查找机制进行通信。Eclipse的服务框架基于OSGi,插件...
Eclipse通过扩展点机制允许插件之间进行通信。一个插件可以定义一个扩展点,其他插件可以声明对这个扩展点的扩展,实现功能的组合。 7. **插件开发工具**: Eclipse提供了一些内置的插件开发工具,如PDE(Plug-in...
1. **Eclipse插件体系结构**:Eclipse采用OSGi(Open Services Gateway Initiative)框架作为其基础,使得插件之间可以互相依赖和通信。理解OSGi的基本概念,如模块化、服务注册和发现机制,是插件开发的基础。 2. ...
- **Extension Points**:这是Eclipse插件系统的核心,是插件之间交互的接口,定义了可以被其他插件扩展的点。 - **ActionSets**:包含了一系列的菜单项和工具栏按钮,是用户界面中执行操作的方式。 - **...
- 使用事件总线(Event Bus)进行插件间的异步通信,通过发布/订阅模型传递消息。 - 利用服务(Service)模式,通过服务注册和发现机制共享数据和服务。 9. **国际化(Internationalization)**: - 使用....
总的来说,Eclipse插件开发是一项需要深入理解Eclipse平台架构、熟悉SWT和JFace、掌握扩展点和插件通信机制的技术。通过学习和实践,开发者能够充分利用Eclipse的开放性,定制自己的开发环境,提高工作效率。对于...
Eclipse插件通过扩展点(Extension Point)和贡献点(Extension)机制实现功能扩展。扩展点是Eclipse定义的接口,允许插件对其进行扩展。贡献点则是插件实际实现的功能,它们通过XML配置文件(通常位于plugin.xml)...
Eclipse插件基于OSGi框架,每个插件都是一个独立的模块,通过提供服务和使用其他插件的服务来实现功能扩展。插件之间通过接口进行通信,这种模块化的设计使得插件可以灵活地添加、更新或移除,而不会影响到整个系统...
了解如何响应Eclipse内发生的事件,以及如何通过服务注册实现插件间的通信,是增强插件功能和协同工作的关键。 第十七章至第二十章可能讲解了调试和测试插件的方法,包括使用Eclipse自身的调试器,以及编写和运行...
"扩展点"是Eclipse插件系统的关键概念,它是Eclipse平台允许插件之间进行通信和扩展的机制。通过定义和实现扩展点,插件可以声明它们提供的功能,而其他插件则可以通过这些扩展点来使用或扩展这些功能。 在压缩包的...
理解事件监听器和事件发布机制,以及如何实现自定义事件。 9. **调试与测试**:利用Eclipse自身的调试工具进行插件的调试,以及如何创建单元测试和集成测试来确保插件的质量。 10. **部署与发布**:将开发完成的...
7. **服务和事件**:讲解了Eclipse的服务机制,如模型服务、工作台服务等,以及如何利用事件系统实现插件间的通信和响应用户操作。 8. **调试与测试**:提供了关于如何调试插件和进行单元测试的指导,确保代码的...
8. **插件间通信**:理解服务定位器(Service Locator)模式和事件总线(Event Bus)机制,了解如何在不同插件之间共享数据和实现通信。 9. **国际化(i18n)与本地化**:如果插件需要面向全球用户,那么学习如何...
通过这个教程,你将了解Eclipse插件开发的全过程,从创建新项目到发布插件,再到实现复杂功能的集成。这将使你具备创建个性化开发环境的能力,提升开发效率。在实践中,不断探索和学习Eclipse的API和最佳实践,将让...
为了便于在Eclipse中开发和调试Hadoop项目,压缩包还提供了`hadoop-eclipse-plugin-2.6.0.jar`,这是一个Eclipse插件。安装这个插件后,开发者可以在Eclipse中直接创建、运行和调试Hadoop MapReduce程序。插件提供了...
5. **事件机制**:Eclipse中的事件模型使得组件之间可以通过监听和触发事件进行通信,减少了组件之间的耦合。例如,当一个文件被修改时,相关的编辑器和视图可以接收到通知并更新自身状态。 6. **调试支持**:...
Eclipse通过扩展点(Extension Point)和扩展(Extension)机制实现插件间的通信。扩展点定义了可以被其他插件扩展的接口,而扩展则是实现这些接口的具体代码。 七、插件生命周期 Eclipse插件有明确的生命周期管理...