将接口转换为 RMI 接口

Convert interface to RMI interface

我有接口,例如:

interface MyService {
  String sayHello(Object who) throws ServiceException;
}

现在我需要将此接口与 RMI 一起使用。当然我可以定义另一个接口,如:

interface MyService extends Remote{
  String sayHello(Object who) throws RemoteException;
}

然后调整我的实现 class 以适应 RMI 接口。

但是我有数百个接口可以转换为 RMI。用相同的逻辑编写数百个 classes 是如此无聊和丑陋。

那么有没有简单的方法可以在RMI中使用这些接口呢?

这是我作品中的真实场景。我使用动态字节码一次完成了数百个工作。

首先,这是我的旧代码演示(没有 RMI)。

class ServiceException extends Exception {
  public ServiceException(String msg) {
    super(msg);
  }
}

interface MyService {
  String sayHello(Object who) throws ServiceException;

  void throwIt() throws ServiceException;
}

class MyServiceImpl implements MyService {

  @Override
  public String sayHello(Object who) throws ServiceException {
    String hello = who.toString();
    System.out.println("Server said: " + hello);
    return "Hello! " + hello;
  }

  @Override
  public void throwIt() throws ServiceException {
    throw new ServiceException("throw in server");
  }
}

并且我使用Javassist将接口转换为RMI接口:

  public static Class<? extends Remote> toRemoteInterface(Class<?> inter) throws Exception {
    return cache("toRemote", inter, () -> uncheck(() -> {
      ClassPool pool = ClassPool.getDefault();
      CtClass cc = pool.getAndRename(inter.getName(), inter.getName() + "$RemoteVersion");
      cc.setModifiers(Modifier.PUBLIC | cc.getModifiers());
      cc.addInterface(pool.get(Remote.class.getName()));
      for (CtMethod cm : cc.getMethods()) {
        cm.setExceptionTypes(new CtClass[] { pool.getCtClass(RemoteException.class.getName()) });
      }
      cc.writeFile();
      return cc.toClass();
    }));
  }

然后,使用Cglib在远程对象和本地对象之间进行转换:

  public static <T> T fromRemote(Remote remote, Class<T> inter) throws Exception {
    Enhancer e = new Enhancer();
    e.setInterfaces(new Class[] { inter });
    e.setCallback((MethodInterceptor) (obj, method, args, proxy) -> {
      Method remoteMethod = remote.getClass().getMethod(method.getName(), method.getParameterTypes());
      try {
        return remoteMethod.invoke(remote, args);
      } catch (InvocationTargetException ex) {
        Throwable targetException = ex.getTargetException();
        while (targetException instanceof RemoteException) {
          targetException = targetException.getCause();
        }
        throw targetException;
      }
    });
    return (T) e.create();
  }

  public static <T> Remote toRemote(T local, Class<T> inter) throws Exception {
    Enhancer e = new Enhancer();
    e.setSuperclass(UnicastRemoteObject.class);
    e.setInterfaces(new Class[] { toRemoteInterface(inter) });
    e.setCallback((MethodInterceptor) (obj, method, args, proxy) -> {
      Method targetMethod = local.getClass().getMethod(method.getName(), method.getParameterTypes());
      try {
        return targetMethod.invoke(local, args);
      } catch (InvocationTargetException ex) {
        Throwable targetException = ex.getTargetException();
        throw new RemoteException(targetException.getMessage(), targetException);
      }
    });
    return (Remote) e.create();
  }

现在我们可以像使用 RMI 接口一样使用非 rmi 接口及其实现了:

  public static void startClient() throws Exception {
    String stringURL = "rmi://127.0.0.1/" + MyService.class.getName();
    toRemoteInterface(MyService.class);// define the Remote interface in client classloader
    MyService service = fromRemote(Naming.lookup(stringURL), MyService.class);
    String said = service.sayHello("Dean");
    System.out.println("Client heard: " + said);
    service.throwIt();
  }

  public static void startServer() throws Exception {
    LocateRegistry.createRegistry(1099);
    Remote remote = toRemote(new MyServiceImpl(), MyService.class);
    Naming.rebind(MyService.class.getName(), remote);
    System.out.println(remote);
    System.out.println(remote.getClass());
    System.out.println("Server started!");
  }

完整代码,see this on github