【问题标题】:Expose as reference return value of service proxied by RmiServiceExporter (or alternative to RMI)公开为由 RmiServiceExporter(或 RMI 的替代方案)代理的服务的参考返回值
【发布时间】:2014-03-10 11:38:42
【问题描述】:

如果我通过 RMI 使用 RmiServiceExporter 导出服务,服务调用的返回值将被序列化并通过线路发送到客户端。但是除非我有一个简单的服务来返回简单的数据,否则这不是很有用。

我也希望能够在这个返回值上调用方法,并让它们在服务器上运行,而不是在客户端上运行。本质上,我想要在客户端创建的返回值的代理。有没有办法使用RmiServiceExporter(或 Spring 支持的其他远程处理选项)来做到这一点?


我尝试实现一些返回值的自动包装,这就是我想出的:

在服务器端:

class RmiReturnValueStubbingExporter extends RmiServiceExporter {

    /* we can't use the Spring implementation of RmiInvocationHandler
     * because it's package protected */
    private class RmiReturnValueInvocationWrapper implements RmiInvocationHandler {
        private final Object wrappedObject;

        private RmiReturnValueInvocationWrapper(Class interf, Object wrappedObject) {
            this.wrappedObject = createProxyFor(interf, wrappedObject);
        }

        @Override
        public String getTargetInterfaceName() throws RemoteException {
            return null;
        }

        @Override
        public Object invoke(RemoteInvocation invocation) throws
                RemoteException, NoSuchMethodException, IllegalAccessException,
                InvocationTargetException {
            return RmiReturnValueStubbingExporter.this.invoke(
                    invocation, this.wrappedObject);
        }
    }

    @Override
    protected Object invoke(RemoteInvocation invocation, Object targetObject)
            throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        Object ret = maybeWrap(super.invoke(invocation, targetObject));

        if (ret instanceof Remote) {
            try {
                UnicastRemoteObject.exportObject((Remote) ret, 0);
            } catch (RemoteException e) {
                throw new InvocationTargetException(e);
            }
        }

        return ret;
    }

    private Object maybeWrap(Object superValue) {
        if (superValue instanceof OntologyTerm) {
            return new RmiReturnValueInvocationWrapper(
                    OntologyTerm.class, superValue);
        } else if (superValue instanceof List) {
            return new RmiReturnValueInvocationWrapper(
                    List.class, superValue);
        } else {
            return superValue;
        }
    }

    private Object createProxyFor(Class interf, Object object) {
        ProxyFactory proxyFactory = new ProxyFactory();
        proxyFactory.addInterface(interf);
        proxyFactory.addAdvice(new RemoteInvocationTraceInterceptor(getExporterName()));

        proxyFactory.setTarget(object);
        // don't make opaque, on the client we need to know the interfaces implemented
        return proxyFactory.getProxy(getBeanClassLoader());
    }
}

在客户端:

class RmiStubReturnValueProxyFactoryBean extends RmiProxyFactoryBean {

    @Override
    protected Object doInvoke(MethodInvocation methodInvocation,
                              RmiInvocationHandler invocationHandler) {

        def superValue = super.doInvoke(methodInvocation, invocationHandler)

        if (superValue instanceof java.lang.reflect.Proxy &&
                java.lang.reflect.Proxy.getInvocationHandler(superValue)
                instanceof RemoteObjectInvocationHandler) {
            RmiInvocationHandler rih = superValue

            def proxiedInterfaces = rih.invoke(
                    new RemoteInvocation('getProxiedInterfaces',
                            [] as Class[], [] as Object[]))

            def clientProxy = new ProxyFactory(proxiedInterfaces)

            clientProxy.addAdvice({ MethodInvocation invoc ->
                doInvoke(invoc, rih)
            } as MethodInterceptor)

            clientProxy.getProxy beanClassLoader
        } else {
            superValue
        }
    }
}

这似乎适用于两层包装;在这种情况下,服务返回 List<OntologyTerm>。我怀疑这有很多问题,首先是调用UnicastRemoteObject.exportObject 而没有回收任何东西。此处可能还缺少的另一件事是处理这些存根的传递,这些存根将客户端作为参数传递给服务。因为参数是在客户端创建的 Proxy 对象(包装 RMI 存根),我猜至少服务器必须从客户端加载类,这是我想避免的。

我的问题是,是否已经实现了类似的东西(不一定使用 RMI),我可以在其中轻松指定哪些类型作为值返回并序列化,以及哪些类型的返回值应该被存根。

【问题讨论】:

  • 这就是 EJB 用于 @Remote 接口的用途。您正在尝试重新发明轮子。
  • @M.Deinum 我希望我在重新发明轮子,但我不知道 EJB 是否是正确的答案。我的服务器是一个在 tomcat 中运行的 webapp(没有 Java EE 堆栈),客户端应用程序应该是一个可以从其他通用 Java SE 应用程序中使用的库,这些应用程序肯定不会在应用程序服务器上运行。
  • 所以基本上你想要 EJB 可以给你的东西,但不能使用 EJB,所以你正在拼凑一些可能有用的东西。默认的RmiServiceExporter(以及所有其他远程解决方案)仅序列化方法调用并序列化该调用的结果。如果这不是您想要的,请不要使用 Springs 远程处理技术,而是使用普通 RMI 来获得正常的远程处理行为。
  • @M.Deinum 我可以这样做,但如果有替代方案我不需要使用 RMI 特定接口和逻辑破坏我的代码,我会非常喜欢。这就是首先吸引我RmiServiceExporter 的原因。

