【问题标题】:Java RMI and ClassNotFoundExceptionJava RMI 和 ClassNotFoundException
【发布时间】:2012-07-01 16:01:48
【问题描述】:

我刚刚开始学习如何使用 RMI,我有一个问题。我有以下目录结构:

compute.jar
client
     |
     org\examples\rmi\client
                           |--> ComputePi     // client main
                           |--> Pi            // implements Task
     org\examples\rmi\compute
                           |--> Compute       // interface
                           |--> Task          // interface

server
     |
     org\examples\rmi\engine
                           |--> ComputeEngine // server main, implements Compute
     org\examples\rmi\compute
                           |--> Compute       // interface
                           |--> Task          // interface

这是 ComputePi 类中的main 方法:

if (System.getSecurityManager() == null) {
  System.setSecurityManager(new SecurityManager());
}
try {
  String name = "Compute";
  // args[0] = 127.0.0.1, args[1] is irrelevant
  Registry registry = LocateRegistry.getRegistry(args[0], 0);
  Compute comp = (Compute) registry.lookup(name);
  Pi task = new Pi(Integer.parseInt(args[1]));
  BigDecimal pi = comp.executeTask(task);
  System.out.println(pi);
}
catch (Exception e) {
  System.err.println("ComputePi exception:");
  e.printStackTrace();
}

这是 ComputeEngine 类中的 main 方法:

if (System.getSecurityManager() == null) {
  System.setSecurityManager(new SecurityManager());
}
try {
  String name = "Compute";
  Compute engine = new ComputeEngine();
  Compute stub = (Compute) UnicastRemoteObject.exportObject(engine, 0);
  Registry registry = LocateRegistry.getRegistry();
  registry.rebind(name, stub);
  System.out.println("ComputeEngine bound.");
}
catch (Exception e) {
  System.err.println("ComputeEngine exception: ");
  e.printStackTrace();
}

这是 executeTask 方法,也在 ComputeEngine 类中:

  public <T> T executeTask(Task<T> task) throws RemoteException {
    if (task == null) {
      throw new IllegalArgumentException("task is null");
    }
    return task.execute();
  }

RMI 注册表和服务器启动正常。以下是服务器的参数:

C:\Users\Public\RMI\server>set CLASSPATH=
C:\Users\Public\RMI\server>start rmiregistry
C:\Users\Public\RMI\server>java -Djava.rmi.server.codebase="file:/C:/Users/Public/RMI/compute.jar" -Djava.rmi.server.hostname=127.0.0.1 -Djava.security.policy=server.policy org.examples.rmi.engine.ComputeEngine

这是客户端的参数:

C:\Users\Public\RMI\client>java -Djava.rmi.server.codebase="file:/C:/Users/Public/RMI/compute.jar" -Djava.security.policy=client.policy org.examples.rmi.client.ComputePi 127.0.0.1 45

但是,当我尝试运行客户端时,出现以下异常:

java.rmi.ServerException: RemoteException occurred in server thread; nested exception is:
        java.rmi.UnmarshalException: error unmarshalling arguments; nested exception is:
        java.lang.ClassNotFoundException: org.examples.rmi.client.Pi
        at sun.rmi.server.UnicastServerRef.dispatch(Unknown Source)
        at sun.rmi.transport.Transport$1.run(Unknown Source)
        at sun.rmi.transport.Transport$1.run(Unknown Source)
        at java.security.AccessController.doPrivileged(Native Method)
        at sun.rmi.transport.Transport.serviceCall(Unknown Source)
        at sun.rmi.transport.tcp.TCPTransport.handleMessages(Unknown Source)
        at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(Unknown Source)
        at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(Unknown Source)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
        at java.lang.Thread.run(Unknown Source)
        at sun.rmi.transport.StreamRemoteCall.exceptionReceivedFromServer(Unknown Source)
        at sun.rmi.transport.StreamRemoteCall.executeCall(Unknown Source)
        at sun.rmi.server.UnicastRef.invoke(Unknown Source)
        at java.rmi.server.RemoteObjectInvocationHandler.invokeRemoteMethod(Unknown Source)
        at java.rmi.server.RemoteObjectInvocationHandler.invoke(Unknown Source)
        at $Proxy0.executeTask(Unknown Source)
        at org.examples.rmi.client.ComputePi.main(ComputePi.java:38)
