简体   繁体   English

从客户端执行的服务器弹出双向rmi回调

[英]spring two-way rmi callback from server executing on client side

On the server-side I have a ListenerManager which fires callbacks to its Listener s. 在服务器端,我有一个ListenerManager ,它会向其Listener发出回调。 The manager is exported using a Spring RmiServiceExporter 使用Spring RmiServiceExporter导出管理器

On the client-side I have a proxy to the manager created by an RmiProxyFactoryBean , and a Listener implementation registered through this proxy with the manager on the server side. 在客户端,我有一个由RmiProxyFactoryBean创建的管理器的代理,以及通过此代理向服务器端的管理Listener注册的Listener实现。

So far so good: the ListenerManager is given a Listener and it invokes its callbacks, however since the listener is just a deserialized copy of the client-side object, the callback runs on the server side, not the client side. 到目前为止一直很好: ListenerManager被赋予一个Listener并且它调用它的回调,但是由于监听器只是客户端对象的反序列化副本,所以回调运行在服务器端,而不是客户端。

How can I get Spring to generate a proxy on the server-side to the client-side listener so that the callback invoked by the server is executed remotely on the client-side? 如何让Spring在服务器端为客户端侦听器生成代理,以便服务器调用的回调在客户端远程执行? Surely I don't need another (exporter, proxy factory) pair in the opposite direction? 当然,我不需要另一个(出口商,代理工厂)对相反的方向?

A pure RMI solution : the client-side listener object needs to implement java.rmi.server.UnicastRemoteObject . 纯RMI解决方案 :客户端侦听器对象需要实现java.rmi.server.UnicastRemoteObject If it does, and each of its methods throw RemoteException then when it is passed to the server through the manager proxy everything is wired up automatically, and method invocations on the server-side proxy to this listener are remote invocations of methods on the real client-side object. 如果是这样,并且它的每个方法抛出RemoteException那么当它通过管理器代理传递给服务器时,一切都自动连接,服务器端代理到该侦听器的方法调用是对真实客户端上的方法的远程调用对象。

This will do, but it's even better to be able to wrap the object for export without requiring a particular superclass . 这样做,但是能够在不需要特定超类的情况下包装对象以进行导出更好。 We can use a CGLIB Enhancer to "proxy" the listener as a subclass of UnicastRemoteObject that also implements the service interfaces. 我们可以使用CGLIB Enhancer将侦听器“代理”为同时实现服务接口的UnicastRemoteObject的子类。 This still requires that the target object implement java.rmi.Remote and declare throws RemoteException . 这仍然要求目标对象实现java.rmi.Remote并声明throws RemoteException

Next step is a solution that can export arbitrary objects for remote invocation of their methods, without requiring that they implement Remote or declare throws RemoteException . 下一步是一个解决方案,可以导出任意对象以远程调用其方法,而无需实现Remote或声明throws RemoteException We must integrate this proxying with the existing Spring infrastructure, which we can do with a new implementation of RmiBasedExporter modelled on the non-registry bits of RmiServiceExporter#prepare() to export the RMI stub of our proxy and on the invocation part of RmiClientInterceptor.doInvoke(MethodInvocation, RmiInvocationHandler) . 我们必须将这个代理与现有的Spring基础结构集成在一起,我们可以使用RmiBasedExporter的新实现来模拟RmiServiceExporter#prepare()的非注册表位,以导出代理的RMI存根和RmiClientInterceptor.doInvoke(MethodInvocation, RmiInvocationHandler)的调用部分RmiClientInterceptor.doInvoke(MethodInvocation, RmiInvocationHandler) We need to be able to get hold of an exported proxy instance of our service interfaces. 我们需要能够获得服务接口的导出代理实例。 We can model this on the means used by Spring to apparently "export" non-RMI interfaces. 我们可以使用Spring对显然“导出”非RMI接口的方法对此进行建模。 Spring proxies the interface to generate a RmiInvocationWrapper for invocation of a non-RMI method, serialises the method details and arguments, then invokes this on the far side of the RMI connection. Spring代理接口以生成用于调用非RMI方法的RmiInvocationWrapper ,序列化方法细节和参数,然后在RMI连接的远端调用它。

  • Use a ProxyFactory and an RmiInvocationHandler implementation to proxy the target object. 使用ProxyFactoryRmiInvocationHandler实现来代理目标对象。
  • Use a new implementation of RmiBasedExporter to getObjectToExport() , and export it using UnicastRemoteObject#export(obj, 0) . 使用RmiBasedExporter的新实现来getObjectToExport() ,并使用UnicastRemoteObject#export(obj, 0) RmiBasedExporter UnicastRemoteObject#export(obj, 0)将其UnicastRemoteObject#export(obj, 0)
  • For the invocation handler, rmiInvocationHandler.invoke(invocationFactory.createRemoteInvocation(invocation)) , with a DefaultRemoteInvocationFactory . 对于调用处理程序, rmiInvocationHandler.invoke(invocationFactory.createRemoteInvocation(invocation)) ,带有DefaultRemoteInvocationFactory
  • Handle exceptions and wrap appropriately to avoid seeing UndeclaredThrowableException s. 处理异常并适当地换行以避免看到UndeclaredThrowableException

So, we can use RMI to export arbitrary objects. 因此,我们可以使用RMI导出任意对象。 This means we can use one of these objects on the client-side as a parameter to an RMI method call on an RMI server-side object, and when the deserialised stub on the server-side has methods invoked, those methods will execute on the client-side. 这意味着我们可以在客户端使用其中一个对象作为RMI服务器端对象上的RMI方法调用的参数,并且当服务器端的反序列化存根调用了方法时,这些方法将在客户端。 Magic. 魔法。

Following Joe Kearney's explaination, I have created my RMIUtil.java . 在Joe Kearney的解释之后,我创建了我的RMIUtil.java Hope there is nothing left. 希望没有什么可以留下的。

BTW, please ref this for "java.rmi.NoSuchObjectException: no such object in table" 顺便说一下,请为“java.rmi.NoSuchObjectException:表中没有这样的对象”引用

Just add some code to Joe's answer. 只需在Joe的答案中添加一些代码即可。

Extends RmiServiceExporter and get access to exported object: 扩展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);
    }
}

The interceptor used in the proxy (the remote service callback): 代理中使用的拦截器(远程服务回调):

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

When exporting the service with this RmiServiceExporter , we cand send a rmi callback with: 使用此RmiServiceExporter导出服务时,我们会发送一个rmi回调:

someRemoteService.someRemoteMethod(rmiServiceExporter.getRemoteService());

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM