精华 RMI, Marshalling, RMI Class Loading
发布于 1 年前 阅读权限 无需登录 作者 n1nty 3304 次浏览 来自 知识碎片

这是我的个人笔记,与安全不直接相关。是在看到 Spring 反序列化 + JNDI 的 paper 以及 JMX 相关 exploits 后记下来的。原本只是打算给自己看的,所以并没有抱着特别严谨的态度在记。所以里面肯定有错误。请见谅。

RMI 基本流程

1. JVM1想要调用 JVM2 中的test对象的方法,首先要求 test 对象实现了一个 JVM1 与 JVM2 中都存在的一个公有的接口,比如 Interface1。JVM1 只能调用Interface1 中所定义的方法。
2. JVM2 上面,利用 rmi registry 来暴露 RMI 服务,然后创建 test 对象,并将 test 对象绑定到暴露出的 RMI 服务上(绑定的时候需要给 test 对象取一个名字)
3. 在 JVM1 上面,连接 JVM2 上暴露的 RMI 服务,然后利用 test 对象绑定的名字来取得 JVM2 上面的 test 对象的引用,然后就可以调用 JVM2 上面的 test 对象的方法了。

marshalling 与序列化

marshalling,与序列化的区别在于。这里只记最简单最明显的一点:除了对象的状态以外,marshalling 会将 class 的 codebase的地址也一并保存到流里面去,而序列化只保存对象的状态(数据)。具体区别:

Marshaling and serialization are loosely synonymous in the context of remote procedure call, but semantically different as a matter of intent. In particular, marshaling is about getting parameters from here to there, while serialization is about copying structured data to or from a primitive form such as a byte stream. In this sense, serialization is one means to perform marshaling, usually implementing pass-by-value semantics. It is also possible for an object to be marshaled by reference, in which case the data “on the wire” is simply location information for the original object. However, such an object may still be amenable to value serialization. As @Bill mentions, there may be additional metadata such as code base location or even object implementation code.

Both do one thing in common - that is serializing an Object. Serialization is used to transfer objects or to store them. But: • Serialization: When you serialize an object, only the member data within that object is written to the byte stream; not the code that actually implements the object. • Marshalling: Term Marshalling is used when we talk about passing Object to remote objects(RMI). In Marshalling Object is serialized(member data is serialzied) + Codebase is attached. So Serialization is part of Marshalling.

CodeBase is information that tells the receiver of Object where the implementation of this object can be found. Any program that thinks it might ever pass an object to another program that may not have seen it before must set the codebase, so that the receiver can know where to download the code from, if it doesn’t have the code available locally. The receiver will, upon deserializing the object, fetch the codebase from it and load the code from that location. 摘自: https://stackoverflow.com/questions/770474/what-is-the-difference-between-serialization-and-marshaling

dynamic rmi class loading

当利用 RMI 在多个 JVM 之间传递对象的时候,这些对象的状态(成员的值)将被序列化,然后被传输,这些对象所对应的 class 定义并不会被传输,它们默认被认为是存在于所有的 JVM 上的。当 JVM1 向 JVM2 利用序列化传递了一个 test 类的对象,但是 JVM2 上又不存在 test 类的定义的时候,这时就会触发 RMI 类动态加载。

When RMI send object between JVMs only the object data is transferred it is assume that the class definition already present on the other JVM when this is not correct the RMI on that other machine have to some how find and load the missing class definition.

默认是禁止 dynamic rmi class loading 的,需要配置一个安全管理器,并配有对应的 policy 文件。

stub 与 skeletons

https://docs.oracle.com/javase/8/docs/platform/rmi/spec/rmi-arch2.html http://blog.csdn.net/samlin930/article/details/6528723 自 JDK1.2 起,就不需要 skeletons 了。从 JDK5 开始,就不再需要我们手动生成 stub 了。但是要了解 stub 与 skeleton 的作用。