Caused by: java.rmi.UnmarshalException: error unmarshalling arguments; nested exception is:
        java.lang.ClassNotFoundException: org.examples.rmi.client.Pi
        at sun.rmi.server.UnicastServerRef.dispatch(Unknown Source)
        at sun.rmi.transport.Transport$1.run(Unknown Source)
        at sun.rmi.transport.Transport$1.run(Unknown Source)
        at java.security.AccessController.doPrivileged(Native Method)
        at sun.rmi.transport.Transport.serviceCall(Unknown Source)
        at sun.rmi.transport.tcp.TCPTransport.handleMessages(Unknown Source)
        at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(Unknown Source)
        at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(Unknown Source)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
        at java.lang.Thread.run(Unknown Source)
Caused by: java.lang.ClassNotFoundException: org.examples.rmi.client.Pi
        at java.net.URLClassLoader$1.run(Unknown Source)
        at java.net.URLClassLoader$1.run(Unknown Source)
        at java.security.AccessController.doPrivileged(Native Method)
        at java.net.URLClassLoader.findClass(Unknown Source)
        at java.lang.ClassLoader.loadClass(Unknown Source)
        at java.lang.ClassLoader.loadClass(Unknown Source)
        at java.lang.Class.forName0(Native Method)
        at java.lang.Class.forName(Unknown Source)
        at sun.rmi.server.LoaderHandler.loadClass(Unknown Source)
        at sun.rmi.server.LoaderHandler.loadClass(Unknown Source)
        at java.rmi.server.RMIClassLoader$2.loadClass(Unknown Source)
        at java.rmi.server.RMIClassLoader.loadClass(Unknown Source)
        at sun.rmi.server.MarshalInputStream.resolveClass(Unknown Source)
        at java.io.ObjectInputStream.readNonProxyDesc(Unknown Source)
        at java.io.ObjectInputStream.readClassDesc(Unknown Source)
        at java.io.ObjectInputStream.readOrdinaryObject(Unknown Source)
        at java.io.ObjectInputStream.readObject0(Unknown Source)
        at java.io.ObjectInputStream.readObject(Unknown Source)
        at sun.rmi.server.UnicastRef.unmarshalValue(Unknown Source)
        ... 11 more

但是如果我将 Pi.class 文件添加到服务器目录:

server
     |
     org\examples\rmi\engine
                           |--> ComputeEngine // server main, implements Compute
     org\examples\rmi\compute
                           |--> Compute       // interface
                           |--> Task          // interface
     org\examples\rmi\client
                           |--> Pi            // same as Pi for client

该程序有效。我的问题是,Pi.class 真的需要在服务器上才能让我的程序运行吗?我的理解是(如果我错了,请纠正我)我将该类的一个实例发送到服务器,服务器会知道如何处理它,即它不关心实现。有人可以解释 RMI 在我的情况下是如何工作的吗?对此,我真的非常感激。谢谢!

