- 浏览: 30070 次
- 来自: ...
文章分类
最新评论
在讨论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文件来进行描述,比如:
- <?xml version="1.0" encoding="utf-8"?>
- <plugin id="org.eclipse.pde.source" name="%pluginName" version="2.1.3" provider-name="%providerName">
- <runtime></runtime>
- <extension point="org.eclipse.pde.core.source">
- <location path="src"> </location>
- </extension>
- </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插件的一个片断):
- <extension-point id="Parser">
- <parameter-def id="class" type="string"/>
- <parameter-def id="icon" type="string"/>
- </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的体系结构是
最后来看一下代码:
- protected synchronized Class<?> loadClass(String name, boolean resolve)
- throws ClassNotFoundException
- {
- // First, check if the class has already been loaded
- Class c = findLoadedClass(name);
- if (c == null) {
- try {
- if (parent != null) {
- c = parent.loadClass(name, false);
- } else {
- c = findBootstrapClass0(name);
- }
- } catch (ClassNotFoundException e) {
- // If still not found, then invoke findClass in order
- // to find the class.
- c = findClass(name);
- }
- }
- if (resolve) {
- resolveClass(c);
- }
- return c;
- }
可见,ClassLoader首先会查找该类是否已经被装载,如果没有,就询问自己的父ClassLoader,如果还不能装载,就调用findClass()方法来装载类。所以,一般简单的自定义ClassLoader只需要重写findClass方法就可以了。
如果你的类不是文件,比如说是序列化在数据库中的二进制流或者网络上的Bit流,就需要重写defineClass()方法,来将二进制数据映射到运行时的数据结构。另外一种需求也可能是你需要对类文件进行某种操作(比如按位取反?),也需要定义自己的defineClass()方法。
还需要注意的是资源的加载和系统Native库的加载,这个可以留在以后再作讨论。
4) Plugin与PluginClassLoader
准备工作做完,就可以来看看具体实现过程。
我们模拟的几个重要的类是:
Plugin: 插件类,描述每个具体插件;
PluginDescriptor: 插件描述符,记录了插件的ID、Name、Version、依赖、扩展点等;
PluginManager: 插件管理器,负责所有插件资源的管理,包括插件的启动、停止、使能(Enable/Disable)等等;
PluginRegistry: 插件注册表,提供了一个由插件ID到Plugin的映射;
我们首先来定义一个简单的Plugin:
- public abstract class Plugin {
- /**
- * Plugin State
- */
- private boolean started_;
- private final PluginManager manager_;
- private final IPluginDescriptor descriptor_;
- public Plugin(PluginManager manager, IPluginDescriptor descr) {
- manager_ = manager;
- descriptor_ = descr;
- }
- /**
- * @return descriptor of this plug-in
- */
- public final IPluginDescriptor getDescriptor() {
- return descriptor_;
- }
- /**
- * @return manager which controls this plug-in
- */
- public final PluginManager getManager() {
- return manager_;
- }
- final void start() throws PluginException {
- if (!started_) {
- doStart();
- started_ = true;
- }
- }
- final void stop() throws PluginException {
- if (started_) {
- doStop();
- started_ = false;
- }
- }
- public final boolean isActive() {
- return started_;
- }
- /**
- * Get the resource string
- * @param key
- * @return
- */
- public String getResourceString(String key) {
- IPluginDescriptor desc = getDescriptor();
- return desc.getResourceString(key);
- }
- /**
- * Get the Plugin Path
- *
- * @return
- */
- public String getPluginPath() {
- return getDescriptor().getPluginHome();
- }
- /**
- * Template method, which will do the really start work
- *
- * @throws Exception
- */
- protected abstract void doStart() throws PluginException;
- /**
- * Template method, which will do the really stop work
- *
- * @throws Exception
- */
- protected abstract void doStop() throws PluginException;
- }
可见,这只是一个抽象类,每个插件需要定义自己的派生自"Plugin"的子类,作为本插件的一个入口。其中doStart和doStop是两个简单的模板方法,每个插件的初始化和资源释放操作可以定义在这里。
接下来我们看看系统的启动流程:首先将所有的插件清单读入("plugin.xml"),并根据这个文件解析出PluginDescriptor(包括这个Plugin的所有导出库、依赖插件、扩展点等等),放到PluginRegistry中。这个过程也是整个插件平台的一个非常重要的部分,需要从插件清单中解析的部分包括:
- 每个插件所依赖的的插件列表(在"plugin.xml"中用"require" element标识);
- 每个插件要输出的资源和类(在"plugin.xml"中用"library" element标识);
- 每个插件所声明的扩展点列表;
- 每个插件所声明的扩展列表(扩展其它扩展点的扩展)。
当把所有的插件信息都读入到系统中,就可以根据自己的需要来启动指定的插件了(比如,在Xerdoc DS中,首先,我们会启动Core插件)。
启动一个插件的步骤是:
- public Plugin getPlugin(String id) throws PluginException {
- ... ...
- IPluginDescriptor descr = pluginRegistry_.getPluginDescriptor(id);
- if (descr == null) {
- throw new PluginException("Cannot found this plugin " + id);
- }
- result = activatePlugin(descr);
- return result;
- }
- private synchronized Plugin activatePlugin(IPluginDescriptor descr)
- throws PluginException {
- ... ...
- try {
- try {
- // 首先需要检查这个插件所依赖的插件是否都已经启动,
- // 如果没有,则需要先启动那些插件,才能启动本插件
- checkPrerequisites(descr);
- } catch (PluginException e) {
- badPlugins_.add(descr.getId());
- throw e;
- }
- // 得到插件的主类名
- // 这个信息也是定义在"Plugin.xml"中,
- // 并且在加载插件信息的时候读入到PluginDescriptor中的
- String className = descr.getPluginClassName();
- if ((className == null) || "".equals(className.trim())) {
- result = null;
- } else {
- Class pluginClass;
- try {
- // 用每个插件自己的PluginClassLoader来得到这个插件的主类
- pluginClass = descr.getPluginClassLoader().loadClass(
- className);
- } catch (ClassNotFoundException cnfe) {
- badPlugins_.add(descr.getId());
- throw new PluginException("can't find plug-in class "
- + className);
- }
- try {
- Class pluginManagerClass = getClass();
- Class pluginDescriptorClass = IPluginDescriptor.class;
- Constructor constructor = pluginClass
- .getConstructor(new Class[] { pluginManagerClass,
- pluginDescriptorClass });
- // 调用插件默认的构造函数
- // Plugin(PluginManager, IPluginDescriptor);
- result = (Plugin) constructor.newInstance(new Object[] {
- this, descr });
- } catch (InvocationTargetException ite) {
- ... ...
- } catch (Exception e) {
- ... ...
- }
- try {
- result.start();
- } catch (Exception e) {
- ... ...
- }
- ... ...
- }
- }
- return result;
- }
其实最核心的工作就是三步:
- 首先检查这个插件所依赖的其它插件是否已经被启动,如果没有,则需要首先将那些插件启动;
- 根据类名,用插件类加载器加载这个类(这个类是Plugin类的一个派生类);
- 调用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类加载器的搜索路径中去。
比如:
- <runtime>
- <library id="com.xerdoc.desktop.view.htmlrender" path="XerdocDSHTMLRender.jar" type="code">
- <export prefix="*"/>
- </library>
- <library id="resources" path="image/" type="resources">
- <export prefix="*"/>
- </library>
- </runtime>
PluginClassLoader会将"XerdocDSHTMLRender.jar"和"image/"目录都加入到URLClassLoader的类搜索路径中去,这样,就可以用这个类加载器来加载相应的插件类和资源了。
PluginClassLoader加载插件的策略是:
首先试图从父ClassLoader加载(系统类加载器),如果无法加载则会试图从本类加载器加载,如果还是找不到,这时的行为与一般的URLClassLoader不同,也PluginClassLoader最大的特色:它会试图从此插件的需求依赖插件("require"形容的插件)中去加载需求的类或者资源。
比如下面这个例子:
- <requires>
- <import plugin-id="com.xerdoc.desktop.core" plugin-version="0.4.0" match="compatible"/>
- <import </
相关推荐
《Dissect框架——开源数据处理的强大工具》 在IT领域,数据处理是不可或缺的一部分,而开源软件更是推动技术进步的重要力量。今天我们将深入探讨一款名为Dissect的开源框架,它为数据处理提供了一个灵活且可扩展的...
dissect, 在纯PHP中,一组用于词汇和句法分析的工具 欢迎剖析 !主 - 这里分支始终包含最后一个稳定版本。开发 - 不稳定的开发分支。剖析是一组用纯PHP编写的词汇和句法分析工具。文档?这里是 。
"Dissect"是一个用于分析和理解SCSS(Sass预处理器)代码的工具。SCSS是Sass语言的一种语法格式,它扩展了CSS,引入了变量、嵌套规则、混合、函数等特性,使得CSS编写更加模块化和可维护。Dissect的目标是帮助开发者...
**Python库Flask-Dissect 1.0.9详解** `Flask-Dissect` 是一个基于Python的轻量级Web开发框架`Flask`的扩展插件,它的主要功能是提供对Flask应用程序的深入分析和调试工具。这个库的版本1.0.9是为了帮助开发者更好...
描述Dissect 过滤器是 Grok 过滤器的替代品,可用于从非结构化线中提取结构化字段。 但是,如果您的文本结构因行而异,那么 Grok 更合适。 有一种混合情况,其中 Dissect 可用于解构可靠重复的线段,然后 Grok 可...
这个包代表了基于离散事件的云和联邦能耗模拟器(DISSECT-CF)。 它旨在作为一个轻量级的云模拟器,可以轻松进行定制和实验。 它专为研究目的而设计,因此研究人员可以试验各种内部云行为变化,而无需实际拥有一个...
dissect-dctracking-v1.0 适用于CVPR 2012论文的DCO原始版本 分解步骤 (1)附加套餐 GCO dctracking-v1.0/gco-v3.0/matlab文件夹中缺少一堆GCO _ *。m文件,这对于桥接Matlab和GCO包装器至关重要 dctracking-v1.0中...
git-dissect:分布式biSECT git-dissect是git bisect的替代方法,它允许在多个主机上运行测试以更快地进行bisect。 它是受罗伯·霍尔茨(Rob Hoelz)的。安装 $ git clone ...
剖析使用Python进行结构解析变得容易。 使用cstruct,您可以编写类似于C的结构,并使用它们来解析二进制数据(作为类似于文件的对象或字节串)。 用cstruct解析二进制数据让人感到熟悉和容易。 在开始解析数据之前,...
You’ll learn how to dissect algorithms at a granular level, using various tests, and discover a framework for testing machine learning code. The author provides real-world examples to demonstrate the...
Scan for flaws in Web applications using Fiddler and the x5 plugin Learn the use-after-free technique used in recent zero days Bypass Web authentication via MySQL type conversion and MD5 injection ...
例如,您的光驱代码为 E:,您希望安装到 C:\Dissect 目录中。可以在 DOS BOX 内做以下动作: > C: > CD \ > MD Dissect > CD Dissect > xcopy E:\Dissect\*.* . 【本书程序范例】 GENERIC 01 JBACKUP 01 ...
例如,您的光驱代码为 E:,您希望安装到 C:\Dissect 目录中。可以在 DOS BOX 内做以下动作: > C: > CD \ > MD Dissect > CD Dissect > xcopy E:\Dissect\*.* . 【本书程序范例】 GENERIC 01 JBACKUP 01 ...
Below, we will dissect these elements to understand their significance and implications for security. #### Registry Keys and Device Paths Explained 1. **IBMPMSVC Parameters (Type2 Notifications):**...
可执行文件的剖析可执行文件,共享库和可重定位目标代码的表示通过多种文件格式进行了标准化,这些文件格式提供了汇编指令和数据的封装。 两种这样的格式分别是Windows和Linux分别使用的可移植可执行(PE)文件格式...
使用dissect-wireshark的方法是将ndn.lua脚本放置在指定目录,如/usr/local/share/ndn-dissect-wireshark。你可以通过Wireshark的命令行选项 `-X lua_script` 指定脚本路径,或者修改Wireshark的init.lua文件来永久...
RTSP(Real-Time Streaming Protocol)是一种应用层协议,主要用于控制实时流媒体的传输。它允许客户端与服务器之间协商媒体的播放、暂停、快进、快退等操作。本项目是用C++实现的一个RTSP客户端,非常适合初学者...
神经元的作用是什么? 当深度网络接受高级任务(例如分类地点或合成场景)训练时,网络中的单个神经单元通常会出现,它们与特定的人类可理解概念相匹配,例如“树”,“窗户”或“人脸。” 这样的个人部门在深层...
structure dissect shows the pointerpath at the bottom Follow register while stepping (rightclick the register to show the option) registersymbol and label now support multiple definitions in one line ...
"DISSECT"一词可能来源于英文"解剖",暗示着这些代码是用于解析和理解MFC的各个组成部分。用户可以通过逐个查看和运行这些子文件,逐步剖析MFC的内部运作。 在深入学习MFC时,首先,可以从"DISSECT"文件夹中的入门...