Dissect Eclipse Plugin Framework (4)

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

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

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

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

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

  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 plugin-id="com.xerdoc.desktop.core.ui.swt" plugin-version="0.2.0" match="compatible"/>
  4. </requires>

这是Office Excel Parser插件清单的片断。如果这个插件的类加载器无法加载某个需要的类或者资源,将会委托"com.xerdoc.desktop.core"插件或者"com.xerdoc.desktop.core.ui.swt"插件的类加载器去加载。

系统Native Library(比如SWT插件中要用到的系统本地库)的加载也是PluginClassLoader的功能。

就举SWT的例子,熟悉SWT的人都知道,运行SWT应用程序的时候需要添加以下命令行参数:

-Djava.library.path="/home/elan/workspace/xerdoc_ds/swt-native"

这就是为了让类加载器能够在相应的目录("/home/elan/workspace/xerdoc_ds/swt-native")下面找到需要的系统本地库资源。但是这样的命令行参数对于某些应用并不合适。对于Xerdoc DS来说,SWT的UI界面也同样是一个插件,同时也还会有其它用到本地资源库的插件,总不能增加一个插件还要修改命令行参数吧?因此,需要修改ClassLoader,使之能够加载指定的Native Library。方法就是重写findLibrary函数:

  1. /**
  2.  * Returns the absolute path name of a native library.  The VM invokes this
  3.  * method to locate the native libraries that belong to classes loaded with
  4.  * this class loader. If this method returns <tt>null</tt>, the VM
  5.  * searches the library along the path specified as the
  6.  * "<tt>java.library.path</tt>" property.  </p>
  7.  *
  8.  * @param  libname
  9.  *         The library name
  10.  *
  11.  * @return  The absolute path of the native library
  12.  *
  13.  * @see  System#loadLibrary(String)
  14.  * @see  System#mapLibraryName(String)
  15.  *
  16.  * @since  1.2
  17.  */
  18. protected String findLibrary(String libname) {
  19.     return null;
  20. }

重写其实很简单,只需要根据每个插件需要加载Native Library的目录来搜索就可以了,比如这是Linux下面,SWT插件清单的片断:

  1. <library id="swt-native" path="swt-native/" type="native_library">
  2.     <export prefix="*"/>                   
  3. </library>

这样,在找Native Library的时候就可以从"$PLUGIN_HOME/swt-native/"这个目录中找到相应的so文件(Linux下的动态链接库)。

最后来说说资源文件(比如说png, ico等等),其实同加载类资源一样,只要在"library"中声明的目录,就都会加入到类加载器的类搜索路径中去,这样,我们都可以直接访问里面的资源。

Popularity: 28%

Related entries:

  • No Related Posts

One Response to “Dissect Eclipse Plugin Framework (4)”

  1. qianshiming Says:

    相当感谢你的文章。我最近正为这个东西发愁呢,看到你的文章就好像是看到了黎明的曙光一样。希望能够将来成长得和你一样厉害,可以去帮助更多的人。
    工作顺利!

Leave a comment

(required)

(required)


Information for comment users
Line and paragraph breaks are implemented automatically. Your e-mail address is never displayed. Please consider what you're posting.

Use the buttons below to customise your comment.


RSS feed for comments on this post | TrackBack URI

 

Creative Commons License
This work is licensed under a Creative Commons License.

你在我的心里永远是故乡