Общая транзакционная логика в java RMI-сервисе?

89
11

У нас есть несколько устаревших java-сервисов с RMI-api, реализованных старым JRMP-подходом, требующим предварительной компиляции rmic.

В рамках переноса всего на последний JDK я также пытаюсь переписать материал RMI на более современный подход, где классы реализации распространяются от UnicastRemoteObject, тем самым избавляясь от этапа предварительной компиляции rmic.

Следуя простому примеру, например, https://www.mkyong.com/java/java-rmi-hello-world-example/, но я не смог найти такой пример с транзакционной логикой commit/rollback.

В текущем унаследованном коде вся транзакционная логика обрабатывается одним общим методом invokeObject() в контейнере-коде JRMP, который будет перехватывать все api-вызовы RMI в одном месте и будет просто фиксировать, если RMI-вызов успешный или откат, если было выбрано исключение.

Я не смог понять, как это сделать в новом подходе без присутствия контейнера JRMP. Очевидно, я не хочу кодировать commit/rollback-логику в каждый один api-метод (их много десятков), но все равно сохраняйте эту единую логику в одном месте.

Любые советы, подсказки, ссылки и т.д., Как перехватывать все RMI-вызовы в одной точке для реализации транзакционной логики?

спросил(а) 2018-08-14T11:20:00+03:00 1 год, 7 месяцев назад
1
Решение
51

Вы можете использовать Spring с RMI. Он предлагает упрощенный способ использования транзакций. Вы просто устанавливаете некоторые настройки сохранения из аннотации @EnableTransactionManagement и управляете транзакциями для вас в точке, определяемой @Transactional. Это может быть класс или методы класса.

пример кода здесь

Объяснение содержится в проекте README.

ответил(а) 2018-08-23T17:39:00+03:00 1 год, 7 месяцев назад
51

Для начала, я согласен с @df778899, решение, которое он предоставляет, является надежным решением. Хотя, я дам вам альтернативный вариант, если вы не хотите работать с весенним каркасом, и вы хотите его копать дальше.

Перехватчики обеспечивают мощный и гибкий дизайн, включая мониторинг вызовов, протоколирование и маршрутизацию сообщений. В некотором смысле ключевое значение, которое вы ищете, - это своего рода поддержка AOP (аспектно-ориентированное программирование) на уровне RMI

Вообще говоря:

несправедливо просить RMI напрямую поддерживать такие возможности, поскольку это всего лишь базовый примитив вызова удаленного метода, в то время как CORBA ORB находится на уровне, близком к тому, что предлагает контейнер J2EE EJB (Enterprise JavaBean). В спецификации CORBA контекст службы поддерживается непосредственно на уровне IIOP (GIOP или General Inter-Orb Protocol) и интегрируется с временем выполнения ORB. Однако для RMI/IIOP приложениям нелегко использовать базовую поддержку обслуживания контекста IIOP, даже если уровень протокола имеет такую поддержку. В то же время такая поддержка недоступна, если используется RMI/JRMP (Java Remote Method Protocol). В результате для распределенных приложений на основе RMI, которые не используют или не должны использовать среду контейнеров ORB или EJB, отсутствие таких возможностей ограничивает доступные варианты дизайна, особенно когда существующие приложения должны быть расширены для поддержки новых функции уровня инфраструктуры. Изменение существующих интерфейсов RMI часто оказывается нежелательным из-за зависимостей между компонентами и огромного влияния на клиентские приложения. Наблюдение этого ограничения RMI приводит к общему решению, которое я описываю в этой статье

Несмотря на все вышеизложенное

Решение основано на методах отражения Java и некоторых распространенных методах реализации перехватчиков. Что еще более важно, он определяет архитектуру, которая может быть легко интегрирована в любой проект распределенного приложения на основе RMI.

Нижеприведенное решение демонстрируется с помощью примерной реализации, которая поддерживает прозрачную передачу данных контекста транзакции, таких как идентификатор транзакции (xid), с RMI. Решение содержит следующие три важных компонента:

    Инкапсуляция функции именования функций удаленного интерфейса RMI и перехватчика Механизм распространения службы и контекста и поддержка интерфейса на стороне сервера Структура данных контекста обслуживания и поддержка распространения контекста контекста

enter image description here

Реализация предполагает, что используется RMI/IIOP. Однако это никоим образом не означает, что это решение предназначено только для RMI/IIOP. Фактически, RMI/JRMP или RMI/IIOP могут использоваться как базовые среды RMI или даже гибрид двух сред, если служба именования поддерживает оба

Инкапсуляция функции именования

Сначала мы инкапсулируем функцию именования, которая обеспечивает поиск удаленного интерфейса RMI, позволяя прозрачно подключать перехватчики. Такая инкапсуляция всегда желательна и всегда может быть найдена в большинстве приложений на основе RMI. Основополагающий механизм разрешения имен не вызывает беспокойства; это может быть все, что поддерживает JNDI (интерфейс именования и интерфейс Java). В этом примере, чтобы сделать код более иллюстративным, мы предполагаем, что все серверные удаленные RMI-интерфейсы наследуются от удаленного интерфейса ServiceInterface, который сам наследует интерфейс Java RMI Remote. На рисунке показана диаграмма классов, за которой следуют фрагменты кода, которые я опишу дальше

enter image description here

Перехватчик вызова RMI

