转 解析Nutch插件系统

一、 在Nutch的插件体系架构下,有些术语需要解释

   1、扩展点(ExtensionPoint )

      扩展点是系统中可以被再次扩展的类或者接口,通过扩展点的定义,可以使得系统的执行过程变得可插入,可任意变化。 
   2、扩展 ( Extension )
      扩展式插件内部的一个属性,一个扩展是针对某个扩展点的一个实现,每个扩展都可以有自己的额外属性,用于在同一个扩展点实现之间进行区分。扩展必须在插件内部进行定义。
   3、插件 ( Plugin )
      插件实际就是一个虚拟的容器,包含了多个扩展 Extension、依赖插件 RequirePlugins 和自身发布的库Runtime,插件可以被启动或者停止。
    Nutch 为了扩展,预留了很多扩展点 ExtenstionPoint,同时提供了这些扩展点的基本实现 Extension,Plugin
用来组织这些扩展,这些都通过配置文件进行控制,主要的配置文件包括了多个定义扩展点和插件(扩展)的配置文件,一个控制加载哪些插件的配置文件。体系结构图如下:

二、插件的内部结构 ,如下图:


 

   1. runtime 属性描述了其需要的 Jar 包,和发布的 Jar 包
   2. requires 属性描述了依赖的插件
   3. extension-point 描述了本插件宣布可扩展的扩展点
   4. extension 属性则描述了扩展点的实现

三、插件定义方法 如下:

<plugin
   id="urlfilter-suffix"  插件ID
   name="Suffix URL Filter" 插件名称
   version="1.0.0" 插件版本
   provider-name="nutch.org"> 插件提供者的ID

   <runtime>
      <library name="urlfilter-suffix.jar"> 依赖的JAR包
         <export name="*"/> 发布的JAR包
      </library>
   </runtime>

   <requires>
      <import plugin="nutch-extensionpoints"/> 依赖的插件
   </requires>

   <extension id="org.apache.nutch.net.urlfilter.suffix" 扩展的插件ID
              name="Nutch Suffix URL Filter" 扩展的插件名
              point="org.apache.nutch.net.URLFilter"> 插件的扩展点ID
      <implementation id="SuffixURLFilter" 插件实现ID
                      class="org.apache.nutch.urlfilter.suffix.SuffixURLFilter"/> 实现类
      <!-- by default, attribute "file" is undefined, to keep classic behavior.
      <implementation id="SuffixURLFilter"
                      class="org.apache.nutch.net.SuffixURLFilter">
        <parameter name="file" value="urlfilter-suffix.txt"/>
      </implementation>
      -->
   </extension>

</plugin>

四、插件主要配置,在nutch-default.xml里面有:

<!-- plugin properties -->  
  
// 插件所在的目录,缺省位置在 plugins 目录下。  
<property>  
  <name>plugin.folders</name>  
  <value>plugins</value>  
  <description>Directories where nutch plugins are located.  Each  
  element may be a relative or absolute path.  If absolute, it is used  
  as is.  If relative, it is searched for on the classpath.</description>  
</property>  
  
// 当被配置为过滤(即不加载),但是又被其他插件依赖的时候,是否自动启动,缺省为 true。  
<property>  
  <name>plugin.auto-activation</name>  
  <value>true</value>  
  <description>Defines if some plugins that are not activated regarding  
  the plugin.includes and plugin.excludes properties must be automaticaly  
  activated if they are needed by some actived plugins.  
  </description>  
</property>  
  
// 要包含的插件名称列表,支持正则表达式方式定义。  
<property>  
  <name>plugin.includes</name>  
  <value>protocol-http|urlfilter-regex|parse-(text|html|js)|index-(basic|anchor)|query-  
  
(basic|site|url)|response-(json|xml)|summary-basic|scoring-opic|urlnormalizer-(pass|regex|basic)  
  
