在 Java 上這個概念可以透過 ClassLoader 的技巧來實現。
雖然網路上也有一些文章教導如何自行建立簡單的套件系統,不過現在其實也有一些 Open Source 的專案可以利用了~。
基於不要重複發明輪子的理念,我就開始快速地搜尋了既有的 plugin framework
而查到比較有人在討論的,大概是 OSGi、JPF 和 jspf 吧。
另外也可以看看 [1] 的討論,發問的網友 Marcus 也提到另一個 "Rolling your own" plugin framework。
我自己主要看了 OSGi、JPF 和 jspf 這三個框架,其中 JPF 因為看起來已經很久沒有在維護了,因此直接跳過它
OSGi 從官方網站上,我沒辦法很快看出它是不是開源的專案,所以也就被略過 XD
同時也看了 [1] 中的回應,jspf 的作者也回覆了那個問題,大略是提到說 jspf 主要目的是做輕量級的套件框架(而且不使用 XML)
因此最後就決定從 jspf 入手試試。
jspf 的官方網站 [2] 中,主要可以參考 FAQ 頁面以及 UsageGuide 頁面,大略就可以拼湊出函式庫的基本使用方法。
以下是我這邊建立的簡易範例,使用的 jspf 版本為 1.0.2,在 Java 7 環境下執行。
主要會建立兩個 Java 專案,一個是包含 main() 的主程式(專案名稱為 PluginSystem),另一個則是只有 plugin 的套件程式(專案名稱為 Plugin)。
兩個專案的資料夾結構如下:
[PluginSystem] - [src] - [plugin] - PluginMain.java - [test] - [plugin] - ExecutablePlugin.java [Plugin] - [src] - [test] - [plugin] - ExecutablePlugin.java - [impl] - ExecutablePluginImpl.java
大略就是建了兩個 package "plugin" 和 "test.plugin",plugin 這個 package 放包含 main() 的主程式,test.plugin 則放套件的介面和實作
其中介面檔 ExecutablePlugin.java 同時放在兩個專案的相同 package,實作檔 ExecutablePluginImpl.java 則只有在 Plugin 這個專案裡。
此外,實作檔的放置位置依照官方文件的建議,放在介面所在的 package 底下的 impl 裡。
接著就是三個 Java 檔的實作內容。
主程式 PluginMain.java
package plugin; import java.nio.file.Paths; import java.util.Collection; import org.apache.log4j.LogManager; import org.apache.log4j.Logger; import test.plugin.ExecutablePlugin; import net.xeoh.plugins.base.PluginManager; import net.xeoh.plugins.base.impl.PluginManagerFactory; import net.xeoh.plugins.base.util.PluginManagerUtil; public class PluginMain { public static void main(String[] args) { Logger log = LogManager.getLogger("Main"); log.debug("Start."); while (true) { PluginManager pm = PluginManagerFactory.createPluginManager(); pm.addPluginsFrom(Paths.get("D:", "test", "plugin").toUri()); log.debug("Try to run a plugin..."); PluginManagerUtil pmu = new PluginManagerUtil(pm); Collection<ExecutablePlugin> colls = pmu.getPlugins(ExecutablePlugin.class); if(colls == null || colls.size() < 1) log.debug("No plugin is found."); else { for(ExecutablePlugin plugin : colls) { plugin.execute(); } } try { Thread.sleep(2000); } catch (InterruptedException e) {} } } }
套件的介面 ExecutablePlugin.java
package test.plugin; import net.xeoh.plugins.base.Plugin; public interface ExecutablePlugin extends Plugin { public void execute (); }
套件的實作 ExecutablePluginImpl.java
package test.plugin.impl; import net.xeoh.plugins.base.annotations.PluginImplementation; import org.apache.log4j.LogManager; import org.apache.log4j.Logger; import test.plugin.ExecutablePlugin; @PluginImplementation public class ExecutablePluginImpl implements ExecutablePlugin { @Override public void execute() { Logger log = LogManager.getLogger("ExecutablePluginImpl"); log.debug("Plugin is executed!"); } }
執行的測試中,一開始在 D:\test\plugin\ 資料夾中是空的,等到過了 6 秒(即主程式的 while 迴圈跑完三次)後才把 Plugin 專案匯出的 jar 放進去。
執行結果如下:
2014-09-23 18:03:54.449 | DEBUG | Main > Start. 2014-09-23 18:03:54.562 | DEBUG | Main > Try to run a plugin... 2014-09-23 18:03:54.564 | DEBUG | Main > No plugin is found. 2014-09-23 18:03:56.568 | DEBUG | Main > Try to run a plugin... 2014-09-23 18:03:56.568 | DEBUG | Main > No plugin is found. 2014-09-23 18:03:58.570 | DEBUG | Main > Try to run a plugin... 2014-09-23 18:03:58.570 | DEBUG | Main > No plugin is found. 2014-09-23 18:04:00.572 | DEBUG | Main > Try to run a plugin... 2014-09-23 18:04:00.572 | DEBUG | Main > No plugin is found. 2014-09-23 18:04:02.600 | DEBUG | Main > Try to run a plugin... 2014-09-23 18:04:02.600 | DEBUG | ExecutablePluginImpl > Plugin is executed! 2014-09-23 18:04:04.622 | DEBUG | Main > Try to run a plugin... 2014-09-23 18:04:04.622 | DEBUG | ExecutablePluginImpl > Plugin is executed!可以看到是能夠動態地把中途突然出現的 jar 載入並正確執行!
此外,在嘗試實作的過程中,發現 jar 出現的時間好像跟 PluginManagerFactory.createPluginManager() 的呼叫時間是有關聯的。
如果在主程式中,將 PluginManagerFactory.createPluginManager() 移至 while 迴圈外頭
Plugin 專案的 jar 檔就必須在主程式一開始執行前就要放在套件的指定目錄裡,才能夠被動態偵測到。
這也是為什麼上面的測試程式是把 PluginManagerFactory.createPluginManager() 放在 while 迴圈裡面。
參考資料:
1、Java plugin framework choice
2、jspf: Java Simple Plugin Framwork. 5 minutes and it works. No XML.
沒有留言:
張貼留言