Чтобы включить перехватчик вызова, исходный номер RMI-заглушки, полученный из службы именования RMI, должен быть обернут локальным прокси-сервером. Чтобы обеспечить общую реализацию, такой прокси реализуется с использованием динамического прокси-API Java. Во время выполнения создается экземпляр прокси; он реализует тот же интерфейс ServiceInterface RMI, что и обернутая ссылка на заглушку. Любой вызов будет делегирован на заглушку в конце концов после первой обработки перехватчиком. Простая реализация фабрики перехватчика RMI следует диаграмме классов, показанной на рисунке

enter image description here

package rmicontext.interceptor;

public interface ServiceInterfaceInterceptorFactoryInterface {
ServiceInterface newInterceptor(ServiceInterface serviceStub, Class serviceInterfaceClass) throws Exception;
}

package rmicontext.interceptor;

public class ServiceInterfaceInterceptorFactory
implements ServiceInterfaceInterceptorFactoryInterface {

public ServiceInterface newInterceptor(ServiceInterface serviceStub, Class serviceInterfaceClass)
throws Exception {

ServiceInterface interceptor = (ServiceInterface)
Proxy.newProxyInstance(serviceInterfaceClass.getClassLoader(),
new Class[]{serviceInterfaceClass},
new ServiceContextPropagationInterceptor(serviceStub)); // ClassCastException

return interceptor;
}
}

package rmicontext.interceptor;

public class ServiceContextPropagationInterceptor
implements InvocationHandler {

/**
* The delegation stub reference of the original service interface.
*/
private ServiceInterface serviceStub;

/**
* The delegation stub reference of the service interceptor remote interface.
*/
private ServiceInterceptorRemoteInterface interceptorRemote;

/**
* Constructor.
*
* @param serviceStub The delegation target RMI reference
* @throws ClassCastException as a specified uncaught exception
*/
public ServiceContextPropagationInterceptor(ServiceInterface serviceStub)
throws ClassCastException {

this.serviceStub = serviceStub;

interceptorRemote = (ServiceInterceptorRemoteInterface)
PortableRemoteObject.narrow(serviceStub, ServiceInterceptorRemoteInterface.class);
}

public Object invoke(Object proxy, Method m, Object[] args)
throws Throwable {
// Skip it for now ...
}
}

Для завершения класса ServiceManager реализован простой кэш прокси-сервера:

package rmicontext.service;

public class ServiceManager
implements ServiceManagerInterface {

/**
* The interceptor stub reference cache.
* <br><br>
* The key is the specific serviceInterface sub-class and the value is the interceptor stub reference.
*/
private transient HashMap serviceInterfaceInterceptorMap = new HashMap();

/**
* Gets a reference to a service interface.
*
* @param serviceInterfaceClassName The full class name of the requested interface
* @return selected service interface
*/
public ServiceInterface getServiceInterface(String serviceInterfaceClassName) {

// The actual naming lookup is skipped here.
ServiceInterface serviceInterface = ...;

synchronized (serviceInterfaceInterceptorMap) {

if (serviceInterfaceInterceptorMap.containsKey(serviceInterfaceClassName)) {
WeakReference ref = (WeakReference) serviceInterfaceInterceptorMap.get(serviceInterfaceClassName);
if (ref.get() != null) {
return (ServiceInterface) ref.get();
}
}
try {
Class serviceInterfaceClass = Class.forName(serviceInterfaceClassName);

ServiceInterface serviceStub =
(ServiceInterface) PortableRemoteObject.narrow(serviceInterface, serviceInterfaceClass);

ServiceInterfaceInterceptorFactoryInterface factory = ServiceInterfaceInterceptorFactory.getInstance();
ServiceInterface serviceInterceptor =
factory.newInterceptor(serviceStub, serviceInterfaceClass);

WeakReference ref = new WeakReference(serviceInterceptor);
serviceInterfaceInterceptorMap.put(serviceInterfaceClassName, ref);

return serviceInterceptor;
} catch (Exception ex) {
return serviceInterface; // no interceptor
}
}
}
}

Вы можете найти более подробную информацию в следующей директиве здесь. Важно понять все это выше, если вы хотите сделать это с нуля, в отличие от надежного решения Spring-AOP Framework. Кроме того, я думаю, вы найдете очень интересный простой исходный код Spring Framework для реализации an RmiClientInterceptor. Посмотрите еще раз на это.

ответил(а) 2018-08-23T12:48:00+03:00 1 год, 7 месяцев назад
51

Я не знаю, если вы уже это рассмотрели, одна из возможностей, которая хорошо сочеталась с двойным требованием RMI и транзакционных границ, явно весна. Примером Spring Remoting является здесь.

Аннотации @Transactional очень широко используются для декларативного управления транзакциями. Spring автоматически переносит ваш компонент в советник по транзакции АОП.

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

Ссылка Spring Remoting выше основана на Spring Boot, что является простым способом начать работу. По умолчанию Boot захочет включить встроенный сервер в среду, хотя это можно отключить. Аналогичным образом существуют другие варианты, такие как автономный AnnotationConfigApplicationContext или WebXmlApplicationContext в существующем веб-приложении.

Естественно, с вопросом, требующим рекомендаций, в любом ответе будет элемент мнения - я был бы разочарован, чтобы не увидеть некоторых других предложений в ближайшее время.

ответил(а) 2018-08-18T12:01:00+03:00 1 год, 7 месяцев назад
Ваш ответ
Введите минимум 50 символов
Чтобы , пожалуйста,
Выберите тему жалобы:

Другая проблема