Spring双向RMI回调,服务器在客户端执行

3
在服务器端,我有一个ListenerManager,它向其Listener触发回调。使用Spring的RmiServiceExporter导出管理器。
在客户端,我有一个代理到由RmiProxyFactoryBean创建的管理器,并通过此代理向服务器端的管理器注册了Listener实现。
到目前为止一切顺利:给ListenerManager提供了一个Listener并调用了其回调,但是由于侦听器只是客户端对象的反序列化副本,因此回调在服务器端而不是客户端上运行。
如何让Spring在服务器端生成到客户端侦听器的代理,以便服务器调用的回调在客户端上远程执行? 我肯定不需要另一个(exporter、proxy factory)对吧?
3个回答

4
一种纯粹的RMI解决方案:客户端侦听器对象需要实现java.rmi.server.UnicastRemoteObject接口。如果它这样做了,并且它的每个方法都抛出RemoteException异常,那么当它通过管理代理传递到服务器时,一切都会自动连接起来,并且对该侦听器的服务器端代理上的方法调用是对真实客户端端对象方法的远程调用。
这样做可以,但更好的方法是能够在不需要特定超类的情况下包装对象以进行导出。我们可以使用CGLIB Enhancer将侦听器“代理”为UnicastRemoteObject子类,该子类还实现服务接口。这仍然要求目标对象实现java.rmi.Remote并声明throws RemoteException。
下一步是一种可以导出任意对象以进行远程调用其方法的解决方案,而无需它们实现Remote或声明throws RemoteException。我们必须将此代理与现有的Spring基础设施集成,我们可以使用新的RmiBasedExporter实现来模拟RmiServiceExporter#prepare()的非注册表部分,以导出代理的RMI存根,并在RmiClientInterceptor.doInvoke(MethodInvocation, RmiInvocationHandler)的调用部分上执行。我们需要能够获得导出的服务接口的代理实例。我们可以模仿Spring用于表面上“导出”非RMI接口的方法。Spring代理接口以生成RmiInvocationWrapper,以调用非RMI方法,序列化方法详细信息和参数,然后在RMI连接的远端调用它。
使用ProxyFactory和RmiInvocationHandler实现来代理目标对象。 使用新的RmiBasedExporter实现getObjectToExport(),并使用UnicastRemoteObject#export(obj, 0)进行导出。 对于调用处理程序,rmiInvocationHandler.invoke(invocationFactory.createRemoteInvocation(invocation)),使用DefaultRemoteInvocationFactory。 适当处理异常并进行包装,以避免看到UndeclaredThrowableException。
因此,我们可以使用RMI导出任意对象。这意味着我们可以在客户端上将其中一个这些对象用作参数,以调用RMI服务器端对象上的RMI方法,当服务器端的反序列化存根调用方法时,这些方法将在客户端上执行。神奇。

2
跟随Joe Kearney的解释,我创建了我的RMIUtil.java。希望没有遗漏。
顺便说一下,请参考这里关于“java.rmi.NoSuchObjectException: no such object in table”的问题。

你可能想要更新RMIUtil.java的链接到这个位置 - 2Aguy

0

只需在Joe的答案中添加一些代码。

扩展RmiServiceExporter并访问导出的对象:

public class RmiServiceExporter extends org.springframework.remoting.rmi.RmiServiceExporter {
    private Object remoteService;
    private String remoteServiceName;

    @Override
    public Remote getObjectToExport() {
        Remote exportedObject = super.getObjectToExport();

        if (getService() instanceof Remote && (
                getServiceInterface() == null || exportedObject.getClass().isAssignableFrom(getServiceInterface()))) {
            this.remoteService = exportedObject;
        }
        else {
            // RMI Invokers. 
            ProxyFactory factory = new ProxyFactory(getServiceInterface(), 
                    new RmiServiceInterceptor((RmiInvocationHandler) exportedObject, remoteServiceName));

            this.remoteService = factory.getProxy();
        }

        return exportedObject;
    }

    public Object getRemoteService()  {
        return remoteService;
    }

    /** 
     * Override to get access to the serviceName
     */
    @Override
    public void setServiceName(String serviceName) {
        this.remoteServiceName = serviceName;
        super.setServiceName(serviceName);
    }
}

代理中使用的拦截器(远程服务回调):

public class RmiServiceInterceptor extends RemoteInvocationBasedAccessor 
    implements MethodInterceptor, Serializable  {

    private RmiInvocationHandler invocationHandler; 
    private String serviceName;

    public RmiServiceInterceptor(RmiInvocationHandler invocationHandler) {
        this(invocationHandler, null);
    }

    public RmiServiceInterceptor(RmiInvocationHandler invocationHandler, String serviceName) {
        this.invocationHandler = invocationHandler;
        this.serviceName = serviceName;
    }

    /**
     * {@inheritDoc}
     */
    public Object invoke(MethodInvocation invocation) throws Throwable {
        try {
            return invocationHandler.invoke(createRemoteInvocation(invocation));
        }
        catch (RemoteException ex) {
                throw RmiClientInterceptorUtils.convertRmiAccessException(
                    invocation.getMethod(), ex, RmiClientInterceptorUtils.isConnectFailure(ex), 
                    extractServiceUrl());
            }
    }

    /**
     * Try to extract service Url from invationHandler.toString() for exception info
     * @return Service Url
     */
    private String extractServiceUrl() {
        String toParse = invocationHandler.toString();
        String url = "rmi://" + StringUtils.substringBefore(
                StringUtils.substringAfter(toParse, "endpoint:["), "]");

        if (serviceName != null)
            url = StringUtils.substringBefore(url, ":") + "/" + serviceName;

        return url;
    }
}

使用这个RmiServiceExporter导出服务时,我们可以发送一个RMI回调:

someRemoteService.someRemoteMethod(rmiServiceExporter.getRemoteService());

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接