来源:http://phoenix-bupt.javaeye.com/blog/47161 2007-01-15

Dissect Eclipse Plugin Framework

在讨论Xerdoc DSearch的架构的时候,我们就讨论决定采用Eclipse Plugin Framework,可惜那时Eclipse Plugin Framework和SWT以及其它耦合比较大,因此,决定借鉴Eclipse Plugin Framework的思想,来实现一个自己的轻量级的Plugin Framework。

一晃已经过去快一年了,其实非常早就想把自己研究Eclipse Plugin Framework的心得写下来,米嘉也一再催促,不过一直比较懒,觉着这个题目实在要写的太多,于是一直拖着。后来想想,真的应该早点儿把自己的一些粗糙想法写出来,即是对自己的一个总结,也能对其他人有些帮助。

Eclipse Plugin Framework是一套非常成功的插件框架结构,它的架构师之一就是鼎鼎大名的Erich Gamma,设计模式的作者之一。Eclipse JDT就是架构在这个插件平台上的一个杰出的Java IDE。Eclipse 良好的插件架构也形成了很好的"An architecture of participation",你可以在Eclipse的社区中找到各种各样的插件,这些插件又极大的扩充了Eclipse的功能,提高了易用性。

记着候捷在写《深入浅出MFC》的时候,用很简单甚至粗糙的一些例子来模仿MFC内部的行为(比如消息循环等),效果非常好。我也想用一些Xerdoc DSearch中的代码来模仿一下Eclipse的插件架构。

注:这里所指的Eclipse Plugin Framework的Codebase是2.1.3,因为当时研究的时候,3.0(OSGi Based)还没出来

1) 插件清单

Eclipse中的插件都用XML文件来进行描述,比如:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <plugin id="org.eclipse.pde.source" name="%pluginName" version="2.1.3" provider-name="%providerName">
  3. <runtime></runtime>
  4. <extension point="org.eclipse.pde.core.source">
  5. <location path="src"> </location>
  6. </extension>
  7. </plugin>

这个清单中描述了插件的绝大多数信息,包括插件的id, name(这个是经过i18n的),版本,启动类等。同时,所有的扩展、扩展点也都在这里定义,此插件对外提供的库(包括Native库)以及资源也都要定义在这个文件中。

这个文件的名称是"plugin.xml",Eclipse启动的时候会扫描"plugins"目录下的所有"plugin.xml"文件,进而装载所有的插件。(注:为了提高效率,Eclipse会保存一个中间文件来加速装载,这里并不讨论。)

因此,你需要用XML Parser将这些信息Parse出来,形成插件的基本信息,具体选用Dom、SAX还是Pull Parser都无所谓。

Eclipse采用微内核+插件的模式构架,也就是说,除了一个微小的核儿之外,所有的东西都是插件(All are plugins)。

2) 扩展点概述

Eclipse Plugin Framework最核心的概念应该就要算"Extension Point"(扩展点)了。

打个通俗的比方,"Extension Point"就像我们日常生活中的插销板,而"Extension"就是能够插入到这个插销板上面的插销。

系统的开放性很大程度上也取决于系统究竟给你多少"Extension Point"。

WordPress的Plugin Framework也同样采用这种"Extension Point"的概念构架,它为自己几乎所有的应用都定义了扩展点。比如,有的插件可以在"Header显示扩展点"的地方加入代码来添加CSS样式表,Google Sitemap插件可以在"文章发布扩展点"的地方进行Google Sitemap的提交,Creative Commons插件可以在"Footer显示扩展点"处增加"Creative Common"信息等等。

对于Eclipse来说,因为采用微内核+插件的方式,因此,定义扩展点也就成了你的任务,在扩展功能的同时,你也可以在任何你觉得可能被扩展的地方定义扩展点,来方便其他人扩展系统的功能。

举个例子,Eclipse SWT UI中,工具栏、视图都留有扩展点,这样可以方便的进行扩展。

