Expose as reference return value of service proxied by RmiServiceExporter (or alternative to RMI)

StackOverflow https://stackoverflow.com/questions/22299077

  •  12-06-2023
  •  | 
  •  

Question

If I export a service via RMI with RmiServiceExporter, the return values of the service calls are serialized and sent over the wire to the client. But unless I have a simple service that returns simply data, this is not very useful.

I want to be able to call methods on this return value as well, and have them run on the server, not on the client. Essentially, I want a proxy to the return value to created on the client. Is there any way to do this with RmiServiceExporter (or another remoting option supported by Spring)?


I tried to implement some auto wrapping of return values, and this is what I came up with:

On the server side:

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());
    }
}

and on the client side:

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

This seems to work fine with two levels of wrapping; in this case the service returning a List<OntologyTerm>. I suspect this has quite some problems, first of all the call UnicastRemoteObject.exportObject without ever reclaiming anything. Another things that's maybe also missing here is handling the passing of these stubs the clients gets as arguments to the services. Because the arguments are Proxy objects created on the client (wrapping the RMI stub), I guess that at the very least the server will have to load classes from the client, which is something I'd like to avoid.

My question is if there's something like this already implemented (not necessarily with RMI), where I can easily specify what types are returned as a value and serialized and which types of return values should be stubbed.

Was it helpful?

Solution

This is an ancient problem that originated technologies like CORBA, Jini and the first versions of EJB. This idea is mostly abandoned as it simply does not scale well.

A remote object cannot be treated transparently as a local object due to the network overhead. Due to the network latency, when calling a remote object you want to pack the most data in a remote call, while in a local object you prefer to keep the method calls the most fine-grained possible, so that they can be the most reusable from a OO point of view.

These two requirements of method reusability and low network overhead conflict and the end result is that the API of a remote object is always going to be very different than the API of a local object, even if the technology existed to make calls to remote objects completelly transparent.

The solution that is nowadays widelly adopted is to send DTOs (Data Transfer Objects) over the wire, which are data only objects. This can be done in RMI, HTTP (REST), SOAP or other protocols.

Then at each end some mapping can be made to the domain model, so that the service and persistence layer can be written in it. The DTO model on the other hand is not meant to cross the presentation layer.

OTHER TIPS

My question is if there's something like this already implemented (not necessarily with RMI), where I can easily specify what types are returned as a value and serialized and which types of return values should be stubbed.

First and foremost thing which I would like to suggest is that you should get rid of RMI. RMI is quite low level when you think about it. Why would you drop all the way back to CORBA? Only answer to this may be that you don't need a Java EE EJB app server. But you have to keep client and server JVMs in sync. You don't upgrade the client without upgrading the server. You have to write all the services that the EJB app server provides for you (e.g., connection pooling, naming and directory services, pooling, request queuing, transactions, etc.).

EJB vs RMI

EJBs are built on top of RMI. Both imply Java clients and beans. If your clients need to be written in something else (e.g., .NET, PHP, etc.) go with web services or something else that speaks a platform-agnostic wire protocol, like HTTP or XML over HTTP or SOAP.

Spring vs EJB

A better choice in EJB 3.0 versus Spring depends on whether you like POJO development, want a choice of relational technologies besides ORM and JPA, among other things. Advantage of EJB 3.0 is that EJB 3.0 is a spec with many vendors; Spring can only be had from Spring Source. Whereas Spring is very solid, has lots of traction, isn't going anywhere. It leaves all your options open.

WebServices

Spring's web service module is very flexible & wonderful in terms of POJO service interfaces. These will allow you to get the conceptual isolation you want, defer the deployment choice until the last moment, and let you change your mind if the first thought doesn't perform well. Web services are great in theory, but there are some gotchas that you need to watch out for:

  • Latency. Fowler's first law of distributed objects: "Don't!" An architecture consisting of lots of fine-grained distributed SOAP services will be elegant, beautiful, and slow as molasses. Think carefully before distributing.
  • Marshalling from XML to objects and back consumes CPU cycles that aren't providing any business value besides allowing your clients to speak a platform-agnostic protocol.
  • SOAP is a standard that is becoming more bloated and complex every day, but it has lots of tool support. Vendors like it because it helps drive sales of ESBs. REST is simple but not as well understood. It's not supported by tools.

In the end, I would recommend to use Spring Web Services

If the return value of any remote method is an exported remote object, it is marshalled to the client as its stub.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top