</value>  
  <description>Regular expression naming plugin directory names to  
  include.  Any plugin not matching this expression is excluded.  
  In any case you need at least include the nutch-extensionpoints plugin. By  
  default Nutch includes crawling just HTML and plain text via HTTP,  
  and basic indexing and search plugins. In order to use HTTPS please enable  
  protocol-httpclient, but be aware of possible intermittent problems with the  
  underlying commons-httpclient library.  
  </description>  
</property>  
  
// 要排除的插件名称列表,支持正则表达式方式定义。  
<property>  
  <name>plugin.excludes</name>  
  <value></value>  
  <description>Regular expression naming plugin directory names to exclude.   
  </description>  
</property>

五、插件主要类 UML 图 :

类包括:
   1. PluginRepository 是一个通过加载 Iconfiguration 配置信息初始化的插件库,里面维护了系统中所有的扩展
点 ExtensionPoint 和所有的插件 Plugin 实例
   2. ExtensionPoint 是一个扩展点,通过扩展点的定义,插件 Plugin 才能定义实际的扩展 Extension,从而实现
扩展,每个 ExtensionPoint 类实例都维护了宣布实现了此扩展点的扩展 Extension.
   3. Plugin 是一个虚拟的组织,提供了一个启动 start 和一个 shutdown 方法,从而实现了插件的启动和停止,
他还有一个描述对象 PluginDescriptor,负责保存此插件相关的配置信息,另外还有一个 PluginClassLoader 负责此插件相关类和库的加载。

六、插件加载过程时序图:

    通过序列图可以发现,Nutch 加载插件的过程需要 actor 全程直接调用每个关联对象,最终得到的是插件的实现对象。详细过程如下:
   1. 首先通过 PluginRepository.getConf() 方法加载配置信息,配置的内容包括插件的目录,插件的配置文件信息 plugin.properties 等,此时 pluginrepository 将根据配置信息加载各个插件的 plugin.xml,同时根据 Plugin.xml 加载插件的依赖类。
   2. 当 actor 需要加载某个扩展点的插件的时候,他可以:
         1. 首先根据扩展点的名称,通过 PluginRepository 得到扩展点的实例,即 ExtensionPoint 类的实例;
         2. 然后调用 ExtensionPoint 对象的 getExtensions 方法,返回的是实现此扩展点的实例列表
(Extension[]);
         3. 对每个实现的扩展实例 Extension,调用它的 getExtensionInstance() 方法,以得到实际的实现类实
例,此处为 Object;

        4. 根据实际情况,将 Object 转型为实际的类对象类型,然后调用它们的实现方法,例如 helloworld 方法。


七、插件的典型调用方式
 :

private Extension findExtension(String pluginName) {  
  
     Extension[] extensions = PluginRepository.get(conf)
        .getExtensionPoint(URLFilter.class.getName()).getExtensions();
    for (int i = 0; i < extensions.length; i++) {
      Extension extension = extensions[i];
      if (extension.getDescriptor().getPluginId().equals(pluginName)) {
        return extension;
      }
    }
 }

八、插件类加载机制 :

    实际整个系统如果使用了插件架构,则插件类的加载是由 PluginClassLoader 类完成的,每个 Plugin 都有自己的 classLoader,此 classloader 继承自 URLClassLoader,并没有做任何事情:

public class PluginClassLoader extends URLClassLoader {

  private URL[] urls;
  private ClassLoader parent;

