简体   繁体   中英

Remote Method Invocation (RMI) - Force “Call-by-Value” on an exported object

For exam preparation I am writing a little program that uses RMI.

A data-collection on a RMI-server should be accessible to external clients. Clients should be able to "lock" and "unlock" the collection.

  • When the collection is unlocked , clients should receive a reference to the exported remote Data-object.
  • When the collection is locked clients should only receive a serialized copy (ie by-value) of said Data-object (thus the Data-interface extends both the Serializable and the Remote-Interfaces).

I have already got the following code, but I do not know how to force the serialization of the object (and then send it via RMI) inside the close() and get() methods. (Modifying the remote -object when the collection isOpen() works perfectly, though.) Any hints, thoughts, ideas?

Service-Object:

public interface Service extends Remote
{
    public Data open() throws RemoteException;
    public Data close() throws RemoteException;

    public Data get() throws RemoteException;

    public boolean isOpen() throws RemoteException;
}

public class ServiceImpl extends UnicastRemoteObject implements Service
{
    private boolean open;
    private DataImpl data;

    public ServiceImpl() throws RemoteException
    {
        open = true;
        data = new DataImpl();
    }

    @Override
    public synchronized Data open() throws RemoteException
    {
        open = true;

        //by-reference
        return data;
    }

    @Override
    public synchronized Data close() throws RemoteException
    {
        open = false;

        //force by-value
        //TODO: How to force serialization???
        return null;
    }

    @Override
    public synchronized Data get() throws RemoteException
    {
        if (open)
        {
            //by-reference
            return data;
        }
        else
        {
            //force by-value
            //TODO: How to force serialization???
            return null;
        }
    }

    @Override
    public synchronized boolean isOpen() throws RemoteException
    {
        return open;
    }
}

Data-Object

public interface Data extends Remote, Serializable
{
    public void append(String s) throws RemoteException;
    public ArrayList<String> getValues() throws RemoteException;

    public String getString() throws RemoteException;
}

public class DataImpl extends UnicastRemoteObject implements Data
{
    private ArrayList<String> data;

    public DataImpl() throws RemoteException
    {
        data = new ArrayList<String>();
    }

    @Override
    public synchronized void append(String s) throws RemoteException
    {
        data.add(s);
    }

    @Override
    public synchronized ArrayList<String> getValues() throws RemoteException
    {
        return data;
    }

    @Override
    public synchronized String getString() throws RemoteException
    {
        String s = "";

        for (String d : data)
        {
            s += d + ", ";
        }

        return s;
    }
}

(imports left out by intention)

UML

UML

There are a couple things going on here.

First, objects that are marshalled as part of an RMI request or response need to be serializable, and they are serialized and deserialized as part of the marshaling/unmarshaling process. However, if such an object implements Remote and is exported, its stub is marshaled instead of the object itself being serialized. This is sometimes the right thing and is sometimes quite counterintuitive.

Second, a common style that the code here exhibits is for Remote objects to extend UnicastRemoteObject . This is convenient, but the consequence of this technique is that every Remote instance is implicitly exported at construction time.

One thing you could do would be to unexport the data objects using UnicastRemoteObject.unexportObject() when the collection is locked and re-export them using exportObject() when the collection is unlocked. Unexporting the object will change the marshaling behavior so that the data objects are serialized instead of the remote stub being marshaled. I don't recommend this technique, though, because clients might have retrieved stubs and be issuing calls on them. If a call is received after the object is unexported, an error will occur, probably NoSuchObjectException .

What I'd recommend instead is to refactor your class hierarchy so the Data objects no longer extend UnicastRemoteObject . This allows you to construct Data objects at will and only selectively export the ones you want exported.

In particular, while the collection is locked, an incoming call on the Service could create copies of the current set of Data objects and return those. Since they won't be exported, they'll be marshaled and serialized and the copies sent across the wire. The original Data objects will remain exported, allowing them to continue to service clients that have remote references to them.

As an aside, I'm unsure of the semantics you're trying to achieve here. A client dealing with the Service can't tell whether it will get a remote reference or a local copy. Thus, any changes it makes to the data objects might update the remote (shared) copy or be stranded in a local copy. I'm also not sure what the result should be if a client has a remote reference to a data object on the server and then the server's collection is locked. Can the client still make updates to the data object on the server at this time?

In any case, explicitly controlling when the data objects are and are not exported will control whether serialized copies are marshaled instead of remote stubs.

As another aside, another reason not to have remote objects extend UnicastRemoteObject is that it unsafely publishes a reference to the remote object (in the sense of the Java Memory Model). The UnicastRemoteObject constructor exports the object, which means that it binds the object into the RMI object table creates the stub for it. Then the subclass constructor runs to initialize the object itself. Since the reference to this object has already been published, there is no happens-before relationship between the completion of the constructor and other threads reading of the values in the object. Thus, other threads -- possibly in response to remote calls -- can see stale or uninitialized values in the remote object. For this reason I prefer not to have remote objects extend UnicastRemoteObject , and instead construct remote objects and then export them explicitly.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM