Zero ICE在跨平台、跨語言的環境中是一種非常好的RPC方案,而且使用簡單。早期在使用ICE時,每一個后端功能模塊都以獨立服務方式部署,在功能模塊較少時不會有明顯的問題,但是隨着功能模塊的增多,部署的服務越來越多,產生的直接問題有:
- 每個服務都需要開啟一個監聽端口,新增服務必須配置防火牆,且影響安全性;
- 每個服務即為一個進程,增大系統負擔。
想到能否按照插件方式來開發功能模塊,同時還能解決上面兩個問題。因為所有的后端服務使用Java語言開發,於是選擇了java平台下的輕量級插件框架pf4j(關於pf4j的詳細資料,請參考github.com上項目說明)。
下面,我以最少的代碼來闡述這個插件式ICE服務框架。
- 定義插件擴展接口
IceService.java
1 package server; 2 3 import ro.fortsoft.pf4j.ExtensionPoint; 4 5 public interface IceService extends ExtensionPoint { 6 Ice.Object getObject(); 7 String getName(); 8 String getGUID(); 9 }
為了演示插件接口的可擴展性,定義了一個IceServiceV2.java
package server; public interface IceServiceV2 extends IceService { String getVersion(); }
- 開發插件
插件1—Echo
package plugin.echo; import Ice.Object; import ro.fortsoft.pf4j.Extension; import ro.fortsoft.pf4j.Plugin; import ro.fortsoft.pf4j.PluginWrapper; import server.IceService; import server.IceServiceV2; public class EchoPlugin extends Plugin { public EchoPlugin(PluginWrapper wrapper) { super(wrapper); } @Override public void start() { System.out.println("start plugin " + this.getClass().getName()); } @Override public void stop() { System.out.println("stop plugin " + this.getClass().getName()); } @Extension public static class EchoService implements IceServiceV2 { Ice.Object object; public Object getObject() { object = new EchoI(); return object; } public String getName() { return "Echo"; } public String getGUID() { return "1234-5678"; } @Override public String getVersion() { return "V2"; } } }
其中,EchoI類的定義如下
package plugin.echo; import Ice.Current; public class EchoI extends rpc._EchoDisp { @Override public String reply(String message, Current __current) { System.out.println("Receive " + message); return message; } }
其實現了如下ice接口
module rpc { interface Echo { string reply(string message); }; };
插件2—Hello
package plugin.hello; import Ice.Object; import ro.fortsoft.pf4j.Extension; import ro.fortsoft.pf4j.Plugin; import ro.fortsoft.pf4j.PluginWrapper; import server.IceService; public class HelloPlugin extends Plugin { public HelloPlugin(PluginWrapper wrapper) { super(wrapper); } @Override public void start() { System.out.println("start plugin " + this.getClass().getName()); } @Override public void stop() { System.out.println("stop plugin " + this.getClass().getName()); } @Extension public static class HelloService implements IceService { Ice.Object object; @Override public Object getObject() { object = new HelloI(); return object; } @Override public String getName() { return "Hello"; } public String getGUID() { return "5678-5678"; } } }
其中,HelloI類的定義如下
package plugin.hello; import Ice.Current; public class HelloI extends rpc._HelloDisp { public void sayHello(String message, Current __current) { System.out.println("Hello " + message); } }
其實現了如下ice接口
module rpc { interface Hello { void sayHello(string message); }; };
- 制作插件
在這里,有必要提一下我遇到的波折。查看了pf4j的文檔后得知,每個插件的目錄結構為
echo + | -- classes + | | | -- META-INF + | | | | | --extensions.idx | | | | | --MANIFEST.MF | -- (編譯后的插件代碼) -- lib + | -- (插件依賴的第三方jar
由於我使用eclipse編輯和生成代碼,因此在編譯代碼時並沒有為@Extension標注生成extensions.idx文件,所以第一次驗證插件是否生效時,loadplugins和startplugins都沒有問題,但是執行List<IceService> exts = manager.getExtensions(IceService.class)時並沒有找到extension,這是掉的第一個坑。
由於我並不想使用maven來生成代碼,於是手動創建了插件的extensions.idx文件,其中echo插件的內容是
plugin.echo.EchoPlugin$EchoService
MANIFEST.MF文件的內容為
Manifest-Version: 1.0 Plugin-Dependencies: Plugin-Version: 0.0.1 Plugin-Id: echo-plugin Plugin-Provider: Super Plugin-Class: plugin.echo.EchoPlugin Build-Jdk: 1.8.0_92
在准備好插件必要的文件后,再次執行代碼,遇到了第二個坑,這個坑有點深——將整個項目編譯后的代碼都拷貝到了兩個插件中,這直接導致的后果是依然找不到extension。直至我在看了N遍pf4j的demo代碼,並使用maven生成並成功執行其demo后,才確定了這個深坑。
- 執行主程序
package server; import java.util.List; import ro.fortsoft.pf4j.DefaultPluginManager; public class Entry { public static void main(String[] args) { try { DefaultPluginManager manager = new DefaultPluginManager(); manager.loadPlugins(); manager.startPlugins(); Ice.Communicator ic = Ice.Util.initialize(); Ice.ObjectAdapter adapter = ic.createObjectAdapterWithEndpoints("IceAdapter", "default -p 9005"); List<IceService> exts = manager.getExtensions(IceService.class); for (IceService ext : exts) { System.out.println("Add object " + ext.getName() + ", GUID=" + ext.getGUID()); if (ext instanceof IceServiceV2) { System.out.println(((IceServiceV2)ext).getVersion()); } adapter.add(ext.getObject(), ic.stringToIdentity(ext.getName())); } System.out.println("Active adapter"); adapter.activate(); ic.waitForShutdown(); } catch (Exception ex) { ex.printStackTrace(); } } }
當然,我實際編寫的ICE服務框架比這個要復雜的多,包括使用到Java Service Wrapper,僅裝載.zip格式的插件,增加異常處理等。
最后給出Ice的Client代碼
package client; public class Echo { public static void main(String[] args) { Ice.Communicator ic = null; try { ic = Ice.Util.initialize(); Ice.ObjectPrx base = ic.stringToProxy("Echo:default -p 9005"); rpc.EchoPrx proxy = rpc.EchoPrxHelper.checkedCast(base); if (proxy == null) { throw new Error("Invalid proxy"); } for (int i = 0; i < 10; i++) { System.out.println(proxy.reply("message " + i)); } } catch (Ice.LocalException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } if (ic != null) { try { ic.destroy(); } catch (Exception ex) { ex.printStackTrace(); } } System.out.println("exit"); } }