Eclipse的插件扩展点都定义在"plugin.xml"文件中,每个插件要扩展哪些扩展点也定义在这个文件中。举个例子(DS中Core插件的一个片断):

  1. <extension-point id="Parser">
  2. <parameter-def id="class" type="string"/>
  3. <parameter-def id="icon" type="string"/>
  4. </extension-point>

这并不是Eclipse Plugin的DTD所规范的"plugin.xml"格式,而是一个非常简单的模拟。它描述的是一个"Parser"的扩展点。因此,你可以扩展任何自 己的Parser(比如QQ聊天记录的Parser,Foxmail Mail的Parser,等等),增加Desktop Search可处理文件的范围。

3) ClassLoader

了解Eclipse的Plugin Framework需要对ClassLoader(类装载器)有比较深入的了解,建议读读JDK的源代码,会很有帮助。

ClassLoader - 顾名思义,就是Java中用来装载类的部分,要将一个类的名字装载为JVM中实际的二进制类数据。在JVM中,任何一个类被加载,都是通过 ClassLoader来实现的,同时,每个Class对象也都有一个引用指向装载他的ClassLoader,你可以通过 getClassLoader()方法得到它。

ClassLoader只是一个抽象类,你可以定义自己的ClassLoader来实现特定的Load的功能。Eclipse Plugin Framework就实现了自己的ClassLoader。

ClassLoader使用所谓的"Delegation Model"(“双亲委托模型”)来查找、定位类资源。每一个ClassLoader都有自己一个父ClassLoader实例(在构造的时候传入),当 这个ClassLoader被要求加载一个类时,它首先会询问自己的父ClassLoader,看看他是否能加载(注意:这个过程是一直递归向上的),如 果不能的话,才自己加载。

Java ClassLoader的体系结构是

最后来看一下代码:

  1. protected synchronized Class<?> loadClass(String name, boolean resolve)
  2. throws ClassNotFoundException
  3. {
  4. // First, check if the class has already been loaded
  5. Class c = findLoadedClass(name);
  6. if (c == null) {
  7. try {
  8. if (parent != null) {
  9. c = parent.loadClass(name, false);
  10. } else {
  11. c = findBootstrapClass0(name);
  12. }
  13. } catch (ClassNotFoundException e) {
  14. // If still not found, then invoke findClass in order
  15. // to find the class.
  16. c = findClass(name);
  17. }
  18. }
  19. if (resolve) {
  20. resolveClass(c);
  21. }
  22. return c;
  23. }

可见,ClassLoader首先会查找该类是否已经被装载,如果没有,就询问自己的父ClassLoader,如果还不能装载,就调用findClass()方法来装载类。所以,一般简单的自定义ClassLoader只需要重写findClass方法就可以了。

如果你的类不是文件,比如说是序列化在数据库中的二进制流或者网络上的Bit流,就需要重写defineClass()方法,来将二进制数据映射到 运行时的数据结构。另外一种需求也可能是你需要对类文件进行某种操作(比如按位取反?),也需要定义自己的defineClass()方法。

还需要注意的是资源的加载和系统Native库的加载,这个可以留在以后再作讨论。

4) Plugin与PluginClassLoader

准备工作做完,就可以来看看具体实现过程。

我们模拟的几个重要的类是:

Plugin: 插件类,描述每个具体插件;

PluginDescriptor: 插件描述符,记录了插件的ID、Name、Version、依赖、扩展点等;

PluginManager: 插件管理器,负责所有插件资源的管理,包括插件的启动、停止、使能(Enable/Disable)等等;

PluginRegistry: 插件注册表,提供了一个由插件ID到Plugin的映射;

