【问题标题】:Common transaction-logic in java RMI-service?java RMI服务中的常见事务逻辑?
【发布时间】:2018-08-14 08:20:12
【问题描述】:

我们有几个带有 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 调用以实现事务逻辑?

【问题讨论】:

  • 您的事务控制(所需)在服务器端还是客户端?
  • 它应该在服务器端。每个 api 调用都是一个事务,应该在成功返回时提交,或者在抛出异常时回滚。这就是 JRMP 容器当前对每个调用所做的事情。

标签: java jdbc rmi rmic jrmp


【解决方案1】:

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

example code here

解释在项目 README 中。

【讨论】:

    【解决方案2】:

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

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

    一般来说:

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

    尽管如此

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

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

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

    该实现假定使用了 RMI/IIOP。然而,它并没有 表示该解决方案仅适用于 RMI/IIOP。事实上,无论是 RMI/JRMP 或 RMI/IIOP 可用作底层 RMI 环境, 如果命名服务,甚至是两种环境的混合 两者都支持

    命名函数封装

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

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

    您可以在以下指南here 中找到更多详细信息。如果你想从头开始,理解以上所有这些是很重要的,与 Spring-AOP Framework 健壮的解决方案相比。此外,我认为你会发现用于实现 an RmiClientInterceptor 的 Spring Framework 纯源代码非常有趣。再看看here

    【讨论】:

      【解决方案3】:

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

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

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

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

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

      【讨论】:

      • 你是对的!这是一个很好且强大的解决方案!还有其他一些,例如用于客户端控制的两阶段提交 (2PC) 或用于服务器端的分布式事务管理器 (DTM)(由 Spring 在 JTA 上实现)。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-12-24
      • 1970-01-01
      • 1970-01-01
      • 2017-08-08
      • 2016-11-06
      相关资源
      最近更新 更多