天天看點

Nutch 1.3 學習筆記 10-3 插件機制分析

分類: Nutch 2011-09-18 00:19  437人閱讀  評論(0)  收藏  舉報 Nutch 1.3 學習筆記 10-3 插件機制分析

-------------------------------------

1. 一些對象說明 

  1. PluginRepository:這是一個用于存儲所有插件描述對象(PluginDescriptor),插件擴充點(ExtensionPoint)和被激活的插件。
  2. PluginDescriptor:用于描述單個擴充插件的元資訊,它的内容主要是從plugin.xml中得到。
  3. Plugin: 用于描述插件的一個抽象,其中包括了一個插件描述符,它們是一對一的關系。
  4. ExtensionPoint: 這個擴充點主要是一個面象對象中的接口的意思,就是說可以有多個擴充來實作這個接口,一個或者多個擴充點實際上就是一個插件,如nutch-extensionpoints.
  5. Extension: 擴充是對于擴充點的實作,一個插件可以包含多個擴充。
  6. PluginManifestParser: 主要是用于解析插件目錄下的plugin.xml檔案,生成相應的PluginDescriptor對象。
  7. PluginClassLoader: 它繼承自URLClassLoader,用來根據urls動态生成相應的插件實作對象

2. 插件倉庫初始化流程

PluginRepository的生成有兩種方法,一個是直接new一個相應的對象,另一個是調用PluginRepository的靜态的get方法,從Cache中得到相應的PluginRepository,在Nutch的流程中,一般是通用使用第二種方法來得到PluginRepository,這樣可以保證資源在多個流程中得到共享。

Nutch 1.3 學習筆記 10-3 插件機制分析

2.1 PluginRepostory的初始化在其ctr函數中進行

源代碼如下:

[html]  view plain copy

  1. fActivatedPlugins = new HashMap<String, Plugin>();  
  2.         fExtensionPoints = new HashMap<String, ExtensionPoint>();  
  3.         this.conf = conf;  
  4.         // 當被配置為過濾(即不加載),但是又被其他插件依賴的時候,是否自動啟動,預設為 true  
  5.         this.auto = conf.getBoolean("plugin.auto-activation", true);  
  6.         // 插件的目錄名,可以是多個目錄  
  7.         String[] pluginFolders = conf.getStrings("plugin.folders");  
  8.         PluginManifestParser manifestParser = new PluginManifestParser(conf, this);  
  9.         Map<String, PluginDescriptor> allPlugins = manifestParser.parsePluginFolder(pluginFolders);  
  10.         // 要排除的插件名稱清單,支援正規表達式方式定義  
  11.         Pattern excludes = Pattern.compile(conf.get("plugin.excludes", ""));  
  12.         // 要包含的插件名稱清單,支援正規表達式方式定義  
  13.         Pattern includes = Pattern.compile(conf.get("plugin.includes", ""));  
  14.         // 對不使用的插件進行過濾,傳回過濾後的插件  
  15.         Map<String, PluginDescriptor> filteredPlugins = filter(excludes, includes,allPlugins);  
  16.         // 對插件的依賴關系進行檢查  
  17.         fRegisteredPlugins = getDependencyCheckedPlugins(filteredPlugins,this.auto ? allPlugins : filteredPlugins);  
  18.         // 安裝擴充點,主要是針對nutch-extensionpoints這個插件的  
  19.         installExtensionPoints(fRegisteredPlugins);  
  20.         try {  
  21.             // 安裝特定擴充點的相應擴充集  
  22.             // NOTE:其實這邊的擴充點與擴充都是以插件的形式表現的  
  23.             installExtensions(fRegisteredPlugins);  
  24.         } catch (PluginRuntimeException e) {  
  25.             LOG.fatal(e.toString());  
  26.             throw new RuntimeException(e.getMessage());  
  27.         }  
  28.         displayStatus();  

   2.2 下面分析一個插件描述符的生成

插件描述符的生成主要是通用調用PluginManifestParser這個對象的parsePluginFolder這個方法生成的,源代碼如下:

