RMI服务启动原理

上一篇我们简单了解了RPC及其概念,并且实现了RMI框架进行服务发布,并且用系统自带的注册中心和代
码启动注册中心的方式将服务注册到了注册中心中,并且实现了两种方式的服务消费,那么我们不禁好奇,
这个由JDK官方早期就开发简易实现的RPC框架,如何实现的服务注册?注册中心启动过程是如何做的?如
何实现的服务消费?那么带着这些疑问,开启本篇文章的学习之旅

RegistryImpl服务端启动原理

还记得上篇我们手动启动RMI的注册中心的时候编写的代码吗?

Registry registry = LocateRegistry.createRegistry(9999);

没错就是这一句,经过上篇文章学习,我们知道createRegistry调用以后会启动一个RMI的注册中心,那么我们就从这个方法开始剖析吧:

public static Registry createRegistry(int port) throws RemoteException {
        return new RegistryImpl(port);
}

从这里可以看出来createRegistry方法其实内部创建了一个RegistryImpl

public RegistryImpl(final int var1) throws RemoteException {
        //如果端口为默认的1099并且java安全管理器不为空
        if (var1 == 1099 && System.getSecurityManager() != null) {
            try {
                AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() {
                    public Void run() throws RemoteException {
                        LiveRef var1x = new LiveRef(RegistryImpl.id, var1);
                        //向上转型将ref引用对象构建并且传递给setup方法
                        RegistryImpl.this.setup(new UnicastServerRef(var1x));
                        return null;
                    }
                }, (AccessControlContext)null, new SocketPermission("localhost:" + var1, "listen,accept"));
            } catch (PrivilegedActionException var3) {
                throw (RemoteException)var3.getException();
            }
        } else {
            LiveRef var2 = new LiveRef(id, var1);
            this.setup(new UnicastServerRef(var2));
        }

    }

我们可以看到根据端口号创建了ref引用对象,并且传递进了setup方法,我们来看卡setup方法:

private void setup(UnicastServerRef var1) throws RemoteException {
        this.ref = var1;//传来的ref引用
        var1.exportObject(this, (Object)null, true);//ref对象调用了exportObject方法
    }

setup方法里面调用了ref引用的exportObject方法,并且传递了三个参数,这里的三个参数分别为Remote类型实例、Object类型参数、以及一个布尔值参数,这里的Remote类型的实例在上篇我们曾介绍过,此类接口的实例都是RMI的远程对象,那么我们来看看exportObject方法做了什么操作:

public Remote exportObject(Remote var1, Object var2, boolean var3) throws RemoteException {
        //获取远程对象的class
        Class var4 = var1.getClass();

        Remote var5;
        try {
            //通过代理的方式创建ref引用
            var5 = Util.createProxy(var4, this.getClientRef(), this.forceStubUse);
        } catch (IllegalArgumentException var7) {
            throw new ExportException("remote object implements illegal remote interface", var7);
        }
        //如果创建出来的引用属于RemoteStub类及其子类,调用setSkeleton方法
        if (var5 instanceof RemoteStub) {
            this.setSkeleton(var1);
        }
        //根据传递进来的远程对象、当前类实例、当前ref引用的id以及传递的boolean参数来创建出Target实例,并再次传递到exportObject方法中
        Target var6 = new Target(var1, this, var5, this.ref.getObjID(), var3);
        this.ref.exportObject(var6);
        this.hashToMethod_Map = (Map)hashToMethod_Maps.get(var4);
        return var5;
    }

这里我们可以注意到两个重要的点,其中一个就是当代理引用类型属于RemoteStub及其子类的时候,会将代理传递给setSkeleton方法,而setSkeleton方法仅仅是校验当前代理的class是否存在,不存在将会创建一个Skeleton,即

this.setSkeleton(var1);

而另外一个点即为通过代理等参数合集创建出Target实例后,再次将Target实例传入ref引用实例的exportObject方法,即

this.ref.exportObject(var6);//这里的ref就是我们上一步setUp方法传递来的LiveRef实例

到目前为止依然都是RMI的赋值和创建工作,接下来开始我们就会进入到连接层相关的内容了,接着我们开始 LiveRef类的exportObject方法,代码如下:

public void exportObject(Target var1) throws RemoteException {
        this.ep.exportObject(var1);
    }

这里可以看到内部调用了ep的exportObject方法,这个ep对象则是LiveRef对象构造的时候传递来的Endpoint实例对象,那么初始化是什么时候调用的呢?还记得RegistryImpl类的构造方法吗?内部实现了LiveRef类的创建并且传递给UnicastServerRef类进行包装将包装后的UnicastServerRef类实例传递到setUp方法中,即:

 LiveRef var1x = new LiveRef(RegistryImpl.id, var1);
 RegistryImpl.this.setup(new UnicastServerRef(var1x));

默认的Endpoint接口的实现类为TCPEndpoint,而TCPEndpoint类的exportObject方法内部又调用了自身的transport对象实例的exportObject方法,我们跟进transport类(TCPTransport)的exportObject方法,代码如下:

public void exportObject(Target var1) throws RemoteException {
        synchronized(this) {
            this.listen();//开启监听,根据传递来的port,创建sokect连接,创建TCPTransport实例
            ++this.exportCount;
        }

        boolean var2 = false;
        boolean var12 = false;

        try {
            var12 = true;
            super.exportObject(var1);//调用父类的exportObject方法
            var2 = true;
            var12 = false;
        } finally {
            if (var12) {
                if (!var2) {
                    synchronized(this) {
                        this.decrementExportCount();//释放一个exportCount,并且关闭对应的socket连接
                    }
                }

            }
        }

        if (!var2) {
            synchronized(this) {
                this.decrementExportCount();
            }
        }

    }

