【原创】插件式ICE服务框架

Zero ICE在跨平台、跨语言的环境中是一种非常好的RPC方案,而且使用简单。早期在使用ICE时,每一个后端功能模块都以独立服务方式部署,在功能模块较少时不会有明显的问题,但是随着功能模块的增多,部署的服务越来越多,产生的直接问题有:

  1. 每个服务都需要开启一个监听端口,新增服务必须配置防火墙,且影响安全性;
  2. 每个服务即为一个进程,增大系统负担。

想到能否按照插件方式来开发功能模块,同时还能解决上面两个问题。因为所有的后端服务使用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");
    }
}
原文地址:https://www.cnblogs.com/sper/p/5716819.html