RMI开发中的codebase

来源:百度文库 编辑:神马文学网 时间:2024/04/29 22:24:32
jini和RMI开发中的codebase问题 作者:未知     文章来源:www.jspcn.net
访问次数:422次     加入时间:2005年01月19日

codebase问题其实是一个怎样从网络上下载类的问题,我想不只是在Jini和RMI程序开发中要用到。只要需要从网络上下载类,就要涉及到codebase问题。例如applet等。但是因为我对applet程序不是很熟悉,所以我就只谈Jini和RMI,但我想codebase问题应该是通用的。

毫无疑问,对于大多数Jini和RMI开发新手来说,如何使用codebase是比较容易令人迷惑的一件事。(我的一位师妹就很痛苦过,而且怀疑这个是不是sun的一个骗局:P)下面我就讲讲codebase要注意的几个方面,也就是容 易出问题的地方。

一、为什么需要codebase

当我们用一个对象作为远程方法调用的参数时,对象是以序列化流来传输到远端,然后在远端重新生成对象。这样就可能在两个Java虚拟机中交换对象了。但是序列化是这种传递对象的一部分。当你序列化对象时,你仅仅是把对象的成员数据转化成字节流,而实际实现该对象的代码却没有。也就是说,传递的只是数据部分,而做为控制逻辑的程序代码部分却没有被传递。这就是RMI初学者容易误解的地方,我已经序列化对象了,而且对象也传过去了,怎么还说找不到呢。其实,对象数据的确过去了,不过找不到是类定义,这个并不是序列化传过去的,RMI协议是不传递代码的。但是,对于本地没有的类文件的对象,RMI提供了一些机制允许接收对象的一方去取回该对象的类代码。而到什么地方去取,这就需要发送方设置codebase了。

二、什么是codebase

简单说,codebase就是远程装载类的路径。当对象发送者序列化对象时,会在序列化流中附加上codebase的信息。 这个信息告诉接收方到什么地方寻找该对象的执行代码。

你要弄清楚哪个设置codebase,而哪个使用codebase。任何程序如果发送一个对方可能没有的新类对象时就要设置codebase(例如jdk的类对象,就不用设置codebase)。

codebase实际上是一个url表,在该url下有接受方需要下载的类文件。如果你不设置codebase,那么你就不能把一个对象传递给本地没有该对象类文件的程序。

三、怎样设置codebase

在大多数情况下,你可以在命令行上通过属性java.rmi.server.codebase来设置codebase。例如,如果你在机器url上运行web服务器,端口是8080,你所提供下载的类文件在webserver的根目录下。那么运行应用程序的java 命令行:


-Djava.rmi.server.codebase=http://url:8080/

这就是告诉任何接受对象的程序,如果本地没有类文件,可以从这个url下载。

四、类文件应该在什么地方

如上所示,当接收程序试图从url的webserver上下载代码时,它会把对象的包名转化成目录,到相对于codebase 的该目录下寻找(这点和classpath是一样的)。例如,如果你传递的是类文件yourgroup.project.bean的实例,那么接受方就会到下面的url去下载类文件:


-Djava.rmi.server.codebase=http://url:8080/yourgroup/project/bean.class

一定要保证你把类文件放到webserver根目录下的正确位置,这样这些类文件才能被找到。另一方面,如果你把所有的类文件包装成jar文件,那么设置codebase时就要明确的指出这个jar文件。(这又和 classpath一致了,其实codebase就是网络范围的类路径。)例如你的jar文件是myclasses.jar,那么codebase 如下:


-Djava.rmi.server.codebase=http://url:8080/myclasses.jar

你注意这两种形式的不同。用jar文件后面不用跟‘/’,而用路径的一定用跟‘/’。


=============================================================   请问在RMI程序中怎样设置java.rmi.server.codebase

我在试验RMI程序的时候,不知道该怎样设置java.rmi.server.codebase,具体如下:
PiRemote.java代码:
package com.prefect.pi;
import java.rmi.*;
interface PiRemote extends Remote
{
  double getPi() throws RemoteException;
}