标签: java spring rmi


【解决方案1】:

这是一个古老的问题,起源于 CORBA、Jini 和 EJB 的第一个版本等技术。这个想法大多被放弃了,因为它根本无法很好地扩展。

由于网络开销,不能将远程对象透明地视为本地对象。由于网络延迟,当调用远程对象时,您希望在远程调用中打包最多的数据, 而在本地对象中,您更喜欢尽可能地保持方法调用的最细粒度,以便从 OO 的角度来看它们是最可重用的。

方法可重用性和低网络开销这两个要求冲突,最终结果是远程对象的 API 总是与本地对象的 API 大不相同, 即使存在使对远程对象的调用完全透明的技术。

现在广泛采用的解决方案是通过网络发送DTOs (Data Transfer Objects),它们是纯数据对象。这可以在 RMI、HTTP (REST)、SOAP 或其他协议中完成。

然后在每一端都可以对域模型进行一些映射,以便可以在其中编写服务和持久层。另一方面,DTO 模型并不意味着跨越表示层。

【讨论】:

    【解决方案2】:

    我的问题是是否已经实现了类似的东西(不是 必须使用 RMI),我可以在其中轻松指定哪些类型是 作为值返回并序列化以及返回值的类型 应该被存根。

    首先我要建议的是你应该摆脱RMIRMI 是相当低的水平,当你考虑它的时候。 你为什么会一直回到 CORBA? 对此的唯一答案可能是你不需要 Java EE EJB 应用服务器。 但你必须保持客户端和服务器 JVM 同步。不升级服务器就不会升级客户端。您必须编写 EJB 应用服务器为您提供的所有服务(例如,连接池、命名和目录服务、池、请求队列、事务等)。

    EJB 与 RMI

    EJB 构建在 RMI 之上。两者都暗示 Java 客户端和 bean。如果您的客户端需要使用其他语言(例如 .NET、PHP 等)编写,请使用 Web 服务或其他与平台无关的有线协议,例如 HTTP 或 XML over HTTP 或 SOAP。

    Spring 与 EJB

    EJB 3.0 与 Spring 的更好选择取决于您是否喜欢 POJO 开发,是否希望选择 ORM 和 JPA 之外的关系技术等。 EJB 3.0 的优势在于 EJB 3.0 是一个有许多供应商的规范; Spring 只能从 Spring Source 获得。而 Spring 非常稳固,有很大的牵引力,不会去任何地方。它使您的所有选择都保持开放。

    网络服务

    Spring 的 Web 服务模块在 POJO 服务接口方面非常灵活和精彩。这些将允许您获得所需的概念隔离,将部署选择推迟到最后一刻,如果第一个想法表现不佳,您可以改变主意。 Web 服务在理论上很棒,但您需要注意一些问题:

    • 延迟。福勒的分布式对象第一定律:“不要!”由许多细粒度的分布式 SOAP 服务组成的架构将优雅、美观且像糖蜜一样缓慢。分发前请仔细考虑。
    • 从 XML 编组到对象并返回会消耗 CPU 周期,除了允许您的客户使用与平台无关的协议之外,这些周期没有提供任何业务价值。
    • SOAP 是一个每天都变得越来越臃肿和复杂的标准,但它有很多工具支持。供应商喜欢它,因为它有助于推动 ESB 的销售。 REST 很简单,但没有那么好理解。工具不支持。

    最后,我会推荐使用 Spring Web Services

    【讨论】:

      【解决方案3】:

      如果任何远程方法的返回值是一个导出的远程对象,它将作为其存根编组到客户端。

      【讨论】:

      • 谢谢,但即使是这样,也对我没有帮助。我希望能够作为存根发送给客户端的对象数量是无限的。它们是从显式导出的服务创建的。我想要的是能够配置将被存根的类型并为我存根该类型的所有对象,而无需在每个对象的基础上指定它。否则,整个模型真的把我推向了一个对 OOP 来说很奇怪的模型,服务返回的对象不能真正有任何有趣的行为。
      • 我不明白。行为是自动的。无需配置。您所要做的就是从相关方法返回 new MyRemoteObjectImpl()。
      • 我没有直接实现Remote(并且返回值也没有实现Remote)。我只是用RmiServiceExporter 包装我的服务,它负责处理RMI 细节。所以这可能更像是一个春季问题。
      猜你喜欢
      • 2023-04-07
      • 2019-11-11
      • 2011-08-12
      • 2012-12-06
      • 1970-01-01
      • 2014-03-31
      • 1970-01-01
      • 2010-09-07
      • 1970-01-01
      相关资源
      最近更新 更多