  /**
   * Construtor
   * 
   * @param urls
   *          Array of urls with own libraries and all exported libraries of
   *          plugins that are required to this plugin
   * @param parent
   */
  public PluginClassLoader(URL[] urls, ClassLoader parent) {
    super(urls, parent);

    this.urls = urls;
    this.parent = parent;
  }

这个 classloader 是属于这个插件的,它只负责加载本插件相关的类、本地库和依赖插件的发布 (exported) 库,也包括一些基本的配置文件例如 .properties 文件。
此类的实例化过程:

/**
   * Returns a cached classloader for a plugin. Until classloader creation all
   * needed libraries are collected. A classloader use as first the plugins own
   * libraries and add then all exported libraries of dependend plugins.
   * 
   * @return PluginClassLoader the classloader for the plugin
   */
  public PluginClassLoader getClassLoader() {
    if (fClassLoader != null)
      return fClassLoader;
    ArrayList<URL> arrayList = new ArrayList<URL>();
    arrayList.addAll(fExportedLibs);
    arrayList.addAll(fNotExportedLibs);
    arrayList.addAll(getDependencyLibs());
    File file = new File(getPluginPath());
    try {
      for (File file2 : file.listFiles()) {
        if (file2.getAbsolutePath().endsWith("properties"))
          arrayList.add(file2.getParentFile().toURI().toURL());
      }
    } catch (MalformedURLException e) {
      LOG.debug(getPluginId() + " " + e.toString());
    }
    URL[] urls = arrayList.toArray(new URL[arrayList.size()]);
    fClassLoader = new PluginClassLoader(urls,
        PluginDescriptor.class.getClassLoader());
    return fClassLoader;
  }

    *  首先判断缓存是否存在
    * 加载需要的 Jar 包、自身需要的 Jar 包,依赖插件发布的 Jar 包
    * 加载本地的 properties 文件
    * 构造此 classloader,父 classloader 为 PluginDescriptor 的加载者,通常是 contextClassLoader

九、总结 :

    Nutch 是一个非常出色的开源搜索框架,它的插件架构更加是它的一个技术亮点,通过此架构,可以保证 Nutch方便的被灵活的扩展而不用修改原来的代码,通过配置文件可以简单方便的控制加载或者不加载哪些插件,而且这些都不需要额外的容器支持。这些都是我们在系统架构设计的时候可以学习和参考的有益经验。

时间: 2024-11-02 01:48:24

转 解析Nutch插件系统的相关文章

转 编写一个最简单的Nutch插件

nutch是高度可扩展的,他使用的插件系统是基于Eclipse2.x的插件系统.在这篇文章中我讲解一下如何编写一个nutch插件,以及在这个过程中我遇到的坑. 请先确保你在eclipse中成功运行了nutch,可以参考在eclipse中运行nutch 我们要实现的插件的功能是接管抓取过程,然后无论抓取什么网址,我们都返回hello world,够简单吧... 插件机制 nutch的插件机制大致是这样:nutch本身暴露了几个扩展点,每个扩展点都是一个接口,我们可以通过实现接口来实现这个扩展点,这

发现 Eclipse 中未解析的插件依赖性

试图定位无法解析的插件依赖性是件紧张而又耗时的事情.激活每个插件都要依赖于很多其他插件,这些插件又会依赖于其他更多插件.如果 Eclipse 无法加载这个长长的链条中的某个插件,那么手工查找出现问题的到底是哪个插件可能会比原计划所花费的时间和精力都要多.如果您希望有一种方法可以自动实现这种插件依赖性的检测,就请阅读本文. 碰到的问题 假设我们希望在 Eclipse 中使用一个特定的插件,并已经执行了所有必须的操作,将其包含到插件的 manifest 文件中,并将其声明为一个依赖文件.但是系统并没

.NET实现之自己动手写高内聚插件系统

今天跟大家分享一下本人在.NET简谈构件系统开发模式一文中提到的软件架构设计思路的具体实现细节. 大家看了我这篇文章后,总问我为什么要起个这么怪异的名字构件而不用插件.其实这个名字在我脑子漂浮了很久,一直找不到合适的场合用它. 在一本书上是这样解释构件的:构件是可以更换的部件,并且这个部件是由一系列很小的部件组成,同样这些小的部件由更小的部件组成:我为什么要区分插件与构件主要原因是这两个名字所表达的思想不同.插件是可插.可卸的过程,没有强调无限极的递归实现子插件的意思,所以本人将其区分开来:当然

.NET实现之(自己动手写高内聚插件系统)

今天跟大家分享一下本人在".NET简谈构件系统开发模式"一文中提到的软件架构设计思路的具体实现细节. 大家看了我这篇文章后,总问我为什么要起个这么怪异的名字"构件"而不用"插件".其实这个名字在我脑子漂浮了很久,一直找不到合适的场合用它. 在一本书上是这样解释构件的:构件是可以更换的部件,并且这个部件是由一系列很小的部件组成,同样这些小的部件由更小的部件组成:我为什么要区分插件与构件主要原因是这两个名字所表达的思想不同.插件是可插.可卸的过程,没

(3)MEF插件系统中通信机制的设计和实现

文章版权由作者李晓晖和博客园共有,若转载请于明显处标明出处:http://www.cnblogs.com/naaoveGIS/ 1.背景 一般的WinForm中通过C#自带的Event机制便能很好的实现事件的注册和分发,但是,在插件系统中却不能这么简单的直接用已有的类来完成.一个插件本不包含另外一个插件,它们均是独立解耦的,实现插件和插件间的通信还需要我们设计出一个事件引擎来完成这个需求. 目前很多高级语言中基本都实现了观察者模式,并进行了自己的包装.比如C#中的delegate和event组合

浅谈C#中一种类插件系统编写的简单方法(插件间、插件宿主间本身不需要通信)

文章版权由作者李晓晖和博客园共有,若转载请于明显处标明出处:http://www.cnblogs.com/naaoveGIS/. 1.背景 三年多前还在上研时,用C#+反射机制写过插件系统,后来又用MEF写过插件系统.插件系统本身具有易于扩展的优势,所以在实际项目中使用很频繁.即使在B/S项目中,插件的思想也是大行其道,比如前端单页面+AMD编程便可以理解为一种插件机制,以及后台扩展项目统一打包为一个jar放入主系统jar文件中一起发布,也可以理解为插件思想的运用. 这里我们回到C/S插件系统编

NET插件系统之一,开头:MEF的一些疑问和相关思考

