Java RMI实现代码动态下载

    技术2022-05-12  14

     

    SiteFromhttp://www.linux521.com/2009/java/200904/1427.html

    摘要:本译文将向你介绍JavaTM RMI动态类文件下载的应用,学习完本文,你将会对JavaTM RMI有进一步的认识。希望你能参考我的上一篇译文:开始学习Java RMI,远程方法调用-基础篇。

    1.1. 1.概要

    Java平台一个重要的优点就是可以动态的从一个给定的URL下载Java软件到一个正在运行JVM的独立进程中,该进程通常位于一个不同物理系统中。这样可以让一个远程系统运行一个程序,例如一个Applet,它从来没有被安装到本地的存储介质上。在该文档的前几部分,我们先讨论Appletcodebase,以帮助我们更好的介绍有关Java RMIcodebase.

    举例来说,一个运行在浏览器中的虚拟机,可以把java.applet.Applet的子类和其相关类的字节码下载下来(到本地)。运行该浏览器的系统以前从没有运行过该Applet,也没有在本地安装。一旦所有的类从服务端下载完成,浏览器借助本地资源就开始运行这个Applet程序。

    Java RMI正是采用了这个优点,下载、运行这些从来没有在本地安装过的类。调用Java RMIAPI的虚拟机,不仅仅像那些浏览器中,能够下载任意Java类文件,其中含有那些特定Java RMI存根类,它使借助服务器资源的远程调用的执行成为可能。

    Codebase观点源于Java程序语言的ClassLoaders的应用。当一个Java程序使用一个ClassLoader时,那么它需要知道它被允许到那里调用类。通常,一个类调用者和HTTP Server一起使用,ServerJava平台应用提供编译过的类。很可能,你提到第一对有关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>

    1.2. 什么是codebase

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

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

    1.3. 工作原理

    1.3.1. Applets如何使用代码库址(codebase

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

    客户端浏览器请求的一个applet的类在CLASSPATH中无法找到;通过HTTPapplet(和它需要其他类)从服务端下载到客户端;在客户端执行applet

    1 下载Applets的过程

    <applet>标签含有的代码库址(codebase)通常是HTML页面URL的相对地址。

    1.3.2. Java RMI如何使用代码库址(codebase

    使用Java RMI,应用程序能够创建出远程对象,该对象接受从客户端中JVM方法调用。为了能让客户端调用远程对象中的方法,客户端必须采用一种机制来和远程对象交流。Java RMI使用了一个叫做存根的特殊类,它能够被下载到客户端和远程对象的交流(进行方法的调用),而不是使用(专用)程序使客户端同远程对象的方法进行对话。java.rmi.server.codebase属性代表了一个或是多个URL地址,从该地址那些存根类(和存根需要的其他类)能够被下载

    applet,那些要进行远程方法调用的类也要从“file:///”地址下载,但是也像applet,一个“file:///”地址通常要求客户端和服务端位于同样的物理主机上,除非URL使用其他的文件系统,像NFS,这样才能变得有效。

    通常,那些被用来进行远程方法调用的类,都是通过网络资源访问的,像是HTTP或是FTP服务器。

    2 下载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)                定义的存根类(和其需要的其他类)被下载到客户端。注意:第45是相同的步骤,当远程对象被一个名字绑定到注册表中时,注册表就开始调用该远程对象类。当注册表试着调用远程对象的存根类时,从代码库址(codebase)中,它和远程对象一起请求该类的定义。

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

    如图3所示:

    3 Java RMI 客户端远程方法调用

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

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

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

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

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

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

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

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

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

     

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

    1.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"

     

    1.6. 疑难问题解答

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

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

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

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

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

     

    在使用Java RMIjava.rmi.server.codebase系统变量时,有两种经常性的问题,我们将在下边讨论。

    1.6.1. 运行Java RMI服务端可能碰到的问题

    你碰到的第一个问题可能是收到ClassNotFoundException的异常,当你向注册表绑定(bind或是rebind)一个远程对象和名字时。这种异常通常是由不合法的codebase属性引起的,导致了在注册表中不能定位远程对象的存根(stub)或是存根需要的其他类。

    同远程对象本身相比,远程对象的存根(stub)实现了所有同样的接口,这需要特别的注意。因此这些接口,同其他定制的类作为方法参数或是返回值,也必须能够通过指定的代码库址(codebase)下载。

    通常,由于忽略了属性设定时URL中末尾“/”,导致这个异常的抛出。其他的一些原因可能是:属性值不是一个URLURL路径拼写错误或是不正确设定的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

    1.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)

    1.7. 其他资源

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

    你可能也想加入RMIUSER邮件列表中。

    2. RMI动态下载类分析

    SiteFrom: http://www.diybl.com/course/3_program/java/javaxl/2008829/138460.html

     

    部署一个分布式应用可能会很困难,系统即使只运行其中的一部分,也必须安装所有的相关部分。对于一个局域网来说,这是很消耗时间的,虽然不会很困难。然而,当部署一个大规模应用且更新频繁的时候,部署过程会困难的多。动态下载是集成在RMI中的一项技术,试图使这个应用容易些。

    一:应用遇到的困难

    部署一个应用需要一下步骤:

           1)配置服务器

           2)向名字服务路径增加stub类和其他相关的类和对象

           3)如果一个重新部署和第一次的部署完全相反,那么你可能会重启名字服务并且重新注册

           4)在每一个客户机上安装和配置应用

           简单来说,部署一个web应用不需要改变客户端或者名字服务。取而代之的是,当一个web浏览器下载一个包含applet标签的web页面时,应用只需更少时间而且更容易正确执行。然而,每次你需要更新或者重新部署,不同点变的越来越大。好消息就是,动态下载允许RMI应用动态从http后者ftp下载类定义的字节码。

    二:ClassLoader原理

           Java虚拟机下载有效独立的类,通过使用classloader

    1:动态链接

    每个java源代码被编译为.class文件,每一个.class文件包含了一个单独类的字节码。每一个java类单独编译,运行的时候动态链接。大多数传统编程语言使用的是静态连接模型。 为了生成一个可执行的应用,源代码必须编译,并且所有引用的必须立即解决 。也就是说,各个独立的应用部件必须链接在一起。动态链接尽可能的推迟组合过程。对于运行一个java应用,很有可能,一些引用从来都没有实际用过,因为引用方法没有被调用。

           刚开始,这个看起来没有什么特别。C++程序员经常那个在大脑中有一个hard time。但是如果你想一下,为了使得java正常工作,一些动态链接的的形式很有必要,考虑下面典型应用:

           1)用JDK1.2linux机器写了一个applet

           2)从Macintosh下载一个web页面,appletMRJ内部使用JDK1.1.6执行应用。

           这些应用场景对C++来说是不可能的,链接必须推迟到直到执行。不幸的是,当应用部分更新的时候,动态链接也会出问题。如果你改变了一些.class文件,你可能有两个不能链接一起的文件。

    RMI采用JVM中采用动态类型加载方式,所以概念上只有当一个类型被应用到时,虚拟机才会加载它。比如,假设类C引用了类D,只有当虚拟机执行到C中引用D的语句时才会去加载D 其中有Defining Class Loader Initiating Class LoaderDefining class loader是指真正执行加载某个类的动作的那个类加载器实例。在虚拟机中,一个类型C是由其类型名和定义它的那个defining class loader来唯一标识的,即C=<N, L>Iniating class loader是指发起这个加载动作的类加载器实例。比如在前面的例子中,虚拟机发现C引用D时,就把Cdefining class loader作为Dinitiating class loader。至于D最终是由谁定义的,则要由class loader的代理机制来决定。

    2:常见Class Loader以及其关系

    Bootstrap class loader:虚拟机运行时必须要用到的类的加载器,比如java.*。它通常是在虚拟机种用本地代码实现,在系统中用null表示。

    Extension class loader:负责加载Java环境中安装的可选包(optional package)中的类。

    Application class loader:负责加载CLASSPATH上的类。有时候也称为system class loaderJava程序可以通过调用java.lang.getSystemClassLoader()获得它的引用。

    Class loader之间有两种层次关系:继承关系和代理(delegation)关系。

    bootstrap class loader之外的所有其它class loader都是用Java语言来实现的,所以它们之间自然有继承关系。Java API里面定义的类的继承关系如下:java.net.URLClassLoader --> java.security.SecureClassLoader --> java.lang.ClassLoader --> java.lang.Object

    代理关系是class loader实例之间的动态关系。在创建一个class loader实例时可以给其构造函数传递一个parent参数,这样该parent就成了新创建的class loader在代理层次上的父节点。这个层次树的根节点就是bootstrap class loader实例。Java程序可以通过调用class loader实例的getParent()方法来获取其父节点。比如在JDK1.5中运行HelloWorld程序时class loader的代理继承关系如下:sun.misc.Launcher$AppClassLoader --> sun.misc.Launcher$ExtClassLoader --> null bootstrap class loader)。

    3:代理机制

    当一个class loader收到加载某个类的请求时,它按照下面的流程处理:

    1)查找这个类是不是已经被自己加载过,如果没有则进行下一步;

    2)请求代理层次中的父加载器加载这个类;

    3)如果父加载器无法加载这个类(抛回异常),则尝试自己加载这个类。如果能够加载,则定义该类,否则抛出异常。

    所以在代理层次结构上的子节点永远只加载父节点(及其祖先节点)所不能加载的类,这样可以保证类始终被合适的class loader加载,比如rt.jar中的类永远被bootstrap class loader加载。

    4:类的下载

           1)当JVM第一次启动,一个独立的classloader类,也就是作为引导程序,被创建。这个classloader负责相爱在和创建在JVM内部使用的类。

           2)一个应用可以创建更多的classloader,每一个classloader,除了引导程序classloader,都有一个父classloaderclassloader集合组成了一个树形结构,其中引导类classloader是根结点。

           3)当需要一个类对象,JVM寻找一个合适的classloader并使用loadClass()来获得类对象。classloader首先想其父节点请求类,如果父节点没有运行,则自己创建一个。

           这个想法的背后是,classloader是一个方式来划分类集合以及其定义。例如,如果两个兄弟classloader被要求同样的类,则这个类被初始化两次。

           classloader在两个应用很有用。第一个,当你想从一个不标准的资源下载类对象。引导classloader试图利用CLASSPATH变量从文件系统下载类。它希望一个类的包和名字映射到一个包含类字节码的.class文件。如果你想从不同资源下载类,你需要安装另一个classloader,例如java.net.URLClassLoader.第二个,相同类的不同版本在同一时间运行。因为classloader只是检查其父节点,而不检查其兄弟节点,来检查类是否被下载,所以所以有可能同一个类的不同版本被下载。

    5:动态下载的实现

           RMI动态下载基于两个思想:

           1)如果一个对象被序列化并且通过线路传送,需要的类定义可能在另一边可获得。

           2)在序列化对喜爱那个内部自动从URLS下载类,这样使得特殊目的的classloader,例如URLClassLoader类,在需要并行化一个对象的时候通过线路自动下载类。

    RMI反序列化思想

           1)一个实例通过线路发送。作为一部分对象序列化信息,所有类定义相关的信息被通过线路发送。每一个类还有URL的字符串序列。

           2)反序列化机制为刚描述的类寻找合适的ClassLoader。之所以可以这样是因为,RMI运行时保持了一个URLCassLoader实例集合的索引。

           3)如果类存在,URLClassLoader的实例就返回他们。如果类不存在则URLClassLoader下载他们。

           因为ClassLoader类在试图下载他们之前先向其父节点请求,第一个试图下载的会从文件系统下载。如果引导ClassLoader不能下载类,则类下载仅仅使用URLs


    最新回复(0)