【问题讨论】:

    标签: java client rmi


    【解决方案1】:

    我在同一网络中的两台 PC 上尝试了这个示例。一个使用 Java 1.7.0_40 作为服务器,另一个使用 Java 1.7.0_45 作为客户端。两台 PC 均基于 Windows。我遇到了 denshaotoko 提出的同样问题。

    解决办法是:

    服务器端:

    C:\>start rmiregistry -J-Djava.rmi.server.useCodebaseOnly=false
    

    Java 7 需要 -J-Djava.rmi.server.useCodebaseOnly 选项,因为默认值为 true,这意味着 RMI 注册表不会查找除了它启动的目录之外的其他代码库。那么下一步启动服务器就会失败。详情见这里:http://docs.oracle.com/javase/7/docs/technotes/guides/rmi/enhancements-7.html

    C:\>java -cp c:\rmi;c:\rmi\compute.jar -Djava.rmi.server.useCodebaseOnly=false -Djava.rmi.server.codebase=file:/c:/rmi/compute.jar -Djava.rmi.server.hostname=192.168.1.124 -Djava.security.policy=c:\rmi\server.policy engine.ComputeEngine
    

    java.rmi.server.useCodebaseOnly 应该再次设置为 false。否则服务器不会使用客户端提供的代码库。然后客户端将得到类未找到异常。 192.168.1.124的主机名是服务器的IP地址

    你应该得到“ComputeEngine bound”

    客户端:

    C:\>java -cp c:\rmi;c:\rmi\compute.jar -Djava.rmi.server.codebase=http://54.200.126.244/rmi/ -Djava.security.policy=c:\rmi\client.policy client.ComputePi 192.168.1.124 45 
    

    我尝试了 file:/ URL 但没有成功。我认为原因很简单。有太多的安全限制使服务器无法访问客户端 PC 上的文件。所以我把 Pi.class 文件放在我的网络服务器上,它位于 rmi 目录下的http://54.200.126.244。我的网络服务器使用 Apache。任何电脑都可以访问http://54.200.126.244/rmi/,问题就解决了。

    最后,您应该能够使用相同的命令从任何目录启动 rmiregistry、服务器和客户端。否则,即使你能成功,某些设置可能仍然不正确。例如,如果您从包含“compute”目录的目录启动 rmiregistry(在我的情况下是 C:\rmi),则 rmiregistry 将直接从其起始目录加载 Compute.class 和 Task.class,因此 -Djava 的设置.rmi.server.codebase=file:/c:/rmi/compute.jar 变得没用了。

    【讨论】:

    • 哇,这救了我的命。我刚刚遇到了同样的问题,指定java.rmi.server.useCodebaseOnly=false 就成功了。
    【解决方案2】:

    您正在尝试向服务器发送未知的类的序列化对象。

    当你执行时:

      Pi task = new Pi(Integer.parseInt(args[1]));
      BigDecimal pi = comp.executeTask(task);
    

    服务器并不真正知道Pi 是什么。由于Pi 类是 API 的一部分,它也应该加载到服务器上。

    当我有一个需要远程执行某事的应用程序时,例如使用 RMI、Spring Remoting 或类似的,我将我的项目分为 3 个项目:API、服务器和客户端。 API 项目将具有与功能相关的所有接口和模型类(此项目将生成一个 jar,并且或多或少类似于您的计算机 JAR)。服务器将导入 API JAR,实现接口并通过远程层提供服务(就像您对服务器所做的那样),而客户端就像您对客户端所做的那样。

    当您使用序列化时,双方都必须知道类本身。然后传输的是对象的状态,以便在另一端重建它。

    序列化是 RMI 用来在对象之间传递对象的机制 JVM,或者作为从客户端到客户端的方法调用中的参数 服务器或作为方法调用的返回值。

    A bit of Serialization on RMI威廉格罗索(2001 年 10 月)。还有here 更多信息。

    【讨论】:

    • 请注意,Grosso 关于 Java 数组不可序列化的说法是不正确的。
    • 非常感谢您的回复,Spaeth!
    • 我花了一段时间才弄清楚这个答案的主要含义。这个想法不是把 Pi.class 放在服务器上,而是把它放在服务器可以得到它的地方。我的问题是,为什么提供的文件:客户端代码库的 URL 不够好?服务器是尝试自行检索实现类定义还是要求客户端提供服务?
    • 我不确定我是否有你的问题,但这里的问题是在(客户端和服务器)上都有接口(一种掩码),客户端基本上会与服务器通信某些方法以服务器可以调用目标对象的方法的方式调用。接口定义需要同时存在(客户端创建代理或存根,服务器实现它,最终将用于实例化骨架)。
    • @FranciscoSpaeth 代码库用于动态加载类定义本身,而不仅仅是对象。因此 Pi.class 的定义不需要在服务器端预先知道,而是从客户端定义的代码库中动态下载。这不是真的吗?
    【解决方案3】:

    我的问题是,Pi.class 真的需要在服务器上才能让我的程序运行吗?我的理解是(如果我错了,请纠正我)我将该类的一个实例发送到服务器,服务器会知道如何处理它,即它不关心实现。

    你理解正确。 Pi.class 编译时不需要在服务器上,但服务器确实需要在运行时下载它! (Pi 必须是可序列化的)

    问题是:服务器如何知道何时需要下载 Pi.class?

    答案是:通过客户端提供的 java.rmi.server.codebase 设置的值。客户端必须设置 java.rmi.server.codebase 选项。你必须说 Pi.class 在哪里。将 Pi.class 的副本放在公共目录中进行部署是一种常见的习惯。因此完整的解决方案是:

    1. 结构:

      compute.jar
      client\
      |-org\
      |   |-examples\
      |       |-rmi\
      |           |client\
      |               |--> ComputePi     // client main
      |               |--> Pi            // implements Task
      |-deploy\            
      |   |-org\
      |       |-examples\
      |           |-rmi\
      |               |-client\ // directory that will contain the deployment copy of Pi.class
      |--> client.policy  
      
      server\
      |-org\
      |   |-examples\
      |       |-rmi\
      |           |-engine\
      |               |--> ComputeEngine // server main, implements Compute
      |--> server.policy
      

      其中 compute.jar 是之前创建的 jar 文件

      cd C:\Users\Public\RMI\
      javac compute\Compute.java compute\Task.java
      jar cvf compute.jar compute\*.class
      

      您是否在您的 java 文件中正确设置了 package 和 import 命令?因为你修改了教程的原始结构...

    2. 编译服务器:

      C:\Users\Public\RMI\> cd server
      C:\Users\Public\RMI\server> javac -cp ..\compute.jar org\examples\rmi\engine\ComputeEngine.java
      
    3. 编译客户端:

      C:\Users\Public\RMI\> cd client
      C:\Users\Public\RMI\client> javac -cp ..\compute.jar org\examples\rmi\client\ComputePi.java org\examples\rmi\client\Pi.java
      
    4. 将 Pi.class 移动到部署目录

      C:\Users\Public\RMI\> cp client\org\examples\rmi\client\Pi.class client\deploy
      
    5. 运行 rmi 注册表。如果您使用的是 java 7,请按照 muyong 的建议设置选项 -J-Djava.rmi.server.useCodebaseOnly=false

      C:\Users\Public\RMI\> start rmiregistry -J-Djava.rmi.server.useCodebaseOnly=false
      
    6. 运行服务器。如果您使用的是 java 7,请按照 muyong 的建议设置选项 -J-Djava.rmi.server.useCodebaseOnly=false

      C:\Users\Public\RMI\> cd server
      C:\Users\Public\RMI\server> java -cp .;..\compute.jar 
          -Djava.rmi.server.useCodebaseOnly=false 
          -Djava.rmi.server.codebase=file:/c:/Users/Public/RMI/compute.jar
          -Djava.rmi.server.hostname=127.0.0.1
          -Djava.security.policy=server.policy
          org.examples.rmi.engine.ComputeEngine
      
    7. 运行客户端。 注意:注意 java.rmi.server.codebase 设置(记住最后的 /)

      C:\Users\Public\RMI\> cd client
      C:\Users\Public\RMI\client> java -cp .;..\compute.jar
          -Djava.rmi.server.codebase=file:/c:/Users/Public/RMI/client/deploy/
          -Djava.security.policy=client.policy
          org.examples.rmi.client.Compute.Pi 127.0.0.1 45
      

    让我知道它是否有效!

    附:我不使用 Windows 操作系统,但使用 Linux,我可能会混淆 '\' 和 '/'

    【讨论】:

      【解决方案4】:

      从 jdk 1.7 开始,useCodebaseOnly 的默认值为 true,这意味着它不会寻找其他代码库,除非它在同一目录中。

      设置此 vm 参数 -Djava.rmi.server.useCodebaseOnly=false 以同时运行服务器和客户端,并提供代码库和主机名的路径。请参见下面的示例。

      以下是我的实现,它根据我的目录结构。对于在 Windows 中运行,将 : (冒号) 替换为 ; (分号)。

      java -cp classes:classes/compute.jar -Djava.rmi.server.useCodebaseOnly=false -Djava.rmi.server.codebase=url:http://localhost:4800/ -Djava.rmi.server.hostname=localhost -Djava.security.policy=client.policy client.ComputePi localhost 45

      【讨论】:

      • 这应该被标记为正确答案。还值得一提的是,Oracle 的 RMI 教程忽略了这一点,这使得事情变得非常困难。这就是为什么这么多人遇到同样的问题。
      【解决方案5】:

      我认为您为客户端指定的代码库不正确:

      -Djava.rmi.server.codebase="file:/C:/Users/Public/RMI/compute.jar"

      这不会帮助服务器找到client.Pi。

      《Java 教程 RMI》规定

      -Djava.rmi.server.codebase=file:/c:/home/jones/public_html/classes/

      这就是 client/Pi.class 所在的目录(即,如果有人遵循教程,“jones”在其中编写了客户端)。

      不幸的是,即使遵循我自己的建议,这意味着在我的情况下指定

      -Djava.rmi.server.codebase=file:/h:/rmi-example/jones/src/

      当我启动客户端时,我得到了和你一样的异常。

      还没解决。我希望 java 选项 -verbose:class 能解决这个问题。

      【讨论】:

      • 我发现了导致 client.Pi 的 ClassNotFoundException 的原因:我使用 java 7 启动服务器,而客户端使用 java 6 。如果我启动注册表、客户端和服务器,全部使用 java 6 就可以了。
      • 代码库项可以是目录或 JAR 文件。
      【解决方案6】:

      在键盘上同时按下 Win + R 快捷键。这将打开“运行”对话框。 ... 在运行框中键入以下命令:rundll32.exe sysdm.cpl,EditEnvironmentVariables

      然后给你当前目录

      【讨论】:

        猜你喜欢
        • 2011-11-15
        • 2017-01-16
        • 2014-06-14
        • 2023-03-21
        • 2016-06-15
        • 1970-01-01
        • 2018-04-22
        相关资源
        最近更新 更多