可以看到这里就是真正的创建Target对象暴露出去,调用TCPTransport的listen()方法,listen()方法创建了一个ServerSocket,并且启动了一条线程等待客户端的请求。接着调用父类Transport的exportObject()将Target对象存放进ObjectTable中 ,至此我们已经成功的将RegistryImpl 对象创建并且监听等待来自客户端的请求

RMI服务端绑定原理

现在我们已经创建好了RMI的注册中心,是该创建服务端绑定到注册中心了

Naming.rebind("user", userHandler);

我们通过Naming类的方式进行绑定,这样我们可以顺便看看如何调用第二种启动方式的,即:

registry.rebind("user", userHandler);

我们进入Naming类的rebind方法,代码如下:

public static void rebind(String name, Remote obj)
        throws RemoteException, java.net.MalformedURLException
    {
        ParsedNamingURL parsed = parseURL(name);//根据name解析url
        Registry registry = getRegistry(parsed);//获取registry实例

        if (obj == null)
            throw new NullPointerException("cannot bind to null");

        registry.rebind(parsed.name, obj);//这里使用持有的registry实例的rebind方法进行服务绑定操作
    }

可以看到这里通过getRegistry(parsed)方法获取了registry实例对象,然后调用了registry.rebind方法进行服务绑定操作,没错,这里就是第二种服务启动方式的代码啦!而getRegistry(parsed)方法做了什么呢?其实就是调用了LocateRegistry.getRegistry(parsed.host, parsed.port);方法,这也是我们第二种创建服务的时候获取registry实例的方式,那么一切都知道了,我们来看看服务如何绑定的吧,进入registry实例的rebind方法(RegistryImpl类):

public void rebind(String var1, Remote var2) throws RemoteException, AccessException {
        checkAccess("Registry.rebind");
        this.bindings.put(var1, var2);//将url和服务引用存入hashTable
    }

可以看出来这里就是将解析出来的url和远程对象存入定义好的hashTable中,bindings则是提前定义好的HashTable,如下:

private Hashtable<String, Remote> bindings = new Hashtable(101);

如此,服务便存入注册中心中进行绑定了

RMI客户端请求原理

我们知道客户端请求调用服务端是通过registry.lookup方式获取服务端的远程对象引用,代码如下:

UserHandler handler = (UserHandler) registry.lookup("user");

首先我们来到RegistryImpl_Stub 类的lookup方法,代码如下:

public Remote lookup(String var1) throws AccessException, NotBoundException, RemoteException {
        try {
            RemoteCall var2 = super.ref.newCall(this, operations, 2, 4905912898345647071L);//从ref引用的newCall方法获取RemoteCall实例

            try {
                ObjectOutput var3 = var2.getOutputStream();//从RemoteCall实例中获取输出流,将数据写入
                var3.writeObject(var1);
            } catch (IOException var18) {
                throw new MarshalException("error marshalling arguments", var18);
            }

            super.ref.invoke(var2);//将RemoteCall实例传递给ref引用的invoke方法

            Remote var23;
            try {
                ObjectInput var6 = var2.getInputStream();//获取输入流,读取传来的数据
                var23 = (Remote)var6.readObject();
            } catch (IOException var15) {
                throw new UnmarshalException("error unmarshalling return", var15);
            } catch (ClassNotFoundException var16) {
                throw new UnmarshalException("error unmarshalling return", var16);
            } finally {
                super.ref.done(var2);
            }

            return var23;
        } catch (RuntimeException var19) {
            throw var19;
        } catch (RemoteException var20) {
            throw var20;
        } catch (NotBoundException var21) {
            throw var21;
        } catch (Exception var22) {
            throw new UnexpectedException("undeclared checked exception", var22);
        }
    }

这里涉及到了ref对象实例(RemoteRef )的newCall方法,获取RemoteCall实例,此实例将用来进行与客户端的数据传输交互,并且将当前的实例传递给ref的invoke方法,所以我们接着看看RemoteRefnewCall方法(UnicastRef 类),代码如下:

 public RemoteCall newCall(RemoteObject var1, Operation[] var2, int var3, long var4) throws RemoteException {
        clientRefLog.log(Log.BRIEF, "get connection");
        Connection var6 = this.ref.getChannel().newConnection();//从ref引用对象中开启连接

        try {
            clientRefLog.log(Log.VERBOSE, "create call context");
            if (clientCallLog.isLoggable(Log.VERBOSE)) {
                this.logClientCall(var1, var2[var3]);
            }

            StreamRemoteCall var7 = new StreamRemoteCall(var6, this.ref.getObjID(), var3, var4);//将我们的服务信息(端口等)传递给StreamRemoteCall实例进行构建

            try {
                this.marshalCustomCallData(var7.getOutputStream());//传输定制化数据(默认无代码实现)
            } catch (IOException var9) {
                throw new MarshalException("error marshaling custom call data");
            }

            return var7;
        } catch (RemoteException var10) {
            this.ref.getChannel().free(var6, false);//调用通信管道中的定时清理连接的方法,内部实现了定时任务调度,清理无用的连接
            throw var10;
        }
    }

可以看出来这里有很多ref引用的操作,而这个ref对象是什么呢?没错就是之前创建的LiveRef实例,通过该实例内部的Channel管道创建连接,进行数据交互,至此RMI的服务注册与调用流程我们已经学习完毕

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容