      实现可扩展的软件系统是我一直的目标和想法.可扩展性显然属于动态编程的范畴.因此,几个月来我在业余时间会抽空学习插件系统.   我参考了博客园的几篇插件式系统的文章.知道了实现插件系统有以下的核心流程:       1. 定义插件接口,并在各个功能组件中实现这些接口 2. 在主程序中,通过遍历所需目录下的dll文件,查询实现该接口的type,从而通过createInstance方法实现其动态创建,并加入到主程序插件列表.下面的代码展示了该功能. public void GetAllPl

MongoDB管理工具的插件系统

MongoDB管理工具  MongoCola的开发已经进入第三个年头了. 官方对于C#驱动的投入不够导致了很多东西都必须自己实现,但是不管怎么样,工具现在已经很强 大了. 最近准备着手插件系统的开发,简单的插件系统,其实代码量非常的少. 1.插件基类 插件系统需要一个插件基类的支持,这个基类,规定了一个插件所包含的固有字段,例如插件名称 ,插件说明,插件作者等等. 同时,还定义了主方法的名称,毕竟插件系统肯定要使用反射来完成,所以很多东西必须要统一起 来. using System; names

插件系统中跨域调用的性能和“一个简单的性能计数器”的使用

系统大概的流程如下:从数据中心接收到数据包(1万~3万(个)/秒,使用WCF)可以被不同的应用场景使 用,每个应用场景的业务逻辑各不相同,而每个场景的接收数据包部分的代码是相同的,于是使用一个容 器将所有的"应用场景的dll"通过反射加载,同时容器接收所有的数据包并把他们分发给"应用场景的dll" ,接收数据的功能,自然被抽象成了Interface.透彻一点讲就是一个插件系统,只不过这个插件有点儿 大而已,大到整个系统都是个插件.这个容器我们暂时叫他"引擎