Java RMI 实现代码动态下载

来源:百度文库 编辑:神马文学网 时间:2024/04/27 23:57:25
摘要:本译文将向你介绍JavaTMRMI动态类文件下载的应用,学习完本文,你将会对JavaTMRMI有进一步的认识。希望你能参考我的上一篇译文:开始学习Java RMI,远程方法调用-基础篇。

    1.概要

    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标签的“codebase”属性。本文档假设你有些Java RMI编程经验,同时写过一些含有applet标签的HTML文件。例如,在HTML源文件中(applet标签)将含有一些类似下面的代码:

    2.什么是codebase

    类代码址可以为一个源文件,或是一个目录,虚拟机可以由此加载类。举个例子来说,如果你邀请一个朋友到家吃晚饭,你需要告诉你朋友你的居住方向,以便你的朋友能够确定你家的位置。同样,你可以把代码库址(Codebase)看作一个你指给JVM的方向,让JVM能够找到[可能是远程]它需要的类。

    你可以把你的classpath看作为是“本地代码库址”,因为它是一系列调用本地代码类目录。当基于本地调用类时,你的classpath环境变量是(JVM的)参照。CLasspath变量可以设定为一个相对,绝对目录或是类文件压缩包。倘若CLASSPATH是一种“本地代码库址”,那么Applets和远程对象使用的codebase也可以认为是一种“远程代码库址”。

    3.工作原理

    3.1 Applets如何使用代码库址(codebase)

    为了能和Applet交互,这个applet和其运行中需要的任何类必须能够被客户端访问。虽然applets也可以通过“ftp://”或是“file:///”地址访问,但是它经常是通过Web服务访问。

    ● 1.客户端浏览器请求的一个applet的类在CLASSPATH中无法找到

    ● 2.通过HTTP,applet(和它需要其他类)从服务端下载到客户端

    ● 3.在客户端执行applet

图一:下载Applets

    标签含有的代码库址(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 存根类

   ● 1. 远程对象代码库址(codebase)是通过在远程对象服务端设定java.rmi.server.codebase属性指定的。在Java RMI 注册表的帮助下,Java RMI 服务端注册了一个远程对象,并绑定了一个名字。服务端JVM中代码库址(codebase)设定给在Java RMI注册表中的远程对象引用提供了注解。

    ● 2. Java RMI客户端请求一个(已知)命名的远程对象的引用。客户端使用该引用(远程对象的存根实例)进行对远程对象的方法调用。

    ● 3. Java RMI注册表向请求的类返回一个引用(存根实例)。客户端会优先于codebase在本地的classpath中寻找存根类,如果发现了,那么它就会在本地调用该类。然而,如果在本地的classpath找不到该stub的存根类,客户端就会试着从远程对象代码库址(codebase)中检索该类。

    ● 4. 客户端从代码库址(codebase)请求类。客户端使用的代码库址(codebase)就是存根实例注解的URL,它是在存根类被注册表加载时注解的。回到第一步1,为导出对象的存根做注解,然后随着绑定的名字被注册到注册表中。

    ● 5. 定义的存根类(和其需要的其他类)被下载到客户端。

    注意:第4和5是相同的步骤,当远程对象被一个名字绑定到注册表中时,注册表就开始调用该远程对象类。当注册表试着调用远程对象的存根类时,从代码库址(codebase)中,它和远程对象一起请求该类的定义。

    ● 6. 现在客户端已经具备了调用远程对象方法的所有条件。存根(在这过程中)像一个服务端的远程对象的代理一样;不像applet使用代码库址(codebase)运行代码在本地的虚拟机中,Java RMI 客户端使用远程的代码库址(codebase)运行代码在另一个,可能是远程JVM中。如图三所示:

图三:Java RMI 客户端远程方法调用

    4.在Java RMI中利用codebase属性,实现非存根(sub)类的下载

    除了下载存根类(stub)和其辅助类到客户端,java.rmi.server.codebase属性还被用来指定其他的,不仅仅是stub类的下载地址。

    当客户端调用远程对象的方法时,该方法可能无参或是有许多的参数,根据方法参数类型,这样就可能有三种不同情况发生。

    第一种情况,所有的(远程)方法参数(或是返回值)都是原始的数据类型,这样远程对象知道如何的解释他们作为方法的参数,同时也无需检查classpath和codebase属性。

    第二种情况,至少有一个参数或是返回值是一个对象,然而远程对象可以在本地的classpath中可以找到该对象类的定义。

    第三种情况(如图四,第六步所示),远程方法收到一个对象参数,然而远程对象在本地的classpath中没有找到对象的定义。这种远程方法的调用情况如图四所示。客户端发送的对象类可能是(远程方法)参数类的子类型,它可能是两者其中之一:

    ● 一个接口的实现,该接口为方法的参数(或是返回值)类型

    ● 一个类的子类,该类为方法的参数(或是返回值)类型

图四:Java RMI客户端远程方法调用,传递一个未知的参数类型的子类型

    类似applet的代码库址(codebase),客户端设定的代码库址(codebase),用于其他JVM下载远程类,非远程类和接口地址。如果在客户端的应用中设定了代码库址(codebase)属性,那么客户端在调用子类型时,代码库址(codebase)就被作为参数加到子类型的实例上。如果在客户端没有设定代码库址(codebase),那么远程对象就会错误的使用自己的代码库址(codebase)。 

  5.命令行例子

    在applet情况下,代码库址(codebase)是嵌在网页中的,就如我们在本文的第一部分看到的HTML例子。

    在Java RMI应用时,codebase不是依靠一个镶嵌在网页中类的引用实现的,客户端会和Java RMI的注册表沟通获得远程对象的应用。由于远程对象的代码库址(codebase)可以指向任意URL,不能仅是一个相对于已知的URL地址,必须是存根类(stub)和其相关类目录的绝对地址。代码库址(codebase)可以指向:

    ● 一个目录地址,该目录中含有类包子目录

    ● 一个Jar文件路径,含有类包的目录压缩文件

    ● 满足以上条件的多个目录或是多个Jar文件,中间用空格间隔

    注意:如果代码库址(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"

    6.疑难问题解答

    如果你的Java RMI 程序配置正确,任何一个可以序列化的类,包含Java RMI 存根类,都是可以被下载下来的。动态的存根(stub)能够正常的下载,需要满足几种状况:

    ● A. 通过URL提供的存根类和存根类依赖的任何类能够被客户端可达。

    ● B. 在服务程序中通过调用bind或是rebind设定(或是在程序安装的过程中激活)(译注:rebind(String url, Remote obj) 或是bind(String url, Remote obj)),如下情况:如步骤A中设定的URL同时如果设定的为一个目录,那么必须是“/”结尾。

    ● C. rmiregistry在它相关的classpath中找不到存根类或是其依赖的其他类。这也是为什么我们在注册表调用存根时,给它加上代码库址(codebase)的参数的原因了,在服务端或是安装代码中,作为一个调用的结果。

    ● D. 客户端安装的SecurityManager允许存根(stub)下载。在Java 2 SE或是高版本中,这将意味着客户端必须在策略文件中进行合适的配置。

    在使用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)

    其他资源

    如果你对代码库址(codebase)还有其他的没有解答的问题,请首先浏览RMI-USER组。

    你可能也想加入RMI-USER邮件列表中。