远程方法调用(RMI) - 强制导出的对象上的“按值调用”

时间:2014-09-04 16:01:21

标签: java serialization interface export rmi

为了准备考试,我正在编写一个使用RMI的小程序。

外部客户端应该可以访问RMI服务器上的数据收集。客户应该能够锁定"并且"解锁"集合。

  • 当收集解锁时,客户应该会向导出的远程数据对象收到引用
  • 当集合已锁定时,客户端应仅接收所述Data对象的序列化副本(即按值)(因此数据接口扩展了Serializable和远程接口)。

我已经得到了以下代码,但我不知道如何强制对象的序列化(然后通过RMI发送)在close()和get()方法中。 (但是,当集合isOpen()完美运行时,修改远程 - 对象。)任何提示,想法,想法?

服务 - 对象:

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

数据-对象

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

(意图遗漏进口)

UML

UML

1 个答案:

答案 0 :(得分:2)

这里有几件事情。

首先,作为RMI请求或响应的一部分编组的对象需要是可序列化的,并且它们作为编组/解组过程的一部分被序列化和反序列化。但是,如果此类对象实现Remote并导出,则其存根将被封送,而不是被序列化的对象本身。这有时是正确的,有时甚至是违反直觉的。

其次,此处代码展示的一种常见样式是Remote个对象扩展UnicastRemoteObject。这很方便,但这种技术的结果是每个Remote实例在构造时都隐式导出。

您可以做的一件事是在锁定集合时使用UnicastRemoteObject.unexportObject()取消导出数据对象,并在解锁集合时使用exportObject()重新导出它们。取消导出该对象将更改编组行为,以便序列化数据对象而不是正在封送的远程存根。但是,我并不推荐这种技术,因为客户端可能已检索到存根并在其上发出调用。如果在取消导出对象后收到调用,则会发生错误,可能是NoSuchObjectException

我建议改为重构您的类层次结构,以便Data个对象不再扩展UnicastRemoteObject。这允许您随意构造Data个对象,并且只选择性地导出您想要导出的对象。

特别是,当集合被锁定时,Service上的来电可以创建当前Data个对象的副本并返回它们。由于它们不会被导出,因此它们将被编组和序列化,并且副本通过网络发送。原始Data对象将保持导出状态,允许它们继续为具有远程引用的客户端提供服务。

顺便说一句,我不确定你在这里尝试实现的语义。处理Service的客户无法判断它是获取远程引用还是本地副本。因此,它对数据对象所做的任何更改都可能会更新远程(共享)副本或被搁置在本地副本中。如果客户端对服务器上的数据对象有远程引用,然后服务器的集合被锁定,我也不确定结果应该是什么。客户端此时是否仍然可以对服务器上的数据对象进行更新?

在任何情况下,显式控制何时导出数据对象将控制序列化副本是否被封送而不是远程存根。

另外,另一个没有远程对象扩展UnicastRemoteObject的原因是它不安全地发布对远程对象的引用(在Java内存模型意义上)。 UnicastRemoteObject构造函数导出对象,这意味着它将对象绑定到RMI对象表中为它创建存根。 然后子类构造函数运行以初始化对象本身。由于已经发布了对该对象的引用,因此在构造函数完成和其他线程读取对象中的值之间没有发生在之前的关系。因此,其他线程 - 可能响应远程调用 - 可以在远程对象中看到陈旧或未初始化的值。出于这个原因,我不希望远程对象扩展UnicastRemoteObject,而是构造远程对象,然后显式导出它们。