java rmi原理详解_RMI原理揭秘之远程对象

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

package com.guojje;

import java.rmi.Remote;

import java.rmi.RemoteException;

public interface IHello extends Remote {

public int helloWorld()throws RemoteException;

}

3. 定义实现类: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.服务端: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.客户端:package com.guojje;

import java.rmi.Naming;

public class HelloClient {

public static void main(String args[]) {

try {

for (int i = 0; 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基类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出来的对象类型:package com.guojje;

import java.rmi.Naming;

public class HelloClient {

public static void main(String args[]) {

try {

for (int i = 0; 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类的继承是如何影响这些的呢? 我们来探索一下。package com.guojje;

public class HelloServer {

public static void main(String args[]) {

try {

new Hello();

} catch (Exception e) {

e.printStackTrace();

}

}

}

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

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

9b144a6857a347ee6d529e1a39b9b93d.png

f40346446ce641d41ca0153e6f9000b3.png

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

7256b1776149bc01c0fcb5ec9cf6e16f.png

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

{

this.port = port;

exportObject((Remote) this, port);

}public static Remote exportObject(Remote obj, int port)

throws RemoteException

{

return exportObject(obj, new UnicastServerRef(port));

}/**

* 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):/**

* 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方法:/**

* 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发布了多少个对象。super.exportObject(target);/**

* 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说起:

b214cf61bbbbf5838d89c3829ba4aacd.png

RegistryImpl.java:/**

* 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方法。

0551797e0425a977faca6577f0f4e365.png

RegistryImpl_Skel类是rmic生成所有没有源代码,我们只能从反编译的代码中查看一点信息: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

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

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

5158f485e6c894720ff8577046de4ffc.png

看一个重要方法MarshalOutputStream.java/**

* 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对象中,序列化到客户端。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值