[html]  view plain copy

  1.             public Map<String, PluginDescriptor> parsePluginFolder(String[] pluginFolders) {  
  2.         Map<String, PluginDescriptor> map = new HashMap<String, PluginDescriptor>();  
  3.         if (pluginFolders == null) {  
  4.                 throw new IllegalArgumentException("plugin.folders is not defined");  
  5.         }  
  6.         for (String name : pluginFolders) {  
  7.             // 周遊所有插件目錄,這裡的getPluginFolder方法解析一個資源的相對路徑的問題  
  8.                 File directory = getPluginFolder(name);  
  9.                 if (directory == null) {  
  10.                 continue;  
  11.                 }  
  12.             LOG.info("Plugins: looking in: " + directory.getAbsolutePath());  
  13.         // 周遊所有子插件目錄中的插件  
  14.             for (File oneSubFolder : directory.listFiles()) {  
  15.             if (oneSubFolder.isDirectory()) {  
  16.                     String manifestPath = oneSubFolder.getAbsolutePath() + File.separator  
  17.                     + "plugin.xml";  
  18.                 try {  
  19.             LOG.debug("parsing: " + manifestPath);  
  20.             // 分析plugin.xml檔案  
  21.             PluginDescriptor p = parseManifestFile(manifestPath);  
  22.             map.put(p.getPluginId(), p);  
  23.                 } catch (MalformedURLException e) {  
  24.             LOG.warn(e.toString());  
  25.             } catch (SAXException e) {  
  26.            LOG.warn(e.toString());  
  27.             } catch (IOException e) {  
  28.            LOG.warn(e.toString());  
  29.             } catch (ParserConfigurationException e) {  
  30.            LOG.warn(e.toString());  
  31.             }  
  32.         }  
  33.         }  
  34.     }  
  35.     return map;  
  36.     }  

[html]  view plain copy

  1. private PluginDescriptor parseManifestFile(String pManifestPath)  
  2.         throws MalformedURLException, SAXException, IOException,  
  3.         ParserConfigurationException {  
  4.     // 解析xml檔案,生成Document對象  
  5.         Document document = parseXML(new File(pManifestPath).toURL());  
  6.         String pPath = new File(pManifestPath).getParent();  
  7. // 對xml進行分析  
  8.         return parsePlugin(document, pPath);  
  9.     }  

[html]  view plain copy

  1. private PluginDescriptor parsePlugin(Document pDocument, String pPath)  
  2.             throws MalformedURLException {  
  3.    Element rootElement = pDocument.getDocumentElement();  
  4. // 這裡是解析xml中的如下資訊  
  5. // <plugin id="index-anchor" name="Anchor Indexing Filter" version="1.0.0" provider-name="nutch.org">  
  6.    String id = rootElement.getAttribute(ATTR_ID);  
  7.    String name = rootElement.getAttribute(ATTR_NAME);  
  8.    String version = rootElement.getAttribute("version");  
  9.    String providerName = rootElement.getAttribute("provider-name");  
  10. // 插件類屬性,不過這裡好像沒有用到過  
  11.    String pluginClazz = null;  
  12.    if (rootElement.getAttribute(ATTR_CLASS).trim().length() > 0) {  
  13.      pluginClazz = rootElement.getAttribute(ATTR_CLASS);  
  14.    }  
  15. // 生成插件描述符對象  
  16.    PluginDescriptor pluginDescriptor = new PluginDescriptor(id, version, name,  
  17.        providerName, pluginClazz, pPath, this.conf);  
  18.    LOG.debug("plugin: id=" + id + " name=" + name + " version=" + version  
  19.          + " provider=" + providerName + "class=" + pluginClazz);  
  20. // 這裡是解析如下内容  
  21. //  <extension id="org.apache.nutch.indexer.anchor" name="Nutch Anchor Indexing Filter" point="org.apache.nutch.indexer.IndexingFilter">  
  22.    //   <implementation id="AnchorIndexingFilter"  
  23.    //       class="org.apache.nutch.indexer.anchor.AnchorIndexingFilter" />  
  24.     //  </extension>  
  25.    parseExtension(rootElement, pluginDescriptor);  
  26. // 這裡主要是解析nutch-extensionPoints這個插件,xml内容如下  
  27. // <extension-point id="org.apache.nutch.indexer.IndexingFilter" name="Nutch Indexing Filter"/>  
  28.    // <extension-point id="org.apache.nutch.parse.Parser" name="Nutch Content Parser"/>  
  29.    // <extension-point id="org.apache.nutch.parse.HtmlParseFilter" name="HTML Parse Filter"/>  
  30.    parseExtensionPoints(rootElement, pluginDescriptor);  
  31. // 這裡主要是解析插件的動态庫與插件所使用的第三方庫,xml内容如下  
  32. // <runtime>  
  33.    //  <library name="parse-tika.jar">  
  34.    //     <export name="*"/>  
  35.    //  </library>  
  36.    //  <library name="apache-mime4j-0.6.jar"/>  
  37.    //  <library name="asm-3.1.jar"/>  
  38.    //  <library name="bcmail-jdk15-1.45.jar"/>  
  39.    //  <library name="bcprov-jdk15-1.45.jar"/>  
  40. //  </runtime>  
  41.    parseLibraries(rootElement, pluginDescriptor);  
  42. // 這裡解析插件依賴的插件庫,xml内容如下  
  43. //  <requires>  
  44.    //   <import plugin="nutch-extensionpoints"/>  
  45.    //  <import plugin="lib-regex-filter"/>  
  46.    //  </requires>  
  47.    parseRequires(rootElement, pluginDescriptor);  
  48.    return pluginDescriptor;  
  49.  }  

   要注意的是這個PluginManifestParser就是用來解析相應的plugin.xml檔案,生成PluginRepository對象的,這個有一個很奇怪的概念就是一個插件描述符(PluginDescriptor)可以包含多個可擴充點或者可擴充點的實作,這裡為什麼不把可擴充點分離出來,PluginDescriptor就隻包含一個或者多個可擴充點的實作。而可擴充點就是插件的接口定義。