JVM1 在获取 JVM2 中的 test 对象的引用的时候,获取到的实际上是一个类似代理的对象。这个代理对象也实现了 Interface1 接口。这个代理对象就被称为 stub。我们通过这个 stub 对象来调用 JVM2 中的 test 对象。这个 stub 对象内部将我们传递的参数等信息进行 marshall,然后与 JVM2 建立 socket 连接,将 marshall 后的字节流写入 socket。而在 JVM2 中,接收这个 socket,并将字节流进行 unmarshall的,则是 skeletons 对象。skeletons 对象在进行完上述操作后,将会调用原始的 test 对象方法,将还原后的参数传入被调用的方法,然后接收到返回值后,像 JVM1 的 stub 一样,将返回值进行 marshall,写入 socket。JVM1 的 stub 从 socket 中接收返回值的字节流,进行 unmarshall,然后返回给调用者。

也就是说 stub 是在 client 用的,而 skeletons 是在服务端用的。

image002.png

image003.png

我们可以利用 rmic 来生成 sub 与 skeletons。

http://www.scons.org/doc/0.97/HTML/scons-user/x3172.html

大体流程:

1. 创建一个 public 的接口比如 Account,该接口需要继承自 Remote 接口
2. 实现接口,比如 AccountImpl,同时 AccountImpl 要继承自 UnicastRemoteObject类,为了实现远程对象的自动导出。当然不继承这个类也可以,我们可以手动将远程对象导出。
3. 将Account与 AccountImpl编绎成 class 文件
4. 利用 rmic,给 AccountImpl 生成 stub 与 skeleton(从 rmic1.2 开始,也就是 JDK1.2 开始,rmic 就不再生成 skeleton 了。从 JDK5 开始,可以使用动态代理技术在运行时动态生成 stub class,而不用手动生成静态的 stub class 了。所以如果是 JDK5 及之后的版本,这一步可以省略掉。)
5. 本地利用 jdk 自带的 rmiregistry 命令来启动一个 rmi registry 服务,该服务默认监听的是 1099 端口。也可以在启动 rmiregistry 的时候加一个参数,这个参数代表我们自己指定的端口。也可以不用 rmiregistry 这个命令,具体看下面的 Registry, LocateRegistry 与 Naming 的记录
6. 服务端程序生成一个接口实现类的对象,(如果这个类没有继承自 UnicastRemoteObject 的话,则我们还需要手动导出这个远程对象,具体步骤看下面的笔记),然后利用 Naming .rebind 方法将这个对象(严格地说是这个远程对象的 stub)绑定到本地刚启动的 rmi registry中。(到底是什么东西被绑定到了rmi registry 中,看下面的笔记。)
7. 客户端也需要有这个 Account 接口的定义(如果没有的话则客户端中只能通过反射来调用服务端的方法了。)。利用 Naming.lookup 方法,传入一个 URI,来连接到远程的 rmi registry 以获取远程的 AccountImpl 对象的stub 的引用,这个 URI 是这种格式的:rmi://aaa.com/remoteobjectname。
(除去Naming,我们也可以利用 JNDI 的 接口来在服务端导出和在客户端上获取远程对象。)
8. 如果客户端存在 AccountImpl 类的 stub,则将正常加载 stub 类,如果没有,则将启用 RMI 的远程加载,从服务端加载 stub 类。这里就涉及到了 rmi dynamic class loading。需要有安全管理器,还涉及到 codebase。

rmiregistry, Registry, LocateRegistry, Naming

所有的远程对象要想被客户端访问,都要被导出到 rmi registry 这个服务中。JDK 中自带了一个名为 rmiregistry 的程序,可以开启这个 rmi registry服务。与 WINDOWS 下的 RPC 机制相对比的话, rmi registry 的作用有点相当于 EPM,也就是监听 135 端口的那个服务。

启动 rmi registry,默认监听 1099 
	rmiregistry

自定义端口

	rmiregistry 9999
	
rmiregistry 本质上也是一个用 JAVA 写的程序,所以我们在启动它的时候,可以给 JVM 传参数:
	rmiregistry -J-D....
	这里与一般的 JAVA 程序的区别在于,传 JVM 参数的时候一定要在-D 前面加上-J 才可以。


