Java平台一个重要的优点就是可以动态的从一个给定的URL下载Java软件到一个正在运行JVM的独立进程中,该进程通常位于一个不同物理系统中。这样可以让一个远程系统运行一个程序,例如一个Applet,它从来没有被安装到本地的存储介质上。在该文档的前几部分,我们先讨论Applet的codebase,以帮助我们更好的介绍有关Java RMI的codebase。 举例来说,一个运行在浏览器中的虚拟机,可以把java.applet.Applet的子类和其相关类的字节码下载下来(到本地)。运行该浏览器的系统以前从没有运行过该Applet,也没有在本地安装。一旦所有的类从服务端下载完成,浏览器借助本地资源就开始运行这个Applet程序。 Java RMI正是采用了这个优点,下载、运行这些从来没有在本地安装过的类。调用Java RMI的API的虚拟机,不仅仅像那些浏览器中,能够下载任意Java类文件,其中含有那些特定Java RMI存根类,它使借助服务器资源的远程调用的执行成为可能。 Codebase观点源于Java程序语言的ClassLoaders的应用。当一个Java程序使用一个ClassLoader时,那么它需要知道它被允许到那里调用类。通常,一个类调用者和HTTP Server一起使用,Server为Java平台应用提供编译过的类。很可能,你提到第一对有关ClassLoader/codebase就是AppletClassLoader和作为HTML标签<applet>的“codebase”属性。本文档假设你有些Java RMI编程经验,同时写过一些含有applet标签的HTML文件。例如,在HTML源文件中(applet标签)将含有一些类似下面的代码: <applet height=100 width=100 codebase="myclasses/" code="My.class"> <param name="ticker"> </applet> 类代码址可以为一个源文件,或是一个目录,虚拟机可以由此加载类。举个例子来说,如果你邀请一个朋友到家吃晚饭,你需要告诉你朋友你的居住方向,以便你的朋友能够确定你家的位置。同样,你可以把代码库址(Codebase)看作一个你指给JVM的方向,让JVM能够找到[可能是远程]它需要的类。 你可以把你的classpath看作为是“本地代码库址”,因为它是一系列调用本地代码类目录。当基于本地调用类时,你的classpath环境变量是(JVM的)参照。CLasspath变量可以设定为一个相对,绝对目录或是类文件压缩包。倘若CLASSPATH是一种“本地代码库址”,那么Applets和远程对象使用的codebase也可以认为是一种“远程代码库址”。 3.1 Applets如何使用代码库址(codebase) 为了能和Applet交互,这个applet和其运行中需要的任何类必须能够被客户端访问。虽然applets也可以通过“ftp://”或是“file:///”地址访问,但是它经常是通过Web服务访问。
图一:下载Applets <applet>标签含有的代码库址(codebase)通常是HTML页面URL的相对地址。 3.2 Java RMI如何使用代码库址(codebase) 使用Java RMI,应用程序能够创建出远程对象,该对象接受从客户端中JVM方法调用。为了能让客户端调用远程对象中的方法,客户端必须采用一种机制来和远程对象交流。Java RMI 使用了一个叫做存根的特殊类,它能够被下载到客户端和远程对象的交流(进行方法的调用),而不是使用(专用)程序使客户端同远程对象的方法进行对话。java.rmi.server.codebase属性代表了一个或是多个URL地址,从该地址那些存根类(和存根需要的其他类)能够被下载。 像applet,那些要进行远程方法调用的类也要从“file:///”地址下载,但是也像applet,一个“file:///”地址通常要求客户端和服务端位于同样的物理主机上,除非URL使用其他的文件系统,像NFS,这样才能变得有效。 通常,那些被用来进行远程方法调用的类,都是通过网络资源访问的,像是HTTP或是FTP服务器。 图二:下载Java RMI 存根类
图三:Java RMI 客户端远程方法调用 4.在Java RMI中利用codebase属性,实现非存根(sub)类的下载 除了下载存根类(stub)和其辅助类到客户端,java.rmi.server.codebase属性还被用来指定其他的,不仅仅是stub类的下载地址。 当客户端调用远程对象的方法时,该方法可能无参或是有许多的参数,根据方法参数类型,这样就可能有三种不同情况发生。 第一种情况,所有的(远程)方法参数(或是返回值)都是原始的数据类型,这样远程对象知道如何的解释他们作为方法的参数,同时也无需检查classpath和codebase属性。 第二种情况,至少有一个参数或是返回值是一个对象,然而远程对象可以在本地的classpath中可以找到该对象类的定义。 第三种情况(如图四,第六步所示),远程方法收到一个对象参数,然而远程对象在本地的classpath中没有找到对象的定义。这种远程方法的调用情况如图四所示。客户端发送的对象类可能是(远程方法)参数类的子类型,它可能是两者其中之一:
图四:Java RMI客户端远程方法调用,传递一个未知的参数类型的子类型 7. 类似applet的代码库址(codebase),客户端设定的代码库址(codebase),用于其他JVM下载远程类,非远程类和接口地址。如果在客户端的应用中设定了代码库址(codebase)属性,那么客户端在调用子类型时,代码库址(codebase)就被作为参数加到子类型的实例上。如果在客户端没有设定代码库址(codebase),那么远程对象就会错误的使用自己的代码库址(codebase)。 在applet情况下,代码库址(codebase)是嵌在网页中的,就如我们在本文的第一部分看到的HTML例子。 在Java RMI应用时,codebase不是依靠一个镶嵌在网页中类的引用实现的,客户端会和Java RMI的注册表沟通获得远程对象的应用。由于远程对象的代码库址(codebase)可以指向任意URL,不能仅是一个相对于已知的URL地址,必须是存根类(stub)和其相关类目录的绝对地址。代码库址(codebase)可以指向:
注意:如果代码库址(codebase)设定为一目录地址,那么结尾一定要是“/”。 例子: 如果你把要下载类在“webvector”HTTP服务器的export目录下(在Web根目录下),那么的你的代码库址(codebase)就该这样设置: -Djava.rmi.server.codebase=http://webvector/export/ 如果你把要下载类放在“webline”HTTP服务器的public目录下(在Web根目录下),一个名字为“mystuff.jar”的Jar文件,你的代码库址(codebase)就该如此设置: -Djava.rmi.server.codebase=http://webline/public/mystuff.jar 现在我们假设你把要下载的类分为两个文件“myStuff.jar”和“myOtherStuff.jar”,而且这两个文件放在不同的服务器上(名字是:“webfront”和“webwave”),你的代码库址(codebase)属性就该这样设定: -Djava.rmi.server.codebase="http://webfront/myStuff.jar http://webwave/myOtherStuff.jar" 如果你的Java RMI 程序配置正确,任何一个可以序列化的类,包含Java RMI 存根类,都是可以被下载下来的。动态的存根(stub)能够正常的下载,需要满足几种状况:
在使用Java RMI的java.rmi.server.codebase系统变量时,有两种经常性的问题,我们将在下边讨论。 6.1 运行Java RMI服务端可能碰到的问题 你碰到的第一个问题可能是收到ClassNotFoundException的异常,当你向注册表绑定(bind或是rebind)一个远程对象和名字时。这种异常通常是由不合法的codebase属性引起的,导致了在注册表中不能定位远程对象的存根(stub)或是存根需要的其他类。 同远程对象本身相比,远程对象的存根(stub)实现了所有同样的接口,这需要特别的注意。因此这些接口,同其他定制的类作为方法参数或是返回值,也必须能够通过指定的代码库址(codebase)下载。 通常,由于忽略了属性设定时URL中末尾“/”,导致这个异常的抛出。其他的一些原因可能是:属性值不是一个URL;URL路径拼写错误或是不正确;设定的URL中存根类(stub)和其相关的类不存在。 这种情况下,你遇到的异常可能是这样: java.rmi.ServerException: RemoteException occurred in server thread; nested exception is: java.rmi.UnmarshalException: error unmarshalling arguments; nested exception is: java.lang.ClassNotFoundException: examples.callback.MessageReceiverImpl_Stub java.rmi.UnmarshalException: error unmarshalling arguments; nested exception is: java.lang.ClassNotFoundException: examples.callback.MessageReceiverImpl_Stub java.lang.ClassNotFoundException: examples.callback.MessageReceiverImpl_Stub at sun.rmi.transport.StreamRemoteCall.exceptionReceivedFromServer(Compiled Code) at sun.rmi.transport.StreamRemoteCall.executeCall(Compiled Code) at sun.rmi.server.UnicastRef.invoke(Compiled Code) at sun.rmi.registry.RegistryImpl_Stub.rebind(Compiled Code) at java.rmi.Naming.rebind(Compiled Code) at examples.callback.MessageReceiverImpl.main(Compiled Code) RemoteException occurred in server thread; nested exception is: java.rmi.UnmarshalException: error unmarshalling arguments; nested exception is: java.lang.ClassNotFoundException: examples.callback.MessageReceiverImpl_Stub
6.2 运行Java RMI客户端可能碰到的问题 你可能遇到第二个问题,就是在注册表中查找远程对象时,输出ClassNotFoundException的异常。如果你在运行客户端代码时收到这个堆栈异常信息,那么问题可能是你的Java RMI注册表启动时classpath设定的问题。参考 requirement C in section 6.0。这里有一个这样异常例子: java.rmi.UnmarshalException: Return value class not found; nested exception is: java.lang.ClassNotFoundException: MyImpl_Stub at sun.rmi.registry.RegistryImpl_Stub.lookup(RegistryImpl_Stub.java:109 at java.rmi.Naming.lookup(Naming.java:60) at RmiClient.main(MyClient.java:28) 其他资源
|
|