讨论开始之前,我们先看看网上的一个例子,这个例子我腾抄了一分,没有用链接的方式,只是为了让大家看得方便,如有侵权,我立马***。

  1. 定义远程接口:

  2. 1
    2
    3
    4
    5
    6
    package com.guojje;
    import java.rmi.Remote;
    import java.rmi.RemoteException;
    public interface IHello extends Remote {
        public int helloWorld()throws RemoteException;
    }

3. 定义实现类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.guojje;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
 
public class Hello extends UnicastRemoteObject implements IHello {
    private static final long serialVersionUID = 1L;
    private int index = 0;
    protected Hello() throws RemoteException {
    }
    @Override
    public int helloWorld(){
        System.out.println("Hello!");
        return ++index;
    }
}

4.服务端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.guojje;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
 
public class HelloServer {
    public static void main(String args[]) {
        try {
            IHello rhello = new Hello();
            Registry registry = LocateRegistry.createRegistry(8888);
            registry.bind("test", rhello);
            System.out.println("Remote Hello Object is bound sucessfully!");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

5.客户端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.guojje;
import java.rmi.Naming;
public class HelloClient {
    public static void main(String args[]) {
        try {
            for (int i = 0; i < 5; i++) {
                IHello rhello = (IHello) Naming
                        .lookup("rmi://localhost:8888/test");
                System.out.println(rhello.helloWorld());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

6.输出结果:

1)服务端输出:

Remote Hello Object is bound sucessfully!

Hello!

Hello!

Hello!

Hello!

Hello!

2)客户端输出:

0

1

2

3

4

7.把实现类更改为不继承UnicastRemoteObject基类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.guojje;
import java.io.Serializable;
import java.rmi.RemoteException;
 
public class Hello implements IHello,Serializable {
    private static final long serialVersionUID = 1L;
    private int index = 0;
    protected Hello() throws RemoteException {
    }
    @Override
    public int helloWorld(){
        System.out.println("Hello!");
        return ++index;
    }
}

8.输出结果:

1)服务端输出:

Remote Hello Object is bound sucessfully!

2)客户端输出:

Hello!

1

Hello!

1

Hello!

1

Hello!

1

Hello!

1

这两个用例的输出结果来看,前一个用例index计数器一直在增加,且Hello!输出在服务端。这说明

helloWorld方法执行是在服务端,客户端的每一次对象方法调用,都是对服务端对象的调用。

而后一个用例就不同了。helloWorld方法执行是在客户端,且每次lookup出来的都是新的对象。

我们看一下lookup出来的对象类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.guojje;
import java.rmi.Naming;
public class HelloClient {
    public static void main(String args[]) {
        try {
            for (int i = 0; i < 5; i++) {
                IHello rhello = (IHello) Naming
                        .lookup("rmi://localhost:8888/test");
                System.out.println(rhello.getClass());
                System.out.println(rhello.helloWorld());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

实现类继承UnicastRemoteObject时,lookup出来的对象类型是$Proxy0,而不继承UnicastRemoteObject的类,对象类型是com.guojje.Hello。

我们把继承UnicastRemoteObject类的对象叫做远程对象,我们lookup出来的对象,只是该远程对象的存根(Stub)对象,而远程对象永远在远方。客户端每一次的方法调用,最后都是仅有的那一个远程对象的方法调用。

没有继承UnicastRemoteObject类的对象,同样可以bind到Registry,但lookup出来了对象却是远程对象

经过序列化,然后到客户端反序列化出来的新的对象,后续的方法调用与远程对象再无关系。

那UnicastRemoteObject类的继承是如何影响这些的呢? 我们来探索一下。

1
2
3
4
5
6
7
8
9
10
package com.guojje;
public class HelloServer {
    public static void main(String args[]) {
        try {
            new Hello();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

仅仅new一个远程对象,运行这个程序,我们就发现进程不会退出。它hang在哪儿呢?

jstack查看线程栈,发现有SocketAccept在监听:

的确启动了一个监听端口。ServerSocket类上添加debug.得到调用栈如下:

UnicastRemoteObject基类的构造方法将远程对象发布到一个随机端口上,当然端口也可以指定。

1
2
3
4
5
 protected UnicastRemoteObject(int port) throws RemoteException
    {
        this.port = port;
        exportObject((Remote) this, port);
    }
1
2
3
4
5
  public static Remote exportObject(Remote obj, int port)
        throws RemoteException
    {
        return exportObject(obj, new UnicastServerRef(port));
    }
1
2
3
4
5
6
7
8
9
10
11
12
 /**
     * Exports the specified object using the specified server ref.
     */
    private static Remote exportObject(Remote obj, UnicastServerRef sref)
        throws RemoteException
    {
        // if obj extends UnicastRemoteObject, set its ref.
        if (obj instanceof UnicastRemoteObject) {
            ((UnicastRemoteObject) obj).ref = sref;
        }
        return sref.exportObject(obj, null, false);
    }

迎来一个重要的方法(UnicastServerRef.java):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
 /**
     * Export this object, create the skeleton and stubs for this
     * dispatcher.  Create a stub based on the type of the impl,
     * initialize it with the appropriate remote reference. Create the
     * target defined by the impl, dispatcher (this) and stub.
     * Export that target via the Ref.
     */
    public Remote exportObject(Remote impl, Object data,
                               boolean permanent)
        throws RemoteException
    {
        Class implClass = impl.getClass();
        Remote stub;
 
        try {
            stub = Util.createProxy(implClass, getClientRef(), forceStubUse);
        } catch (IllegalArgumentException e) {
            throw new ExportException(
                "remote object implements illegal remote interface", e);
        }
        if (stub instanceof RemoteStub) {
            setSkeleton(impl);
        }
 
        Target target =
            new Target(impl, this, stub, ref.getObjID(), permanent);
        ref.exportObject(target);
        hashToMethod_Map = hashToMethod_Maps.get(implClass);
        return stub;
    }

这里的stub变量就是我们lookup出来的远程对象的存根对象。而target保留了远程对象的信息集合:

对象,存根,objId等。

接着看TCPTransport.java的exportObject方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
  /**
     * Export the object so that it can accept incoming calls.
     */
    public void exportObject(Target target) throws RemoteException {
        /*
         * Ensure that a server socket is listening, and count this
         * export while synchronized to prevent the server socket from
         * being closed due to concurrent unexports.
         */
        synchronized (this) {
            listen();
            exportCount++;
        }
 
        /*
         * Try to add the Target to the exported object table; keep
         * counting this export (to keep server socket open) only if
         * that succeeds.
         */
        boolean ok = false;
        try {
            super.exportObject(target);
            ok = true;
        } finally {
            if (!ok) {
                synchronized (this) {
                    decrementExportCount();
                }
            }
        }
    }

listen()启动监听。这里对TcpTransport加了同步,防止多个线程同时执行,同时也防止同一端口启动多次。

一次TcpTransport会有一个监听端口,而一个端口上可能会发部多个远程对象。exportCount计录该TcpTransport发布了多少个对象。

1
super.exportObject(target);
1
2
3
4
5
6
7
/**
     * Export the object so that it can accept incoming calls.
     */
    public void exportObject(Target target) throws RemoteException {
        target.setExportedTransport(this);
        ObjectTable.putTarget(target);
    }

ObjectTable为静态方法调用,那么所有的远程对象信息(target)都会放到这个对象表中。我们这个target包含了远程对象几乎所有的信息,找到他,就找到远程对象的存根对象。

远程对象的发布,先说这么多。我们再看一下lookup是如何找到远程对象的存根的.

当然先从远程对象的bind说起:

RegistryImpl.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 /**
     * Binds the name to the specified remote object.
     * @exception RemoteException If remote operation failed.
     * @exception AlreadyBoundException If name is already bound.
     */
    public void bind(String name, Remote obj)
        throws RemoteException, AlreadyBoundException, AccessException
    {
        checkAccess("Registry.bind");
        synchronized (bindings) {
            Remote curr = bindings.get(name);
            if (curr != null)
                throw new AlreadyBoundException(name);
            bindings.put(name, obj);
        }
    }

bindings里放得确实是远程对象本身,而不是他的存根。这是怎么回事?继续追究lookup方法。

RegistryImpl_Skel类是rmic生成所有没有源代码,我们只能从反编译的代码中查看一点信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
case 2: // lookup(String)
    {
        java.lang.String $param_String_1;
        try {
        java.io.ObjectInput in = call.getInputStream();
        $param_String_1 = (java.lang.String) in.readObject();
        } catch (java.io.IOException e) {
        throw new java.rmi.UnmarshalException("error unmarshalling arguments", e);
        } catch (java.lang.ClassNotFoundException e) {
        throw new java.rmi.UnmarshalException("error unmarshalling arguments", e);
        } finally {
        call.releaseInputStream();
        }
        java.rmi.Remote $result = server.lookup($param_String_1);
        try {
        java.io.ObjectOutput out = call.getResultStream(true);
        out.writeObject($result);
        } catch (java.io.IOException e) {
        throw new java.rmi.MarshalException("error marshalling return", e);
        }
        break;
    }

从网络流中读取第一个参数必须是lookup的字符串参数。然后调用服务端RegistryImpl对象lookup

方法,该方法返回的的确是远程对象,而非远程对象的存根呀?

问题的原因在于下面的序列化过程,继续看调用栈:

看一个重要方法MarshalOutputStream.java

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
     * Checks for objects that are instances of java.rmi.Remote
     * that need to be serialized as proxy objects.
     */
    protected final Object replaceObject(Object obj) throws IOException {
        if ((obj instanceof Remote) && !(obj instanceof RemoteStub)) {
            Target target = ObjectTable.getTarget((Remote) obj);
            if (target != null) {
                return target.getStub();
            }
        }
        return obj;
    }

如果对象是java.rmi.Remote实例,则向对象表中找到该对象的Target,如果存在,则返回其存根对象。

所以返回服务端的变成了远程对象的存根。先到此,后续再探索其方法调用,安全等相关问题。

补充:

其实Registry的实现类RegistryImpl也是个远程对象,这里registry.bind却没有进行远程调用。这是因为我是用LocateRegistry.createRegistry(8888)创建的远程对象,而不是通过LocateRegistry.getRegistry(8888)获取的远程对象,而getRegistry返回的是RegistryImpl的Stub.仅此而已。

另外一次RMI调用,要创建两个连接,一个连接面向注册端口,即上面的8888端口。另一个面向服务端口。在这里这个服务端口是随机的。最后绑定在UnicastServerRef对象中,序列化到客户端。

本文转自 anranran 51CTO博客,原文链接:http://blog.51cto.com/guojuanjun/1423392

RMI原理揭秘之远程对象的更多相关文章

  1. RMI原理及简单示例

    分布式对象 在学习 RMI 之前,先来分布式对象(Distributed Object):分布式对象是指一个对象可以被远程系统所调用.对于 Java 而言,即对象不仅可以被同一虚拟机中的其他客户程序( ...

  2. java RMI原理详解

    java本身提供了一种RPC框架——RMI(即Remote Method Invoke 远程方法调用),在编写一个接口需要作为远程调用时,都需要继承了Remote,Remote 接口用于标识其方法可以 ...

  3. RMI原理

    一.分布式对象 在学习 RMI 之前,先来分布式对象(Distributed Object):分布式对象是指一个对象可以被远程系统所调用.对于 Java 而言,即对象不仅可以被同一虚拟机中的其他客户程 ...

  4. RMI原理及简单demo

    1 简介 RMI是远程方法调用的简称,它能够帮助我们查找并执行远程对象的方法.通俗地说,远程调用就象将一个class放在A机器上,然后在B机器中调用这个class的方法. 2 概念 其他机器需要调用的 ...

  5. 使用 RMI 实现方法的远程调用

    RMI 介绍 RMI 指的是远程方法调用 (Remote Method Invocation).它是一种机制,能够让在某个 Java虚拟机上的对象调用另一个 Java 虚拟机中的对象上的方法.可以用此 ...

  6. .Net Remoting 调用远程对象

    根据需求,我们的系统必须以C/S方式构建,而且是三层架构,这样一来,就出现了服务器端和客户端通信的问题. 为了解决双方的通信问题,还要考虑效率.性能等方面,经过分析.试验,我们根据效率.移植.开发难易 ...

  7. (译) Angular运行原理揭秘 Part 1

    当你用AngularJS写的应用越多, 你会越发的觉得它相当神奇. 之前我用AngularJS实现了相当多酷炫的效果, 所以我决定去看看它的源码, 我想这样也许我能知道它的原理. 下面是我从源码中找到 ...

  8. 【转】Angular运行原理揭秘 Part 1

    当你用AngularJS写的应用越多, 你会越发的觉得它相当神奇. 之前我用AngularJS实现了相当多酷炫的效果, 所以我决定去看看它的源码, 我想这样也许我能知道它的原理. 下面是我从源码中找到 ...

  9. VisualVM 使用 service:jmx:rmi:///...无法连接linux远程服务器

    VisualVM 无法使用 service:jmx:rmi:///jndi/rmi:///jmxrmi 连接到 关闭远程机器的防火墙即可:service iptables stop 不关闭防火墙的解决 ...

随机推荐

  1. SpringCloud(二)之我学 Ribbon

    1.负载均衡 Ribbon 虽然不是显示的配置为一个子项目,但是无论是在 API 网关的转发请求,还是服务之间的调用 Feign ,都是通过 Ribbon 来做负载均衡的. 负载均衡,主要是为了对系统 ...

  2. Jenkins Pipeline 持续集成

    Jenkins Pipeline 持续集成 Pipeline Script 执行流程 在使用Pipeline之前请确保Jenkins是2.x版本以上,并且安装了Pipeline插件. Jenkins提 ...

  3. 适用于 Mpvue 的微信小程序富文本解析自定义组件

    废话不多说,直接上方法: 首先 npm 安装 mpvue-wxparse npm i mpvue-wxparse 接下来:使用 <template> <div> <wxP ...

  4. Linux 压缩备分篇(一 备份数据)

    备份文件                dump dump: -S                    仅列出待备份数据需要多少磁盘空间才能够备份完毕 -u                    将 ...

  5. 9.1 ArrayList(集合)的使用,与array(数组)的对比

    1.array 和ArrayList的区别? array 数组的长度是固定的,适应不了变化的需求. ArrayList集合的长度可变.大小可变. 2.为什么要用集合,它优点是什么? java是面向对象 ...

  6. GitHub搭建个人主页

    GitHub搭建个人主页 1.注册登录GitHub 2.新建仓库 新建一个名为"username.github.io",其中username为你的用户名,仓库必须为公有类型,私有仓 ...

  7. CocoaPods应用于iOS项目框架管理方案

  8. AJ学IOS(04)UI之半小时搞定Tom猫

    AJ分享 必须精品  效果图 曾经风靡一时的tom猫其实制作起来那是叫一个相当的easy啊 功能全部实现,(关键是素材,没有素材的可以加我微信) 新手也可以很快的完成tom这个很拉轰的ios应用哦 然 ...

  9. 1 - Apache HttpClient 简单使用

    Apache HttpClient 是Apache 开源的实现Http协议的java开源库. HttpClien 是客户端的HTTP通信实现库,实现HTTP GET 和POST请求,获取响应内容. A ...

  10. 复习python的__call__ __str__ __repr__ __getattr__函数 整理

    class Www: def __init__(self,name): self.name=name def __str__(self): return '名称 %s'%self.name #__re ...