我们首先来定义一个简单的Plugin:

  1. public abstract class Plugin {
  2. /**
  3. * Plugin State
  4. */
  5. private boolean started_;
  6. private final PluginManager manager_;
  7. private final IPluginDescriptor descriptor_;
  8. public Plugin(PluginManager manager, IPluginDescriptor descr) {
  9. manager_ = manager;
  10. descriptor_ = descr;
  11. }
  12. /**
  13. * @return descriptor of this plug-in
  14. */
  15. public final IPluginDescriptor getDescriptor() {
  16. return descriptor_;
  17. }
  18. /**
  19. * @return manager which controls this plug-in
  20. */
  21. public final PluginManager getManager() {
  22. return manager_;
  23. }
  24. final void start() throws PluginException {
  25. if (!started_) {
  26. doStart();
  27. started_ = true;
  28. }
  29. }
  30. final void stop() throws PluginException {
  31. if (started_) {
  32. doStop();
  33. started_ = false;
  34. }
  35. }
  36. public final boolean isActive() {
  37. return started_;
  38. }
  39. /**
  40. * Get the resource string
  41. * @param key
  42. * @return
  43. */
  44. public String getResourceString(String key) {
  45. IPluginDescriptor desc = getDescriptor();
  46. return desc.getResourceString(key);
  47. }
  48. /**
  49. * Get the Plugin Path
  50. *
  51. * @return
  52. */
  53. public String getPluginPath() {
  54. return getDescriptor().getPluginHome();
  55. }
  56. /**
  57. * Template method, which will do the really start work
  58. *
  59. * @throws Exception
  60. */
  61. protected abstract void doStart() throws PluginException;
  62. /**
  63. * Template method, which will do the really stop work
  64. *
  65. * @throws Exception
  66. */
  67. protected abstract void doStop() throws PluginException;
  68. }

可见,这只是一个抽象类,每个插件需要定义自己的派生自"Plugin"的子类,作为本插件的一个入口。其中doStart和doStop是两个简单的模板方法,每个插件的初始化和资源释放操作可以定义在这里。

接下来我们看看系统的启动流程:首先将所有的插件清单读入("plugin.xml"),并根据这个文件解析出 PluginDescriptor(包括这个Plugin的所有导出库、依赖插件、扩展点等等),放到PluginRegistry中。这个过程也是整个 插件平台的一个非常重要的部分,需要从插件清单中解析的部分包括:

  1. 每个插件所依赖的的插件列表(在"plugin.xml"中用"require" element标识);
  2. 每个插件要输出的资源和类(在"plugin.xml"中用"library" element标识);
  3. 每个插件所声明的扩展点列表;
  4. 每个插件所声明的扩展列表(扩展其它扩展点的扩展)。

当把所有的插件信息都读入到系统中,就可以根据自己的需要来启动指定的插件了(比如,在Xerdoc DS中,首先,我们会启动Core插件)。

启动一个插件的步骤是:

  1. public Plugin getPlugin(String id) throws PluginException {
  2. ... ...
  3. IPluginDescriptor descr = pluginRegistry_.getPluginDescriptor(id);
  4. if (descr == null) {
  5. throw new PluginException("Cannot found this plugin " + id);
  6. }
  7. result = activatePlugin(descr);
  8. return result;
  9. }
  10. private synchronized Plugin activatePlugin(IPluginDescriptor descr)
  11. throws PluginException {
  12. ... ...
  13. try {
  14. try {
  15. // 首先需要检查这个插件所依赖的插件是否都已经启动,
  16. // 如果没有,则需要先启动那些插件,才能启动本插件
  17. checkPrerequisites(descr);
  18. } catch (PluginException e) {
  19. badPlugins_.add(descr.getId());
  20. throw e;
  21. }
  22. //    得到插件的主类名
  23. //    这个信息也是定义在"Plugin.xml"中,
  24. //    并且在加载插件信息的时候读入到PluginDescriptor中的
  25. String className = descr.getPluginClassName();
  26. if ((className == null) || "".equals(className.trim())) {
  27. result = null;
  28. } else {
  29. Class pluginClass;
  30. try {
  31. //    用每个插件自己的PluginClassLoader来得到这个插件的主类
  32. pluginClass = descr.getPluginClassLoader().loadClass(
  33. className);
  34. } catch (ClassNotFoundException cnfe) {
  35. badPlugins_.add(descr.getId());
  36. throw new PluginException("can't find plug-in class "
  37. + className);
  38. }
  39. try {
  40. Class pluginManagerClass = getClass();
  41. Class pluginDescriptorClass = IPluginDescriptor.class;
  42. Constructor constructor = pluginClass
  43. .getConstructor(new Class[] { pluginManagerClass,
  44. pluginDescriptorClass });
  45. //    调用插件默认的构造函数
  46. //    Plugin(PluginManager, IPluginDescriptor);
  47. result = (Plugin) constructor.newInstance(new Object[] {
  48. this, descr });
  49. } catch (InvocationTargetException ite) {
  50. ... ...
  51. } catch (Exception e) {
  52. ... ...
  53. }
  54. try {
  55. result.start();
  56. } catch (Exception e) {
  57. ... ...
  58. }
  59. ... ...
  60. }
  61. }
  62. return result;
  63. }

