весенний двусторонний обратный вызов rmi с сервера, выполняющийся на стороне клиента - PullRequest
3 голосов
/ 23 сентября 2010

На стороне сервера у меня есть ListenerManager, который запускает обратные вызовы для Listener с. Менеджер экспортируется с помощью Spring RmiServiceExporter

На стороне клиента у меня есть прокси для менеджера, созданного RmiProxyFactoryBean, и реализация Listener, зарегистрированная через этот прокси с менеджером на стороне сервера.

Пока все хорошо: ListenerManager дается Listener, и он вызывает свои обратные вызовы, однако, поскольку слушатель является просто десериализованной копией объекта на стороне клиента, обратный вызов выполняется на стороне сервера, а не клиентская сторона.

Как я могу заставить Spring генерировать прокси на стороне сервера для прослушивателя на стороне клиента, чтобы обратный вызов, вызываемый сервером, выполнялся удаленно на стороне клиента? Конечно, мне не нужна другая пара (экспортер, фабрика прокси) в обратном направлении?

Ответы [ 3 ]

4 голосов
/ 23 сентября 2010

A Чистое решение 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-соединения.

  • Use a ProxyFactory и реализация RmiInvocationHandler для прокси целевого объекта.
  • Используйте новую реализацию от RmiBasedExporter до getObjectToExport() и экспортируйте ее, используя UnicastRemoteObject#export(obj, 0).
  • Для вызоваобработчик, rmiInvocationHandler.invoke(invocationFactory.createRemoteInvocation(invocation)), с DefaultRemoteInvocationFactory.
  • Обрабатывать исключения и переносить их соответствующим образом, чтобы не видеть UndeclaredThrowableException s.

Таким образом, мы можем использовать RMI для экспорта произвольных объектов,Это означает, что мы можем использовать один из этих объектов на стороне клиента в качестве параметра для вызова метода RMI для объекта на стороне сервера RMI, и когда десериализованная заглушка на стороне сервера вызывает методы, эти методы будут выполняться насторона клиента.Магия.

2 голосов
/ 06 января 2012

Следуя объяснениям Джо Кирни, я создал свой RMIUtil.java . Надеюсь, ничего не осталось.

Кстати, пожалуйста, укажите это для "java.rmi.NoSuchObjectException: нет такого объекта в таблице"

0 голосов
/ 21 марта 2013

Просто добавьте немного кода в ответ Джо.

Расширяет 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());
...