在介绍"Extension Point"之前,先来看一个概念:Eclipse中著名的懒加载原则(Lazy Loading Rule)。

懒加载法则:只有在真正需要的时候才加载插件,实现起来最重要的方面就是声明和实现的分离。

插件的外形(比如名字,ID,图标)等等都在插件描述清单"plugin.xml"中声明,而具体功能封装在class文件中。

这种懒加载原则表现在各个方面,比如最基本的插件启动。系统在启动的时候,只加载和启动最必须的一些插件,而其它插件只有在真正用到的时候才被加载和启动,这样可以最大限度的节省系统启动时的资源和时间。而对用户来说,每次启动也确实有很多插件根本不会去用到。

懒加载还表现在扩展点的应用上,待会儿可以看到具体例子。

接下来就看看"Extension Point",像前面曾经介绍的那样,"Extension Point"是Eclipse Plugin Frame中最核心的概念。首先来看一个Xerdoc DS中"Extension Point"和"Extension"的声明:

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

这是"core"插件中关于"Parser"的扩展点,你可以定义不同的扩展,来增强Xerdoc DS能够索引文件类型的范围。

  1. <extension plugin-id="com.xerdoc.desktop.core" point-id="Parser" id="MP3FileParser">
  2.     <parameter id="class" value="com.xerdoc.desktop.parser.mp3.MP3FileParser"/>
  3.     <parameter id="icon" value="image/mime_icon_Music_mp3.gif"/>
  4. </extension>

这是"mp3 parser"插件中对此扩展点的一个扩展声明,声明了自己扩展的类和图标。图标完全是为了显示,而其中的"class"则是为了加载真正的功能。

"core"插件会在需要的时候加载所有扩展了这个扩展点的插件:

  1. private static void loadSupportedParsers() {
  2.     ... ...
  3.    
  4.     try {
  5.         descriptor = manager.getPlugin("com.xerdoc.desktop.core")
  6.                 .getDescriptor();
  7.  
  8.         //    得到Parser扩展点声明
  9.         IExtensionPoint extPoint = descriptor.getRegistry()
  10.                 .getExtensionPoint(descriptor.getId(), "Parser");
  11.  
  12.         //    根据这个声明得到所有连接到这个扩展点的扩展对象
  13.         for (Iterator it = extPoint.getConnectedExtensions().iterator(); it
  14.                 .hasNext();) {
  15.             IExtension ext = (IExtension) it.next();
  16.            
  17.             //    根据扩展对象生成Parser代理
  18.             //    也就是著名的懒加载法则
  19.             ParserProxy parser = ParserProxy.createParserProxy(ext);
  20.  
  21.             parserList_.add(parser);
  22.         }
  23.     } catch (PluginException e) {
  24.         e.printStackTrace();
  25.     }
  26. }

ParserProxy其实就是Parser的代理,它只读取Parser的表现部分,比如图标,名称等等,而实例化的操作要等到具体使用的时候才去调用。

  1. ...
  2.  
  3. /**
  4.  * Parser Extension Point
  5.  */
  6. private IExtension extension_;
  7.  
  8. /**
  9.  * Real Parser Instance, it will not be load until really needed
  10.  */
  11. private AbstractParser realParser_;
  12.  
  13. ...
  14.    
  15. private ParserProxy(IExtension extension) {
  16.     extension_ = extension;
  17. }
  18.  
  19. /**
  20.  * Create the Parser Proxy based on the Extension
  21.  *
  22.  * @param extension
  23.  * @return
  24.  */
  25. public static ParserProxy createParserProxy(IExtension extension) {
  26.     return new ParserProxy(extension);
  27. }

当真正需要这个Parser的时候,ParserProxy会生成相应的真实对象(如其名,这是Proxy模式的典型应用):

  1. private AbstractParser getRealInstance() {
  2.     if (realParser_ == null) {
  3.         try {
  4.             //    得到插件
  5.             //    如果插件还未被激活,这里要激活这个插件
  6.             //    LAZY LOADING!!!
  7.            
  8.             Plugin plugin = CorePlugin.getInstance().getManager()
  9.                     .getPlugin(
  10.                             extension_.getDeclaringPluginDescriptor()
  11.                                     .getId());
  12.  
  13.             if (plugin != null) {
  14.                 Class pluginCls = plugin.getClass();
  15.                
  16.                 //    得到主类
  17.                 Class cls = extension_.getDeclaringPluginDescriptor()
  18.                         .getPluginClassLoader().loadClass(
  19.                                 extension_.getParameter("class")
  20.                                         .valueAsString());
  21.  
  22.                 if (cls != null) {
  23.                     if (pluginCls.isAssignableFrom(cls)) {
  24.                         realParser_ = (AbstractParser) plugin;
  25.                     } else {
  26.                         //    反射生成这个类
  27.                         realParser_ = (AbstractParser) cls.newInstance();
  28.                     }
  29.                 }
  30.             }
  31.         } catch (Exception e) {
  32.             return null;
  33.         }
  34.     }
  35.  
  36.     return realParser_;
  37. }

然后,就可以调用这个Parser完成必要的工作了。这就是"Extension Point"的大概的工作流程。

在Eclipse中,遍地都是这样的例子,比如:Eclipse Platform的菜单显示就是一个扩展点,Eclipse在显示菜单之前首先会从系统的插件列表(PluginRegistry)中寻找所有扩展此扩展点的插件,取得图标和名字显示出来,然后在用户点击的时候生成真实的对象,并调用之,嗯,还是懒加载法则。

BTW:菜单扩展中的类是IAction,点击的时候调用它的run()函数,Command模式。

写这些的时候想起来,在声明每个扩展点前,这个插件都需要定义一定的Interface,也就是扩展这个扩展点的插件需要遵循的API。这个Interface如果用C#中的Delegate实现,是不是会看起来更好呢?(从包的import等等)。不知道有没有C#模仿Eclipse Plugin Framework的实例,呵呵。

Popularity: 18% [?]

Related entries:

  • No Related Posts