2.3 插件依賴關系的檢查

這個依賴關系的檢查很有趣,主要是根據plugin.auto-activation這個參數來定的,部分源代碼如下:

[html]  view plain copy

  1.  private List<PluginDescriptor> getDependencyCheckedPlugins(  
  2.      Map<String, PluginDescriptor> filtered, Map<String, PluginDescriptor> all) {  
  3.    if (filtered == null) {  
  4.      return null;  
  5.    }  
  6.    Map<String, PluginDescriptor> checked = new HashMap<String, PluginDescriptor>();  
  7. // 周遊所有過濾後的插件  
  8.    for (PluginDescriptor plugin : filtered.values()) {  
  9.      try {  
  10.       // 儲存目前插件的依賴插件描述符  
  11.        checked.putAll(getPluginCheckedDependencies(plugin, all));  
  12.     // 儲存目前插件描述符  
  13.        checked.put(plugin.getPluginId(), plugin);  
  14.      } catch (MissingDependencyException mde) {  
  15.        // Log exception and ignore plugin  
  16.        LOG.warn(mde.getMessage());  
  17.      } catch (CircularDependencyException cde) {  
  18.   // Simply ignore this plugin  
  19.        LOG.warn(cde.getMessage());  
  20.      }  
  21.    }  
  22.    return new ArrayList<PluginDescriptor>(checked.values());  
  23.  }  

3. 插件調用流程

  插件調用流程主要分成如下幾步:

  1. 根據擴充點ID号從插件倉庫中得到相應的擴充點對象
  2. 根據擴充點對象得到相應的擴充集
  3. 周遊擴充集,從擴充對象中執行個體化出相應的擴充來,執行個體化的過濾就是調用PluginClassLoader

  下面是生成URLFilter插件的部分代碼:

[html]  view plain copy

  1.    (1)  
  2. ExtensionPoint point = PluginRepository.get(conf).getExtensionPoint(URLFilter.X_POINT_ID);  
  3. if (point == null)  
  4.   throw new RuntimeException(URLFilter.X_POINT_ID + " not found.");  
  5.    (2)  
  6. Extension[] extensions = point.getExtensions();  
  7. Map<String, URLFilter> filterMap = new HashMap<String, URLFilter>();  
  8. for (int i = 0; i < extensions.length; i++) {  
  9.   Extension extension = extensions[i];  
  10.    (3)  
  11.   URLFilter filter = (URLFilter) extension.getExtensionInstance();  
  12.   if (!filterMap.containsKey(filter.getClass().getName())) {  
  13.     filterMap.put(filter.getClass().getName(), filter);  
  14.   }  
  15. }  

4. 總結

Nutch的插件機制還是比較經典的,上面隻是做了一個簡單的分析,要深入了解還是要多實踐。