其实最核心的工作就是三步:

  1. 首先检查这个插件所依赖的其它插件是否已经被启动,如果没有,则需要首先将那些插件启动;
  2. 根据类名,用插件类加载器加载这个类(这个类是Plugin类的一个派生类);
  3. 调用Plugin类的默认的构造函数(主要是为了将PluginManager和PluginDescriptor传进去)。

这就用到了前面说过的类加载器(ClassLoader),Eclipse中定义了插件类加载器(PluginClassLoader)。插件类加载器(PluginClassLoader)其实很简单,它派生自URLClassLoader -

This class loader is used to load classes and resources from a search path of URLs referring to both JAR files and directories.

PluginClassLoader会将PluginDescriptor中声明输出的路径(可以是JAR文件,可以是类路径,可以是资源路径)加入到此URLClassLoader类加载器的搜索路径中去。

比如:

  1. <runtime>
  2. <library id="com.xerdoc.desktop.view.htmlrender" path="XerdocDSHTMLRender.jar" type="code">
  3. <export prefix="*"/>
  4. </library>
  5. <library id="resources" path="image/" type="resources">
  6. <export prefix="*"/>
  7. </library>
  8. </runtime>

PluginClassLoader会将"XerdocDSHTMLRender.jar"和"image/"目录都加入到URLClassLoader的类搜索路径中去,这样,就可以用这个类加载器来加载相应的插件类和资源了。

PluginClassLoader加载插件的策略是:

首先试图从父ClassLoader加载(系统类加载器),如果无法加载则会试图从本类加载器加载,如果还是找不到,这时的行为与一般的 URLClassLoader不同,也PluginClassLoader最大的特色:它会试图从此插件的需求依赖插件("require"形容的插件) 中去加载需求的类或者资源。

比如下面这个例子:

  1. <requires>
  2. <import plugin-id="com.xerdoc.desktop.core" plugin-version="0.4.0" match="compatible"/>
  3. <import

