<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    精彩的人生

    好好工作,好好生活

    BlogJava 首頁 新隨筆 聯系 聚合 管理
      147 Posts :: 0 Stories :: 250 Comments :: 0 Trackbacks

    在討論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)還沒出來 :P

    1) 插件清單

    Eclipse中的插件都用XML文件來進行描述,比如:

    1. <?xml version="1.0" encoding="utf-8"?>
    2. <plugin id="org.eclipse.pde.source" name="%pluginName" version="2.1.3" provider-name="%providerName"> 
    3.     <runtime></runtime>
    4.     <extension point="org.eclipse.pde.core.source">
    5.         <location path="src"> </location>
    6.     </extension> 
    7. </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"。

    WordPressPlugin Framework也同樣采用這種"Extension Point"的概念構架,它為自己幾乎所有的應用都定義了擴展點。比如,有的插件可以在"Header顯示擴展點"的地方加入代碼來添加CSS樣式表,Google Sitemap插件可以在"文章發布擴展點"的地方進行Google Sitemap的提交,Creative Commons插件可以在"Footer顯示擴展點"處增加"Creative Common"信息等等。

    對于Eclipse來說,因為采用微內核+插件的方式,因此,定義擴展點也就成了你的任務,在擴展功能的同時,你也可以在任何你覺得可能被擴展的地方定義擴展點,來方便其他人擴展系統的功能。

    舉個例子,Eclipse SWT UI中,工具欄、視圖都留有擴展點,這樣可以方便的進行擴展。

    Eclipse的插件擴展點都定義在"plugin.xml"文件中,每個插件要擴展哪些擴展點也定義在這個文件中。舉個例子(DS中Core插件的一個片斷):

    1. <extension-point id="Parser"> 
    2.     <parameter-def id="class" type="string"/> 
    3.     <parameter-def id="icon" type="string"/>
    4. </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的體系結構是

    ClassLoader Architect

    最后來看一下代碼:

    1. protected synchronized Class<?> loadClass(String name, boolean resolve)
    2.     throws ClassNotFoundException
    3.     {
    4.     // First, check if the class has already been loaded
    5.     Class c = findLoadedClass(name);
    6.     if (c == null) {
    7.         try {
    8.         if (parent != null) {
    9.             c = parent.loadClass(name, false);
    10.         } else {
    11.             c = findBootstrapClass0(name);
    12.         }
    13.         } catch (ClassNotFoundException e) {
    14.             // If still not found, then invoke findClass in order
    15.             // to find the class.
    16.             c = findClass(name);
    17.         }
    18.     }
    19.     if (resolve) {
    20.         resolveClass(c);
    21.     }
    22.     return c;
    23. }

    可見,ClassLoader首先會查找該類是否已經被裝載,如果沒有,就詢問自己的父ClassLoader,如果還不能裝載,就調用findClass()方法來裝載類。所以,一般簡單的自定義ClassLoader只需要重寫findClass方法就可以了。

    如果你的類不是文件,比如說是序列化在數據庫中的二進制流或者網絡上的Bit流,就需要重寫defineClass()方法,來將二進制數據映射到運行時的數據結構。另外一種需求也可能是你需要對類文件進行某種操作(比如按位取反?),也需要定義自己的defineClass()方法。

    還需要注意的是資源的加載和系統Native庫的加載,這個可以留在以后再作討論。

    4) Plugin與PluginClassLoader

    準備工作做完,就可以來看看具體實現過程。

    我們模擬的幾個重要的類是:

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

    可見,這只是一個抽象類,每個插件需要定義自己的派生自"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.     IPluginDescriptor descr = pluginRegistry_.getPluginDescriptor(id);
    4.     if (descr == null) {
    5.         throw new PluginException("Cannot found this plugin " + id);
    6.     }
    7.     result = activatePlugin(descr);
    8.     return result;
    9. }
    10. private synchronized Plugin activatePlugin(IPluginDescriptor descr)
    11.         throws PluginException {
    12.     ... ...
    13.    
    14.     try {
    15.         try {
    16.             // 首先需要檢查這個插件所依賴的插件是否都已經啟動,
    17.             // 如果沒有,則需要先啟動那些插件,才能啟動本插件
    18.             checkPrerequisites(descr);
    19.         } catch (PluginException e) {
    20.             badPlugins_.add(descr.getId());
    21.             throw e;
    22.         }
    23.         //    得到插件的主類名
    24.         //    這個信息也是定義在"Plugin.xml"中,
    25.         //    并且在加載插件信息的時候讀入到PluginDescriptor中的
    26.        
    27.         String className = descr.getPluginClassName();
    28.         if ((className == null) || "".equals(className.trim())) {
    29.             result = null;
    30.         } else {
    31.             Class pluginClass;
    32.             try {
    33.            
    34.                 //    用每個插件自己的PluginClassLoader來得到這個插件的主類
    35.                
    36.                 pluginClass = descr.getPluginClassLoader().loadClass(
    37.                         className);
    38.             } catch (ClassNotFoundException cnfe) {
    39.                 badPlugins_.add(descr.getId());
    40.                 throw new PluginException("can't find plug-in class "
    41.                         + className);
    42.             }
    43.             try {
    44.                 Class pluginManagerClass = getClass();
    45.                 Class pluginDescriptorClass = IPluginDescriptor.class;
    46.                 Constructor constructor = pluginClass
    47.                         .getConstructor(new Class[] { pluginManagerClass,
    48.                                 pluginDescriptorClass });
    49.                 //    調用插件默認的構造函數
    50.                 //    Plugin(PluginManager, IPluginDescriptor);
    51.                
    52.                 result = (Plugin) constructor.newInstance(new Object[] {
    53.                         this, descr });
    54.             } catch (InvocationTargetException ite) {
    55.                 ... ...
    56.             } catch (Exception e) {
    57.                 ... ...
    58.             }
    59.             try {
    60.                 result.start();
    61.             } catch (Exception e) {
    62.                 ... ...
    63.             }
    64.             ... ...
    65.         }
    66.     }
    67.     return result;
    68. }

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

    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"中聲明的目錄,就都會加入到類加載器的類搜索路徑中去,這樣,我們都可以直接訪問里面的資源。

    5) I18N

    I18N(Internationalization,見后注)也是插件平臺的一個重要組成部分,國際化軟件很重要的一個部分就是I18N的支持。這其實也是"An architecture of participation"的一個方面,只要你留出良好的接口,別人會替你進行各種語言的Translation工作。

    Java對于I18N有非常好的支持,可以看看Sun的Online Tutorial,其實最重要的概念就算是Locale和ResourceBundle了。

    先來看看Locale:

    A Locale object represents a specific geographical, political, or cultural region

    常見的比如"zh_CN", "en_US"等等 。它是由Language(語言)和Country(國家/地區)兩部分組成的,比如"en"表示英語,"US"表示美國,通常,我們可以通過Locale.getDefault()來得到本地默認的Locale。

    當你將Locale設置為其它,理論上,系統的語言、習慣等等也應該被相應的切換。

    再來看看ResourceBundle:

    Resource bundles contain locale-specific objects. When your program needs a locale-specific resource, a String for example, your program can load it from the resource bundle that is appropriate for the current user’s locale. In this way, you can write program code that is largely independent of the user’s locale isolating most, if not all, of the locale-specific information in resource bundles.

    ResourceBundle實例化的策略為:

    1) 名字

    簡單的說,首先會根據你設定的Locale和Base Name來取得相應的名字,比如默認的來說,我們的中文系統中,Base Name = "plugin"的情況下,搜索的順序為:

    plugin_zh_CN

    plugin_zh

    plugin

    所以,Xerdoc DS默認都會提供中、英兩種Resource文件("plugin_zh_CN.properties", "plugin.properties"),當然,也可以很方便的根據"plugin.properties"翻譯成為其它語言(比如法語、德語)。

    這樣的結果是,如果你設置為中文Locale,那么則讀取"plugin_zh_CN.properties",如果設置為英文,則讀取"plugin.properties",如果設置為德文等其它找不到的,也默認的使用英文的"plugin.properties"。

    2)類 or Properties文件 ?

    ResourceBundle首先會試圖根據上面的名字加載ResourceBundle的子類,如果加載失敗,再試圖加載以這個名字為文件名,以"propereies"為后綴的資源文件,如果還是找不到,就會拋出Exception。

    根據這樣兩步,ResourceBundle完成自己的實例化。

    這樣,可以為每個插件都配一個自己的ResourceBundle,負責自己插件的I18N工作。

    實現起來,在PluginClassLoader中,可以將每個插件的"i18n"目錄都默認的加入到搜索路徑中去,這樣,可以通過下面這段代碼得到每個插件自己的ResourceBundle:

    1. ...
    2. private static final String RESOURCE_BUNDLE_NAME = "plugin";
    3. ...
    4. /**
    5. * I18N Work
    6. */
    7. /**
    8. * Returns the plugin's resource bundle,
    9. */
    10. public ResourceBundle getResourceBundle() {
    11.     try {
    12.         if (resourceBundle_ == null) {
    13.             ClassLoader loader = getPluginClassLoader();
    14.             resourceBundle_ = ResourceBundle.getBundle(
    15.                     RESOURCE_BUNDLE_NAME, Locale.getDefault(), loader);
    16.         }
    17.     } catch (MissingResourceException x) {
    18.         resourceBundle_ = null;
    19.     }
    20.     return resourceBundle_;
    21. }

    得到ResourceBundle后,就可以通過它來得到所需要的字符串什么的了:

    1. resourceBundle_.getString("parser.word.title");

    有兩個注意的是:

    1. 編譯的時候,編碼應該指定為utf-8;
    2. 程序中所有的字符串應該都由ResourceBundle得到,不應該出現硬編碼。

    寫到這里,想起原來用Visual C++開發項目。I18N的道理其實差不多,不過是將字符串都寫到RC文件中(二進制)。相比起來,VC隊I18N的支持比Java還是要差不少。

    最后補充一點的是,Eclipse插件清單("plugin.xml")中需要I18N的字符串通常在前面添加"%",這樣在取得這樣的字符串后,ResourceBundle可以根據這個Key從properties中取出相應的I18N后的字符串,并表示出來。

    注:I18N作為Internationalization的簡稱,表示中間省略18個字母,常用的還有G10N(Globalization), A11Y(Accessbility)等等。

    6) Lazy Loading

    在介紹"Extension Point"之前,先來看一個概念:Eclipse中著名的懶加載原則(Lazy Loading Rule)。

    懶加載法則:只有在真正需要的時候才加載插件,實現起來最重要的方面就是聲明和實現的分離。

    插件的外形(比如名字,ID,圖標)等等都在插件描述清單"plugin.xml"中聲明,而具體功能封裝在class文件中。

    這種懶加載原則表現在各個方面,比如最基本的插件啟動。系統在啟動的時候,只加載和啟動最必須的一些插件,而其它插件只有在真正用到的時候才被加載和啟動,這樣可以最大限度的節省系統啟動時的資源和時間。而對用戶來說,每次啟動也確實有很多插件根本不會去用到。

    懶加載還表現在擴展點的應用上,待會兒可以看到具體例子。

    7) 擴展點的實現

    接下來就看看"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.         //    得到Parser擴展點聲明
    8.         IExtensionPoint extPoint = descriptor.getRegistry()
    9.                 .getExtensionPoint(descriptor.getId(), "Parser");
    10.         //    根據這個聲明得到所有連接到這個擴展點的擴展對象
    11.         for (Iterator it = extPoint.getConnectedExtensions().iterator(); it
    12.                 .hasNext();) {
    13.             IExtension ext = (IExtension) it.next();
    14.            
    15.             //    根據擴展對象生成Parser代理
    16.             //    也就是著名的懶加載法則
    17.             ParserProxy parser = ParserProxy.createParserProxy(ext);
    18.             parserList_.add(parser);
    19.         }
    20.     } catch (PluginException e) {
    21.         e.printStackTrace();
    22.     }
    23. }

    ParserProxy其實就是Parser的代理,它只讀取Parser的表現部分,比如圖標,名稱等等,而實例化的操作要等到具體使用的時候才去調用。

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

    當真正需要這個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.             if (plugin != null) {
    13.                 Class pluginCls = plugin.getClass();
    14.                
    15.                 //    得到主類
    16.                 Class cls = extension_.getDeclaringPluginDescriptor()
    17.                         .getPluginClassLoader().loadClass(
    18.                                 extension_.getParameter("class")
    19.                                         .valueAsString());
    20.                 if (cls != null) {
    21.                     if (pluginCls.isAssignableFrom(cls)) {
    22.                         realParser_ = (AbstractParser) plugin;
    23.                     } else {
    24.                         //    反射生成這個類
    25.                         realParser_ = (AbstractParser) cls.newInstance();
    26.                     }
    27.                 }
    28.             }
    29.         } catch (Exception e) {
    30.             return null;
    31.         }
    32.     }
    33.     return realParser_;
    34. }

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

    在Eclipse中,遍地都是這樣的例子,比如:Eclipse Platform的菜單顯示就是一個擴展點,Eclipse在顯示菜單之前首先會從系統的插件列表(PluginRegistry)中尋找所有擴展此擴展點的插件,取得圖標和名字顯示出來,然后在用戶點擊的時候生成真實的對象,并調用之,嗯,還是懶加載法則。

    BTW:菜單擴展中的類是IAction,點擊的時候調用它的run()函數,Command模式。

    寫這些的時候想起來,在聲明每個擴展點前,這個插件都需要定義一定的Interface,也就是擴展這個擴展點的插件需要遵循的API。這個Interface如果用C#中的Delegate實現,是不是會看起來更好呢?(從包的import等等)。不知道有沒有C#模仿Eclipse Plugin Framework的實例。

    8) 寫在后面

    對于一個良好的插件平臺來說,僅有一個良好的插件架構是不夠的,還需要有非常方便易用的插件開發環境。Eclipse的PDE就是這樣的產品,它能夠很大程度幫助程序員開發插件,極大降低其它人“participation”的難度 :-)

    相比之下,Netbeans就沒有這樣的插件開發環境(Netbeans中,插件都叫做模塊 - "Module"),因此,開發插件還是一件很麻煩的事情,這也就造成了其他人“participation”的困難。

    基本上就是這樣,Eclipse2.1的插件結構真的非常優秀,看到那些代碼的時候更佩服最初設計者的想法。Eclipse2.1 Plugin Framework也有一些缺點,比如不能Load/Unload on the fly(動態加載/卸載),需要重啟Eclipse等等。

    在Eclipse3.0之后,Eclipse決定遵循OSGi的標準來重構其插件機制,擁抱標準,總是一件美好的事情。

    最后強烈推薦 "Contributing to Eclipse",由Erich Gamma、Kent Beck執筆,值得一讀。

    More entries about : ,


    原文地址: http://www.mengyan.org/blog/tag/Framework

    posted on 2005-12-15 11:55 hopeshared 閱讀(2084) 評論(3)  編輯  收藏 所屬分類: Eclipse

    Feedback

    # re: 轉一篇好文:Dissect Eclipse Plugin Framework 2005-12-15 12:47 小強
    中文字體太小,看著累  回復  更多評論
      

    # re: 轉一篇好文:Dissect Eclipse Plugin Framework 2005-12-15 14:10 hopeshared
    現在好了嗎?
    這里改字體真麻煩,所以干脆改了模版  回復  更多評論
      

    # re: 轉一篇好文:Dissect Eclipse Plugin Framework 2005-12-15 16:51 Meng Yan
    Thanks for your reference. BTW: I am also BUAAer. :P  回復  更多評論
      

    主站蜘蛛池模板: 亚洲国产精品久久久久婷婷老年| 亚洲最大的黄色网| 98精品全国免费观看视频| 2020年亚洲天天爽天天噜| 亚洲国产精品碰碰| 1000部啪啪毛片免费看| 免费播放美女一级毛片| 久久国产亚洲观看| 日本免费人成黄页网观看视频| 国产一级一毛免费黄片| 亚洲色图校园春色| 亚洲Av无码乱码在线播放| 最近中文字幕mv免费高清视频8| 处破女第一次亚洲18分钟| 亚洲AV无码一区二区三区DV| 精品免费国产一区二区| 国产精品免费大片| 小说专区亚洲春色校园| 亚洲激情电影在线| 在线观看亚洲成人| 中文字幕无码成人免费视频| 免费无码av片在线观看| 精品国产日韩亚洲一区在线| 亚洲成人免费网址| 亚洲色偷拍另类无码专区| 超pen个人视频国产免费观看| 95免费观看体验区视频| 中文字幕无线码免费人妻| 亚洲成在人线aⅴ免费毛片| 精品无码一区二区三区亚洲桃色 | 亚洲av午夜成人片精品电影 | 国产婷婷成人久久Av免费高清| 亚洲人av高清无码| 亚洲成综合人影院在院播放| 亚洲精品无码永久在线观看你懂的 | 午夜免费不卡毛片完整版| 91成人在线免费视频| a级毛片在线免费看| 一级日本高清视频免费观看| 亚洲AV无码专区国产乱码不卡| 亚洲人成人77777在线播放 |