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"); } }