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

У нас есть несколько устаревших 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-вызовы в одной точке для реализации логики транзакций?

3 ответа

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

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

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

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

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

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

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

Несмотря на все вышесказанное

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

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

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

введите описание изображения здесь

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

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

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

введите описание изображения здесь

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

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

введите описание изображения здесь

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, Взгляните сюда еще раз.

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

@Transactional аннотации очень широко используются для декларативного управления транзакциями - Spring автоматически обернет ваш компонент в помощнике по транзакциям AOP.

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

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

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

Другие вопросы по тегам