Dissect Eclipse Plugin Framework相关推荐

  1. Java Plugin Framework (JPF) java插件框架学习

    2019独角兽企业重金招聘Python工程师标准>>> JPF (Java Plugin Framework) 是一个插件框架,类似Eclipse的插件系统,不过这个的依赖关系是使用 ...

  2. 关于Eclipse中的开源框架EMF(Eclipse Modeling Framework),第三部分

    Eclipse Modeling Framework(EMF)中包含了一个开放源代码的工具 JMerge,这个工具可以使代码生成更加灵活,可定制性更好.本文使用一个例子来展示如何将 JMerge 添加 ...

  3. Fat Jar Eclipse Plug-In Tutorial

    Fat Jar Eclipse Plug-In [FJEP] (http://fjep.sourceforge.net/) 是应用One-Jar(http://one-jar.sourceforge. ...

  4. GWT(Google Web Tookit) Eclipse Plugin的zip下载地址(同时提供GWT Designer下载地址)

    按照Eclipse Help->Install new software->....(这里是官方安装文档:http://code.google.com/intl/zh-CN/eclipse ...

  5. hadoop eclipse plugin windows下载集合

    收集了hadoop稳定版本的eclipse plugin for windows.资源分一律为0分 hadoop-eclipse-plugin-1.2.1.jar http://download.cs ...

  6. Eclipse+ GNU ARM Eclipse Plug-in+ Sourcery G++ Lite Edition for ARM+OPENCD+Jlink的开源开发环境。

    Eclipse+ GNU ARM Eclipse Plug-in+ Sourcery G++ Lite Edition for ARM+OPENCD+Jlink的开源开发环境. 具体介绍: Eclip ...

  7. Eclipse+ GNU ARM Eclipse Plug-in+ Sourcery G++ Lite Edition for ARM+OPENCD+Jlink的开源开发环境

    作者:emouse 转自:http://blog.csdn.net/haozi_1989/article/details/6023242 Eclipse+ GNU ARM Eclipse Plug-i ...

  8. Eclipse+ GNU ARM Eclipse Plug-in+ Sourcery G++ Lite Edition for ARM+OPENCD+Jlink

    这两天在尝试着搭建Eclipse+ GNU ARM Eclipse Plug-in+ Sourcery G++ Lite Edition for ARM+OPENCD+Jlink的STM32开发环境, ...

  9. RubyOnRails的安装和eclipse plugin

    Windows系统下开发环境的搭建 本文中将介绍如何在Windows操作系统下搭建RoR开发环境. 1.远程安装 a.步骤1:下载并安装Ruby一键安装包 下载最新的Ruby 1.8.4-16一键安装 ...

  10. EGit(Git Eclipse Plugin)使用(转载)

    发现一片篇egit深度好文章先转载一下 以下是原文 原先博客地址http://shihlei.iteye.com/blog/2124411 -– EGit(Git Eclipse Plugin)使用 ...

最新文章

  1. 元素多层嵌套,JS获取问题
  2. 云原生安全的挑战与实践
  3. HTML基础知识个人总结
  4. iphone用计算机显示器,苹果显示器接普通电脑怎么操作【详细介绍】
  5. 北大中文期刊目录_最新版语言学C刊及北大核刊投稿方式全收录
  6. 如何选择和计算滤波电容?--电容使用详述
  7. [20190805]在小程序中使用npm包
  8. 四、Dynamic-programming algorithm Dynamic--LCS
  9. MySQL不能启动 Can't start server : Bind on unix socket: Permission denied
  10. 微软MIX10开幕 支持在线观看
  11. php 中正则表达式详解
  12. matlab中esp=1.0e-3,ESP系列杂谈(一): eFuse 简介
  13. deamon(大鹅模拟器steam)
  14. Linux常用命令分享
  15. 核磁共振测井设备市场现状及未来发展趋势分析
  16. Windows 7 修改系统临时文件夹
  17. matlab 柱状图不同颜色(取巧哈)
  18. 关于自己写的第一份简历
  19. 开启手机找回连接服务器失败,原神连接服务器失败什么意思?连接服务器失败解决方法...
  20. [剑指Offer]斐波那契数列、跳台阶、兔子数量问题(递归、非递归)(Java)

热门文章

  1. CSS 单(多)行文本超过部分显示省略号,解决数字或英文不换行问题
  2. 使用极狐GitLab限制开发者使用CI/CD的权限,三种方案
  3. 合肥工业大学的计算机专业的导师,合肥工业大学计算机与信息学院硕士生导师:程运安副教授...
  4. android 广告平台—杀毒软件是如何知道是否有广告的
  5. docker的创建 指定CPU 内存 网络 硬盘_为什么 CPU 访问硬盘很慢
  6. 青玉案·元夕 【宋代】辛弃疾
  7. 轻量级服务器和ECS云服务器有什么区别?
  8. 用什么词典可以翻译php,PHP调用有道词典翻译API实现通译功能及代码
  9. “伽利略”卫星定位系统
  10. 如何刷微博,怎么刷微博下拉框,怎样刷微博相关搜索