Pi.java代码:
package com.prefect.pi;
import  java.net.*;
import  java.rmi.*;
import  java.rmi.registry.*;
import  java.rmi.server.*;
public  class Pi extends UnicastRemoteObject implements PiRemote
{
  public double getPi() throws RemoteException
  {
    return Math.PI;
  }

  public Pi() throws RemoteException
  {
  }

  public static void main(String[] arguments)
  {
    System.setSecurityManager(new RMISecurityManager());
    try
    {
      Pi p=new Pi();
      Naming.bind("Default:1010/Pi",p);
    } catch (Exception e)
      {
        System.out.println("Error---"+e.toString());
        e.printStackTrace();
      }
  }
}

OutputPi.java代码:
package com.prefect.pi;
import  java.rmi.*;
import  java.rmi.registry.*;
public  class OutputPi
{
  public static void main(String[] arguments)
  {
    System.setSecurityManager(new RMISecurityManager());
    try
    {
      PiRemote pr=(PiRemote)Naming.lookup("//Default:1010/Pi";
      for (int i=0; i<10; i++)
      {
        System.out.println("Pi="+pr.getPi());
      }
    } catch(Exception e)
      {
        System.out.println("Error---"+e.toString());
        e.printStackTrace();
      }
  }
}


我的PiRemote.java,Pi.java,OutputPi.java都存放在c:\test\com\prefect\pi下面,我的CLASSPATH中包含c:\test.
现在我已经生成了它们的class文件,并通过rmic com.prefect.pi.Pi在c:\test\com\prefect\pi下面生成了Pi_Stub.class和Pi_Skel.class;同时我通过start rmiregistry 1010启动了RMI注册,然后通过java Pi运行了服务器程序。接下来要指出在哪里能找到跟应用程序相关的所有类文件,这是通过设置java.rmi.server.codebase来完成的,我试着设置为:
C:\test>java -D java.rmi.server.codebase=com.prefect.pi.Pi
Exception in thread "main" java.lang.NoClassDefFoundError: java/rmi/server/codeb
ase=com/prefect/pi/Pi

C:\test>java -D java.rmi.server.codebase=c:\test\com.prefect.pi.Pi
Exception in thread "main" java.lang.NoClassDefFoundError: java/rmi/server/codeb
ase=c:\test\com/prefect/pi/Pi

C:\test>java -D java.rmi.server.codebase=c:\test\com\prefect\pi\com.prefect.pi.P
i
Exception in thread "main" java.lang.NoClassDefFoundError: java/rmi/server/codeb
ase=c:\test\com\prefect\pi\com/prefect/pi/Pi


C:\test>java -D java.rmi.server.codebase=c:\test\com\prefect\pi\Pi
Exception in thread "main" java.lang.NoClassDefFoundError: java/rmi/server/codeb
ase=c:\test\com\prefect\pi\Pi
都不对,请问该如何设置才能正确运行呢?谢谢!




__________________
step by step 只看该作者    chen09
中级会员


精华贴数 0
个人空间 0
技术积分 454 (4582)
社区积分 5 (16112)
注册日期 2002-2-14
论坛徽章:0
                       

 

#2 使用道具   发表于 2002-3-2 03:44  file:/c:\test\com\prefect\pi\Pi
或者是file:///c:\test....

这是个概念问题,如果你可以理解为什么要file:///,那么你对java的认识又深了一层。
因为这里要求的网络的资源,事实上你的class文件完全可能在远端,比如在unix,可能会这样:http://ulr/test/test.class
如果不是远端,怎么能叫远程方法调用呢?
俺记得有人问appletviewer的用法,不知道是不是你,正确的是appletvierer file:///c:\web\test.html
总之,所有可以要求远端的东西,而不是本地的都是这个格式。
看来你在自学java,如果你学到了XML和java的应用,也会有这个问题。
XML中的DTD文件指定,也是远端的:< dtd = http://host/..../test.dtd >
如果你把那个dtd下载到本地,也可以用:file:///c:\dtd\test.dtd
=============================================================================== 再问RMI中的codebase属性的相关配置及相关问题
发表于: 2006-3-20 下午6:42   回复 在发帖之前搜索了一下,也看了一些关于RMI的使用的讨论,可是这些帖子都是很早的了,差不多都在2003年以前的了.所以想再次提起这个问题问一下.
★这个codebase属性到底指明了什么东西?是指明了这个rmiregistry服务应该去哪里找client程序所需要的类吗?还是别的什么东西?我使用的例子如下:

一下的例子都是基于这个tutorial说明的。
http://java.sun.com/docs/books/tutorial/rmi/index.html

##首先创建了两个接口:Compoute和Task。
package compute;

import java.rmi.Remote;
import java.rmi.RemoteException;

public interface Compute extends Remote {
Object executeTask(Task t) throws RemoteException;
}

package compute;

import java.io.Serializable;

public interface Task extends Serializable {
Object execute();
}

然后ComputeEngine实现了这个接口:
package engine;

import java.rmi.*;
import java.rmi.server.*;
import compute.*;

public class ComputeEngine extends UnicastRemoteObject
implements Compute
{
public ComputeEngine() throws RemoteException {
super();
}

public Object executeTask(Task t) {
  System.out.println("got a task.");
return t.execute();
}

public static void main(String[] args) {
if (System.getSecurityManager() == null) {
System.setSecurityManager(new RMISecurityManager());
}
String name = "//"args[0]"/Compute";
try {
Compute engine = new ComputeEngine();
Naming.rebind(name, engine);
System.out.println("ComputeEngine bound");
} catch (Exception e) {
System.err.println("ComputeEngine exception: " + e.getMessage());
e.printStackTrace();
}
}
}

这样server端的工作算是做完了。

在client端用类Pi实现了一个Task接口:
package client;

import compute.*;
import java.math.*;

public class Pi implements Task {

/** constants used in pi computation */
private static final BigDecimal ZERO =
BigDecimal.valueOf(0);
private static final BigDecimal ONE =
BigDecimal.valueOf(1);
private static final BigDecimal FOUR =
BigDecimal.valueOf(4);

/** rounding mode to use during pi computation */
private static final int roundingMode =
BigDecimal.ROUND_HALF_EVEN;

/** digits of precision after the decimal point */
private int digits;

/**
* Construct a task to calculate pi to the specified
* precision.
*/
public Pi(int digits) {
this.digits = digits;
}

/**
* Calculate pi.
*/
public Object execute() {
return computePi(digits);
}
…………
…………
}

再创建了一个ComputePi的类来使用远程接口:
package client;

import java.rmi.*;
import java.math.*;
import compute.*;

public class ComputePi {
public static void main(String args[]) {
if (System.getSecurityManager() == null) {
System.setSecurityManager(new RMISecurityManager());
}
try {
String name = "//" + args[0] + "/Compute";
System.out.println("try to look up the Registry...");
Compute comp = (Compute) Naming.lookup(name);
System.out.println("Remote access succeed");
Pi task = new Pi(Integer.parseInt(args[1]));
BigDecimal pi = (BigDecimal) (comp.executeTask(task));
System.out.println(pi);
} catch (Exception e) {
System.err.println("ComputePi exception: " + e.getMessage());
e.printStackTrace();
}
}
}

我原本认为作为远程的方法调用,那么只要对client发布Compute和Task接口就可以了,于是在client端我的文件组织如下:
g:\classes\ 下有:client\Pi.class ,client\ComputePi.class; compute\Compute.class ,compute\Task.class;
而在server端我的文件组织如下:
c:\classes\ 下有:engine\ComputeEngine_Stub.class,ComputeEngine_Skel.class; C:\app下有:compute\Compute.class ,compute\Task.class; engine\ComputeEngine.class

然后在两台机器之间做实验:server:192.168.14.113 ;client:192.168.14.118均为Windows
在server上的操作为:
>start rmiregistry //这时没有classpath环境变量
>set classpath=c:\app
> java -Djava.rmi.server.codebase=file:///C:\classes/ -Djava.security.policy=java.policy engne.ComputeEngine 192.168.14.113 //最后的参数是用来指明rmiregistry所在的机器,用在 String name = "//"args[0]"/Compute"; …… Naming.rebind(name, engine);
java.policy文件如下:
grant {
permission java.net.SocketPermission "*:1024-65535",
"connect,accept";
permission java.net.SocketPermission "*:80", "connect";
};
然后再client端操作如下:
>set classpath=g:\classes
>java -Djava.rmi.server.codebase=file:///g:\classes/ -Djava.security.policy=java.policy client.ComputePi 192.168.14.113 15//两个参数,第一个是指明rmiregistry的位置,第二个指明Pi的计算精度。

如此执行下来之后,结果server端可以起来,但是client端抛出异常,提示找不到engine.ComputeEngine_Stub,可是如果把ComputeEngine_Stub.class拷贝到client的 g:\classes\engine目录下,通过为调试而加入的语句,知道已经可以访问到CmputeEngnie这个类了,可是有提示 client.Pi找不到,可是client.Pi明明就在client的codebase里,

就以上的实验,有几个疑问:
1.在server的codebase中已经包含了ComputeEngine_Stub,可是为什么不能自己下载到client呢?是因为我的java.policy文件有问题吗?
2.既然Pi这个类的对象也要传递到server端,那么为什么这个类却不需要通过rmic来编译一下?
3.policy文件在使用有没有放在什么目录下的位置限制?

再后来我就在server和client上启动了IIS,然后将两个机器上的classes文件加分别拷贝到各自的网页根目录下,然后在server的codebase中使用了HTTP://192.168.14.118/classes/ 在client的codebase上使用了HTTP://192.168.14.113/classes/ 这样终于成功地完整执行了一次。

这样看来,难道RMI的运行还必须得借助HTTP服务?可是在JDK 文档中RMI的FAQ中说明不必需要HTTP服务的,内容如下:
A.2 Do I have to install the _Stub file in the client's CLASSPATH? I thought it could be downloaded.
A stub class can be downloaded, if the server that is exporting the remote object annotates the marshalled stub instance with the java.rmi.server.codebase property, which indicates the location from where the stub class can be loaded. You should set the java.rmi.server.codebase property on the server exporting a remote object. While remote clients could set this property, they would then be limited to only getting remote objects from the specified codebase. You should not assume that any client VM will have specified a codebase that resolves to the location of your object.
When a remote object is marshalled by Java RMI (whether as an argument to a remote call or as a return value), the codebase for the stub class is retrieved by Java RMI and used to annotate the serialized stub. When the stub is unmarshalled, the codebase is used to load the stub classfile using the RMIClassLoader, unless the class can already be found in the CLASSPATH or by the context classloader for the receiving object, such as an applet codebase.

If the _Stub class was loaded by an RMIClassLoader, then Java RMI already knows which codebase to use for its annotation. If the _Stub class was loaded from the CLASSPATH, then there is no obvious codebase, and Java RMI consults the java.rmi.server.codebase system property to find the codebase. If the system property is not set, then the stub is marshalled with a null codebase, which means that it cannot be used unless the client has a matching copy of the _Stub classfile in the client's CLASSPATH.

It is easy to forget to specify the codebase property. One way to detect this error is to start the rmiregistry separately and without access to the application classes. This will force Naming.rebind to fail if the codebase is omitted.

这段话我没有看明白,到底是不是只要在server的codebase中包含了ComputeEngine_Stub就可以了呢?
到底是我的codebase给的不对呢还是policy文件写得有问题?抑或是别的问题?

如果有对这一部分熟悉的programmer,希望能对于这个例子,给出详细一点的解释,最好能给出正确的执行命令,如果我上面说的不清楚,那就请具体指出,我在加以叙述。