但是如果我们不想用这个程序,也可以利用JAVA 代码来开启这个服务。下面介绍几个接口。

Registry

	这个接口代表着一个 registry,可以是开在本地的 registry,也可以是开在远程服务器上面的 registry。
	Registry 方法提供了bind, rebind, unbind, lookup, list 这五个方法用于在对应的 rmi registry 上绑定,取消绑定,查找绑定与列出所有绑定操作。
	
	要注意的是,如果当前 Registry 指向的是一个远程的 registry,则无法使用 bind rebind unbind 这三个方法,会得一个 java.rmi.AccessExeption 异常。只有 Registry 对象指向的是一个本地的 registry 的时候,才可以使用前三个方法。

LocateRegistry

	该类提供一些静态方法,用于:
		1. 连接本地或者远程的 rmi registry 服务,连接成功后将返回上面说到的 Registry 的引用。方法为 getRegistry 方法,存在多个重载。
		2. 创建一个本地的 rmi registry,并监听指定的端口。方法为 createRegistry,存在多个重载。使用 LocateRegistry.createRegistry 可以代替 JDK 自带的 rmiregistry 程序。

LocateRegistry + Registry 的使用

	Registry remote = LocateRegistry.getRegistry("test.com",9999);
	Account account = remote.lookup("account");
	
	上面使用 LocateRegistry.getRegistry 方法连接到 test.com 上运行在 9999 端口的 rmi registry 服务,并返回一个指向该服务的 Registry 类型的引用。
	然后利用该引用,得到该 rmi registry 上面导出的名为 account 的远程对象。

Naming

	该类提供了与 Registry 类一样的几个静态方法,bind, rebind, unbind, list, lookup
	它与 Registry 类的区别在于,这些方法的第一个参数都必须是一个 rmi:// 协议的 URL字符串,来指定操作要在哪个 rmi registry 上面做。
	
	下面看代码做一下对比:
	Account account = Naming.lookup("rmi://test.com:9999/account");
	Naming.bind("rmi://localhost/account", new Account());
	
	而用 Registry + LocateRegistry:
	Registry remote = LocateRegistry.getRegistry("test.com",9999);
	Account account = remote.lookup("account");
	remote.bind("account", new Account());
	
	 用 Naming 进行操作的时候,不需要获取一个 Registry 对象。但是在做任何操作的时候,都需要传入一个 rmi 协议的 url 来说明要对哪个 rmi registry 进行操作。
	
	如果该字符串中没有指定远程服务器的名字,则默认为 localhost,如果没有指定端口,则默认为 1099。比如:
	Naming.lookup("account")
	这里没有指定远程服务器的名字与端口,那么就会在运行在本地1099 端口上的的 rmi registry中查找名为 account 的远程对象。

对象被 marshall 时,绑定对象到 rmi registry 时

