5

我们有几个带有 RMI-api 的遗留 Java 服务,由需要“rmic”预编译的旧 JRMP 方法实现。

作为将所有内容迁移到最新 JDK 的一部分,我还尝试将 RMI 内容重写为更当前的方法,其中实现类从 UnicastRemoteObject 扩展,从而摆脱了 rmic 预编译步骤。

遵循一个简单的示例,例如这里: https ://www.mkyong.com/java/java-rmi-hello-world-example/ 但我无法使用commit/rollback transaction-logic找到这样的示例。

在当前的遗留代码中,所有事务逻辑都在 JRMP 容器代码中的单个通用方法 invokeObject() 中处理,该方法将在一个地方拦截所有RMI api 调用,如果 RMI 调用是成功,如果抛出异常则回滚。

在没有 JRMP 容器的情况下,我无法弄清楚如何在新方法中执行此操作。显然,我不想将提交/回滚逻辑编码到每个 api 方法中(有几十个),但仍将统一的逻辑保留在一个地方。

任何建议、提示、参考等,如何在一个点中拦截所有 RMI 调用以实现事务逻辑?

4

3 回答 3

2

您可以考虑将 Spring 与 RMI 一起使用。它提供了一种使用事务的简化方式。@EnableTransactionManagement您只需从注释中设置一些持久性设置,它就会在@Transactional. 它可以是一个类或一个类的方法。

示例代码在这里

解释在项目 README 中。

于 2018-08-23T14:39:01.960 回答
2

首先,我同意@df778899 他提供的解决方案是一个强大的解决方案。虽然,如果您不想使用 spring 框架并且想进一步挖掘它,我会给您一个替代选择。

拦截器提供了强大而灵活的设计,包括调用监控、日志记录和消息路由。从某种意义上说,您正在寻找的关键价值是一种RMI 级别的 AOP(面向方面​​的编程)支持

通常来说,一般来说:

要求 RMI 直接支持这些功能是不公平的,因为它只是一个基本的远程方法调用原语,而 CORBA ORB 处于接近 J2EE EJB(企业 JavaBean)容器所提供的层。在 CORBA 规范中,服务上下文在 IIOP 级别(GIOP,或通用 Orb 间协议)直接得到支持,并与 ORB 运行时集成。然而,对于 RMI/IIOP,应用程序利用底层 IIOP 服务上下文支持并不容易,即使协议层确实有这种支持。同时,当使用 RMI/JRMP(Java 远程方法协议)时,这种支持不可用。因此,对于不使用或不必使用 ORB 或 EJB 容器环境的基于 RMI 的分布式应用程序,缺乏此类功能限制了可用的设计选择,尤其是在必须扩展现有应用程序以支持新的基础设施级功能时。由于组件之间的依赖关系以及对客户端应用程序的巨大影响,修改现有 RMI 接口通常被证明是不可取的。对这种 RMI 限制的观察导致了我在本文中描述的通用解决方案

尽管以上所有

该解决方案基于 Java 反射技术和一些实现拦截器的常用方法。更重要的是,它定义了一个可以轻松集成到任何基于 RMI 的分布式应用程序设计中的架构。

下面的解决方案通过一个示例实现来演示,该示例实现支持使用 RMI 透明传递事务上下文数据,例如事务 ID (xid)。该解决方案包含以下三个重要组件:

  • RMI远程接口命名-功能封装和拦截插件
  • 服务上下文传播机制和服务器端接口支持
  • 服务上下文数据结构和事务上下文传播支持

在此处输入图像描述

该实现假定使用了 RMI/IIOP。但是,这绝不意味着该解决方案仅适用于 RMI/IIOP。事实上,RMI/JRMP 或 RMI/IIOP 都可以用作底层 RMI 环境,如果命名服务同时支持这两种环境,甚至可以混合使用这两种环境

命名函数封装

首先,我们封装提供 RMI 远程接口查找的命名函数,允许透明地插入拦截器。这样的封装总是可取的,并且总是可以在大多数基于 RMI 的应用程序中找到。底层的命名解析机制在这里不是问题;它可以是任何支持 JNDI(Java 命名和目录接口)的东西。在这个例子中,为了使代码更具有说明性,我们假设所有服务器端远程 RMI 接口都继承自一个标记远程接口 ServiceInterface,该接口本身继承自 Java RMI Remote 接口。图显示了类图,后面是我将进一步描述的代码片段

在此处输入图像描述

RMI 调用拦截器

要启用调用拦截器,从 RMI 命名服务获取的原始 RMI 存根引用必须由本地代理包装。为了提供通用实现,使用 Java 动态代理 API 来实现这样的代理。在运行时,会创建一个代理实例;它实现了与包装的存根引用相同的 ServiceInterface RMI 接口。在第一次被拦截器处理之后,任何调用最终都会被委托给存根。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。再看看这里

于 2018-08-23T09:48:00.037 回答
1

我不知道您是否已经考虑过这一点,很适合 RMI 和事务边界的双重要求的一种可能性显然是 Spring。这里是 Spring Remoting 的一个示例。

@Transactional注释非常广泛地用于声明式事务管理 - Spring 将自动将您的 bean 包装在 AOP 事务顾问中。

然后,这两者将很好地结合在一起,例如使用可以作为远程服务导出的单个事务 bean。

上面的 Spring Remoting 链接基于 Spring Boot,这是一种简单的入门方式。默认情况下,Boot 会希望将嵌入式服务器引入环境,但可以禁用此功能。同样,还有其他选项,例如独立的AnnotationConfigApplicationContext,或WebXmlApplicationContext现有 Web 应用程序中的。

自然地,对于一个要求建议的问题,任何回复都会包含一些意见 - 如果很快没有看到其他建议,我会很失望。

于 2018-08-18T09:01:23.930 回答