将对象绑定到 rmi registry本质上就是将对象 marshall 到 rmi registry,所有的对象在 marshall 的时候都遵守以下的规则:
	如果被绑定的对象实现了 Remote 接口,则被绑定到 rmi registry 的会是这个对象的 stub 对象(如果被绑定的对象本身就已经是 stub 对象了,则直接绑定这个stub对象,如果不是,则绑定之前已经为这个对象生成好了的 stub 对象,远程对象的 stub 的生成操作是在导出远程对象的时候做的,看下面导出远程对象的笔记)。
	
	此时要求:
		1. rmi registry 能够加载到这个远程对象的接口(并不是 Remote 接口,而是我们自定义的那个继承自 Remote 的接口)。无论是将这个接口直接放到 rmi registry 的 CLASSPATH 里面,还是让rmi registry 通过 java.rmi.server.codebase 去动态加载这个接口。
		2. 如果 stub class 是利用 rmic 生成的,则要求 rmi registry 能够加载到这个 stub 的 class 类。无论是将这个接口直接放到 rmi registry 的 CLASSPATH 里面,还是让rmi registry 通过 java.rmi.server.codebase 去动态加载这个接口。
		3. 如果 stub class 是动态生成的,则不需要 rmi registry 去下载这个类的定义。
		4. 要知道的一点是,在绑定 stub 的情况下,rmi registry 以及后续的 rmi client 都不需要加载远程对象的 class。

	如果被绑定的对象没有实现 Remote 接口,但是实现了 Serializable 接口,则被绑定到 rmi registry 的实现上是这个对象的一个复制品。
	
	此时要求:
		1. rmi registry 能够加载到这个对象的 class,以及这个 class 所依赖的所有的类。无论是将这些类直接放到 rmi registry 的 CLASSPATH 里面,还是让rmi registry 通过 java.rmi.server.codebase 去动态加载这个接口。

	如果绑定的是一个复制品,则表示 rmi client 以后从 rmi registry 那里取到的对象将是复制品的复制品,而不是对原来的那个远程对象的引用。此时调用复制品的方法,这些方法将在 rmi client 的 VM 里执行,而不是在 rmi server 的 VM 里面执行。


		a = Naming.lookup("rmi://test.com/account")
		这里得到的 a 是远程的 account 对象的一个 stub 对象。
		a.test(haha)
		这里,调用 a (其实是 a 的 stub 对象)的 test 方法,并传入 haha 做为参数。stub 的 test 方法会对 haha 参数进行检测,如果 haha 是可序列化的或者基本数据类型,则将 haha 序列化,远程的 account 对象将会得到 haha 这个对象的一个复制体。
		如果 haha 是一个客户端这边的远程对象(实现了 Remote 接口的对象),则远程的 account 对象收到的就是 haha 这个对象的 stub 对象,也就是一个远程引用。
		

	The RMI stub/skeleton layer decides how to send method arguments and return values over the network, based on whether a particular object is Remote, Serializable, or neither:
		•  If the object is a Remote object, a remote reference for the object is generated, and the reference is marshaled and sent to the remote process. The remote reference is received on the other end and converted into a stub for the original object. This process applies to both method arguments and return values.
		• If the object is Serializable but not Remote, the object is serialized and streamed to the remote process in byte form. The receiver converts the bytes into a copy of the original object.
		•  If the method argument or return value is not serializable (i.e., it's not a primitive data type or an object that implementsSerializable), the object can't be sent to the remote client, and a java.rmi.MarshalException is thrown.
	The principal difference between remote and nonremote objects is that remote objects are sent by reference, while nonremote, serializable objects are sent by copy. In other words, a remote reference maintains a link to the original object it references, so changes can be made to the original object through the remote stub. If the server object calls update methods on an argument to a remote method, and you want the updates to be made on the original object on the client side, the argument needs to be a Remote object that automatically exports a stub to the server object. Similarly, if the return value of a remote method call is intended to be a reference to an object living on the server, the server implementation needs to ensure that the object returned is a Remote object.

导出远程对象,绑定到 rmi registry

导出远程对象

	一个 Remote 接口的实现类,说明这个类对接口的实现是可以被远程调用的。但是要想这个类的某个对象真正的能够被远程调用,则我们需要将这个对象导出到 RMI 运行时。“导出”操作会做下面这些事情:
		1. RMI 运行时将会为这个对象开启一个 server socket,并监听一个随机的或者指定的端口。
		2. RMI 运行时将会为这个对象生成 stub class(如果没有预先生成的 stub class 的话。)与 stub class 的对象,具体看 UnicastRemoteObject 的 JAVA DOC。RMI CLIENT 通过这个 stub 对象来远程调用被调出的对象的方法。stub 对象里面记录着 RMI SERVER 的 IP 地址以及远程对象被绑定在了哪个端口。

	然后我们将已经导出的对象的 stub 实例绑定到 rmi registry 服务,RMI CLIENT 从 rmi registry 服务那里得到远程对象的 stub 对象,然后通过 stub 对象来调用远程对象上面的方法。stub 对象将会通过自己记录的 RMI SERVER 的IP 与远程对象所绑定的端口以及 JRMP 协议来与远程对象通信。

如何导出远程对象

	导出远程对象的方法有两种,都与 UnicastRemoteObject 类有关。
		1. 远程对象的类继承自 UnicastRemoteObject(它实现了 Serializable 接口),这样的话,在实例化这个类的时候,会自动调用 UnicastRemoteObject 类的默认构造器,在这个默认构造器里面,将完成上面提到的导出操作。也就是说,只要实例化一个 UnicastRemoteObject 类的对象,这个对象就自动被导出了。
		2. 首先要说明,第 2 种方法并不推荐。因为我们需要手动导出需要被远程调用的所有对象。
		如果我们想要导出一个没有继承自 UnicastRemoteObject 的类的实例,则我们需要调用 UnicastRemoteObject.exportObject 方法,来手动导出这个对象。该方法有很多重载,不过一般第一个参数是要导出的对象,第二个参数是指定要将对象导出到哪个端口,默认情况下,对象会被导出到所有网络接口的指定端口。如果端口参数传入 0,则会使用一个随机的端口。

远程对象导出后

	在将远程对象导出后,说明 RMI SERVER 端已经启动了 server socket 在监听指定的端口。此时我们需要将远程对象的 stub 绑定到 rmi registry,随后 RMI CLIENT 就可以通过 rmi registry 来查找他们想要调用的远程对象的 stub,以实现远程调用。

实例

	下面将用代码演示导出远程对象的那两种方法:
		第一种
			定义接口,注意接口一定要是 public 的
			public class Account extends Remote{
				public String getName() throws RemoteException;
			}
			
			实现接口,注意实现类一定要继承 UnicastRemoteObject,UnicastRemoteObject 实现了 Serializable 接口。
			public class AccountImpl extends UnicastRemoteObject implements Account {
				public String getName() {
					System.out.println(111);
					return "name";
				}
			}
			
			服务端进行导出操作
			public static void main(String[] args) throws Exception {
				Account account = new AccountImpl(); //在声明这个对象的时候,就同时完成了导出操作
				Naming.rebind("account", account); //直接将这个对象导定到运行在本地 1099 端口的 rmi registry 服务。
				/*
				这里要注意,上面说过,绑定到 rmi registry 的应该是远程对象的 stub,而不是远程对象本身。所以我猜测这里虽然传入的是 account 这个远程对象本身,但是在向 rmi registry marshall account 这个参数的时候,真正写进去的应该是 stub 对象。
				*/
				while (true) {
					Thread.currentThread().sleep(1000);
				}
			}
			
			客户端进行调用
			((Account)Naming.lookup("account")).getName();
			
		
		第二种方法,远程对象类不继承自 UnicastRemoteObject
			定义接口,注意接口一定要是 public 的
			public class Account extends Remote{
				public String getName() throws RemoteException;
			}
			
			实现接口,不继承 UnicastRemoteObject,所以在实现 Account 接口的时候,还要实现 Serializable 接口,否则无法进行 marshalling 与 unmarshalling
			public class AccountImpl implements Account, Serializable {
				public String getName() {
					System.out.println(111);
					return "name";
				}
			}
			
			服务端进行导出操作
			public static void main(String[] args) throws Exception {
				Accout account = new AccountImpl();
				Account stub = (Account)UnicastRemoteObject.exportObject(account, 0); //这里对 account 对象进行导出,并获得导出后返回的 stub
				
				Naming.rebind("account", stub);
				while (true) {
					Thread.currentThread().sleep(1000);
				}
			}
			
			客户端进行调用
			((Account)Naming.lookup("account")).getName();
			
		
		如果没有继承 UnicastRemoteObject,同时也没有用 exportObject 将对象进行导出,而是直接将对象绑定到了 rmi registry 是什么效果?
			那么,rmi registry 里面绑定的就不是远程对象的 stub,而是远程对象的一个复制体。也就是说远程对象本身被序列化到 rmi registry 里面了。此时 RMI CLIENT 从 rmi registry 里获取的也将是一个远程对象的复制体,而不是这个远程对象的引用,此时如果调用远程对象的方法,则该方法就是在 RMI CLIENT 的 VM 里面执行的,而不是在远程对象所属的 VM 里面执行的。

关于 stub

如果是 JDK5 以后的版本,是不需要我们手动为远程对象生成 stub 的。JDK5 以后的版本能够在远程对象被导出的时候,使用动态代理的机制动态地生成 stub class。
加载 stub 的流程变成下面这样:
	1. 当 java.rmi.server.ignoreStubClasses 为 false 的时候(默认为 false)
		1. 会先尝试查找是否有利用 rmic 生成的 stub classes 的存在,如果有的话,则加载。
		2. 如果没有,则动态生成。
	2. 如果 java.rmi.server.ignoreStubClasses 为 true,则无论利用 rmic 事先生成的 stub classes 存不存在,都将忽略他们,将会动态生成 stub classes。

但是要注意一点,一定要保证所有的 RMI SERVER 与 RMI CLIENT 使用的 JDK 都是 5 或以上的版本。因为如果 RMI SERVER 导出的远程对象用的是动态生成的 stub,而 RMI CLIENT 运行的是 5 之前的 JDK,则 RMI CLIENT 将无法正常加载这个动态生成的 stub class 的对象。
具体的原理看这里:

http://docs.oracle.com/javase/1.5.0/docs/guide/rmi/relnotes.html

如果是运行时动态生成的 stub,VM 不需要到 codebase 指定的地方去远程下载。如果是利用 rmic 来事先生成的 stub,而且在导出对象的时候,也确实用的是这些生成的 stub class,则 rmiregistry 和 rmi client 都需要去下载这些 stub class 才可以。

关于 java.rmi.server.codebase 与 java.rmi.server.useCodebaseOnly

当 RMI CLIENT 向 RMI SERVER 传递了一个对象,比如传递了一个 Address 类的对象,但是 RMI SERVER 上面却找不到Address类的定义的时候,就会触发 RMI SERVER 进行 RMI 类动态加载机制。RMI SERVER 会去远程加载这个 Address 的 class 文件。

但是应该去哪里加载呢?这就涉及到 codebase的值了。
	这里一共涉及到两个 codebase 的值。
		1. 从 RMI CLIENT 传递过来的 codebase。当 RMI CLIENT 向 RMI SERVER 传递这个对象的时候,按照上面对 marshalling 的定义,这个对象会被序列化到字节流里面去,除去这个对象的状态信息会被序列化到字节流外,这个对象的 codebase 地址也会被写入字节流。
		2. RMI SERVER 自己设置的 java.rmi.server.codebase

这个对象的 codebase 地址到底是什么呢?

	上面提到,有一个 codebase 地址是从客户端传递过来的那个对象的 codebase 地址。那这个对象的 codebase 地址到底是什么?
	对象的 codebase 就是它所属的类的 codebase 地址。而类的 codebase 地址,则要看这个类是由哪个类加载器加载的。
	如果一个类是由系统默认的类加载器加载的,则这个类的 codebase 地址就是当前 JVM 进程的 java.rmi.server.codebase 的值,这个值默认为空,需要我们手动去传。如果我们没有传这个值,则为 null。
	如果一个类不是由系统默认的类加载器加载的,则这个类的 codebase 地址就是加载这个类的那个类加载器的 codebase 地址。这一点对于codebase 的传递是很重要的。
		比如:
		RMI CLIENT 向 RMI SERVER 传递了一个Email 类的对象,但是这个 Email 类是 RMI CLIENT 从另一个地方远程加载的。则这个 Email 对象在被 marshalling 的时候,写入的 codebase 就是加载 Email 这个类的那个类加载器的 codebase 地址,而不是 RMI CLIENT 的 java.rmi.server.codebase 的地址。
	
	具体看这里的介绍:

http://barakb.github.io/asyncrmi/docs/dynamic-class-loading.html

RMI SERVER 在进行 unmarshall 的时候,遇到了自己没有的类的对象,该如何去做呢 ?
	如果 RMI SERVER 收到了 RMI CLIENT传递过来的一个对象,但是自己又没有这个类的定义(在自己的 classpath 中找不到该类的定义),则 RMI SERVER 会利用 RMI 类动态加载机制去加载这个类(当然类的远程加载默认是禁止的,需要 RMI SERVER 上配置了安全管理器,以及相关的 policy)
	
	如何启用 RMI 动态类加载机制?
		要想 RMI 类动态加载生效,JVM 中必须设置一个security manager 以允许类的远程加载。有一个JDK 自带的安全管理器类名为RMISecurityManager。在指定了安全管理器后,一般要指定一个自定义的 policy file,因为默认的 policy file 是不允许进行任何 socket 操作的。
		
		permission java.net.SocketPermission "xxx.com", "accept, connect"
		或者直接 ALLPermisson。
		然后启动应用的时候加上 -Djava.security.policy=policy.txt
		
	
	RMI SERVER会从 codebase 指定的地址去下载类的定义,codebase 可以是 HTTP 、FTP、FILE 等协议。 前面说到了,一共涉及到两个 codebase,那么到底使用哪个 codebase 的值呢 ?是使用随着对象一起从 RMI CLIENT 传递过来的那个 codebase,还是使用 RMI SERVER 自身所设置的 java.rmi.server.codebase 的值呢?
	
	这一切是由RMI SERVER 端的 java.rmi.server.useCodebaseOnly 的值决定的。
	如果 java.rmi.server.useCodebaseOnly 值为 false
		则,RMI SERVER 会采用从 RMI CLIENT 传递过来的 codebase。
	如果 java.rmi.server.useCodebaseOnly 值为 true
		则,RMI SERVER 会采用自己的 JVM 进程的 java.rmi.server.codebase 的值,而忽略从客户端传过来的值。
		From JDK 7 Update 21,  JDK 6 Update 45 and JDK 5 Update 45 releases 开始,就默认为 true 了。

http://docs.oracle.com/javase/7/docs/technotes/guides/rmi/enhancements-7.html

codebase 的格式:

1. 如果是一个远程的 HTTP 目录,则必须以  / 结尾
2. 可以是远程 JAR 文件的地址,也可以是多个 JAR 文件的地址, 多个地址之间以空格隔开

codebase 在 RMI SERVER、RMI REGISTRY、RMI CLIENT 之间的传递

有时为了保证 codebase 在 rmi server, rmiregistry, rmi client 之间得正确的传递,使得它们三都都能正确地进行远程类加载,有可能 RMI SERVER,RMI REGISTRY,RMI CLIENT 都需要配置自己的 java.rmi.server.codebase。或者如果是高版本的 JDK,则都需要将 useCodebaseOnly 重新改为 false。

正确的 JAVA 程序设置 useCodebaseOnly

	-Djava.rmi.server.useCodebaseOnly=true。

rmiregistry 设置 useCodebaseOnly

	rmiregistry -J-Djava.rmi.server.useCodebaseOnly=true

参考资料

http://barakb.github.io/asyncrmi/docs/dynamic-class-loading.html https://docs.oracle.com/javase/8/docs/technotes/guides/rmi/codebase.html http://docs.oracle.com/javase/1.5.0/docs/guide/rmi/hello/hello-world.html http://docs.oracle.com/javase/7/docs/technotes/guides/rmi/enhancements-7.html http://www.math.uni-hamburg.de/doc/java/tutorial/rmi/implementing.html http://stackoverflow.com/questions/16769729/why-rmi-registry-is-ignoring-the-java-rmi-server-codebase-property

10 回复

谢谢分享 :)

虽然看不懂,膜拜一波师傅先

周末简单写了个demo试了下,但对原理了解的还不深,谢谢分享!

这东西很有用,得反复的看。

这篇文章好长,先瞻仰一下,👍

之前rmi没遇见过几次…mark一下

厉害了哥虽然看不懂

谢谢了,好多都不懂啊

想问楼主大师一个问题: 在启动Java后(V1.6 version),执行rmiregistry 9999,会有时消耗掉所有CPU资源,造成系统hang,为何?

虽然看不懂,膜拜一波师傅先

回到顶部