Java RMI 和 ClassNotFoundException
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/11273353/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Java RMI and ClassNotFoundException
提问by BJ Peter DeLaCruz
I am just starting to learn how to use RMI, and I have a question. I have the following directory structure:
我刚刚开始学习如何使用 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
Here's the main
method in the ComputePiclass:
这是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();
}
Here's the main
method in the ComputeEngineclass:
这是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();
}
Here's the executeTask
method, also in the ComputeEngineclass:
这是executeTask
方法,也在ComputeEngine类中:
public <T> T executeTask(Task<T> task) throws RemoteException {
if (task == null) {
throw new IllegalArgumentException("task is null");
}
return task.execute();
}
The RMI registry and server start up just fine. Here are the params for the server:
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
Here are the params for the client:
以下是客户端的参数:
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
However, I get the following exception when I try to run the client:
但是,当我尝试运行客户端时出现以下异常:
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.run(Unknown Source)
at sun.rmi.transport.Transport.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.run(Unknown Source)
at sun.rmi.transport.Transport.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.run(Unknown Source)
at java.net.URLClassLoader.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.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
But if I add the Pi.classfile to the server directory:
但是如果我将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
The program works. My question is, does Pi.classreally need to be on the server for my program to work? My understanding is (and please correct me if I'm wrong) that I send an instance of that class to the server, and the server would know what to do with it, i.e. it doesn't care about the implementation. Can someone explain how RMI is working in my case? I really appreciate it. Thanks!
该程序有效。我的问题是,Pi.class真的需要在服务器上才能让我的程序运行吗?我的理解是(如果我错了,请纠正我)我将那个类的实例发送到服务器,服务器会知道如何处理它,即它不关心实现。有人可以解释一下 RMI 在我的情况下是如何工作的吗?对此,我真的非常感激。谢谢!
采纳答案by Francisco Spaeth
You are trying to send a serialized object of a class that is unknown to the server.
您正在尝试发送服务器未知的类的序列化对象。
When you execute:
执行时:
Pi task = new Pi(Integer.parseInt(args[1]));
BigDecimal pi = comp.executeTask(task);
The server doesn't really know what is Pi
. And since the Pi
class is a part of your API, it should be loaded on server, too.
服务器并不真正知道什么是Pi
. 由于Pi
该类是 API 的一部分,它也应该加载到服务器上。
When I have an application that needs to execute something remotely, using for example RMI
, Spring Remoting or similar, I divide my project in 3 projects: API
, Server and Client. The API project will have all interfaces and model classes relevant to the functionality (this project will result in a jar, and is more or less like your computer JAR). The server will import the API JAR, will implement the interfaces and make the service available through an Remote layer (like you did with your server), and the client as you did with your client.
当我有一个应用程序需要远程执行某些操作时,例如使用RMI
Spring Remoting 或类似工具,我将我的项目分为 3 个项目:API
服务器和客户端。API 项目将包含与功能相关的所有接口和模型类(该项目将生成一个 jar,或多或少类似于您的计算机 JAR)。服务器将导入 API JAR,将实现接口并使服务通过远程层(就像您对服务器所做的那样)和客户端,就像您对客户端所做的那样。
When you work with serialization, the class itself must be known by both sides. What is then transferred is the state of the objects in order to rebuild it on the other side.
当您使用序列化时,双方必须知道类本身。然后传输的是对象的状态,以便在另一侧重建它。
Serialization is the mechanism used by RMI to pass objects between JVMs, either as arguments in a method invocation from a client to a server or as return values from a method invocation.
序列化是 RMI 用于在 JVM 之间传递对象的机制,可以作为从客户端到服务器的方法调用中的参数,也可以作为方法调用的返回值。
A bit of Serialization on RMIBy William Grosso (October 2001). And herea bit more info.
威廉·格罗索 (William Grosso) 的RMI 上的一些序列化(2001 年 10 月)。而且这里多一点信息。
回答by muyong
I tried this example with two PC in same network. One with Java 1.7.0_40 working as server, another with Java 1.7.0_45 as client. Both PCs are Windows based. I met the same problem raised by denshaotoko.
我在同一网络中的两台 PC 上尝试了这个例子。一个使用 Java 1.7.0_40 作为服务器,另一个使用 Java 1.7.0_45 作为客户端。两台PC都是基于Windows的。我遇到了 denshaotoko 提出的同样问题。
The solution is:
解决办法是:
Server side:
服务器端:
C:\>start rmiregistry -J-Djava.rmi.server.useCodebaseOnly=false
The -J-Djava.rmi.server.useCodebaseOnly option is needed for Java 7 since the default value is true, which means the RMI Registry will not look for other code base except the directory it is started from. Then the next step starting the server will fail. Details see here: http://docs.oracle.com/javase/7/docs/technotes/guides/rmi/enhancements-7.html
Java 7 需要 -J-Djava.rmi.server.useCodebaseOnly 选项,因为默认值是 true,这意味着 RMI Registry 不会查找除启动目录之外的其他代码库。那么下一步启动服务器就会失败。详细信息请参见此处: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
Again the java.rmi.server.useCodebaseOnly should be set to false. Otherwise the server won't use the codebase provided by the client. Then client side will get the class not found exception. The hostname of 192.168.1.124 is the server's IP address
再次将 java.rmi.server.useCodebaseOnly 设置为 false。否则服务器将不会使用客户端提供的代码库。然后客户端会得到 class not found 异常。192.168.1.124的主机名是服务器的IP地址
You should get "ComputeEngine bound"
你应该得到“ComputeEngine bound”
Client side:
客户端:
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
I trid the file:/ URL but not successful. I think the reason is simple. There're so many security limits that make the server not possible to access a file on the client PC. So I put the Pi.class file on my web server which is at http://54.200.126.244
under rmi directory. My web server use Apache. Any PC can access http://54.200.126.244/rmi/
so the problem is solved cleanly.
我试过 file:/ URL 但没有成功。我想原因很简单。有太多的安全限制使服务器无法访问客户端 PC 上的文件。所以我把 Pi.class 文件放在我的 web 服务器上,它位于http://54.200.126.244
rmi 目录下。我的网络服务器使用 Apache。任何PC都可以访问,http://54.200.126.244/rmi/
因此问题得到了彻底的解决。
Finally, you should be able to start the rmiregistry, the server and the client from any directory using the same commands. Otherwise, some settings may still not be correct even if you can succeed. For example, if you start rmiregistry from the directory contain the "compute" directory (in my case is C:\rmi), the rmiregistry will directly load Compute.class and Task.class from it's starting directory, so the setting of -Djava.rmi.server.codebase=file:/c:/rmi/compute.jar become useless.
最后,您应该能够使用相同的命令从任何目录启动 rmiregistry、服务器和客户端。否则,即使您能够成功,某些设置也可能仍然不正确。例如,如果您从包含“compute”目录的目录(在我的例子中是C:\rmi)启动rmiregistry,则rmiregistry 将直接从其启动目录加载Compute.class 和Task.class,因此-Djava 的设置.rmi.server.codebase=file:/c:/rmi/compute.jar 变得没用了。
回答by giuseta
My question is, does Pi.class really need to be on the server for my program to work? My understanding is (and please correct me if I'm wrong) that I send an instance of that class to the server, and the server would know what to do with it, i.e. it doesn't care about the implementation.
我的问题是,Pi.class 真的需要在服务器上才能让我的程序运行吗?我的理解是(如果我错了,请纠正我)我将那个类的实例发送到服务器,服务器会知道如何处理它,即它不关心实现。
You understood correctly. Pi.class doesn't need to be on server when you compile it, but the server does need to download it at runtime! (Pi must be serializable)
你理解正确。Pi.class 编译时不需要在服务器上,但服务器确实需要在运行时下载它!(Pi 必须是可序列化的)
The question is: How does a server know where to download the Pi.class when does he need it?
问题是:服务器在需要时如何知道从哪里下载 Pi.class?
And the answer is: by the value of java.rmi.server.codebase setting provided by the client. The client must set the java.rmi.server.codebase option. You have to say where the Pi.class is. It is a common habit to put a copy of Pi.class in a public directory for deployment. Therefore the complete solution is:
答案是:由客户端提供的 java.rmi.server.codebase 设置的值。客户端必须设置 java.rmi.server.codebase 选项。你必须说 Pi.class 在哪里。将 Pi.class 的副本放在公共目录中进行部署是一种常见的习惯。因此,完整的解决方案是:
The Structure:
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
where compute.jar is a jar file previously created
cd C:\Users\Public\RMI\ javac compute\Compute.java compute\Task.java jar cvf compute.jar compute\*.class
Did you set correctly the package and import commands in your java files? Because you modified the original structure of the tutorial...
Compile the Server:
C:\Users\Public\RMI\> cd server C:\Users\Public\RMI\server> javac -cp ..\compute.jar org\examples\rmi\engine\ComputeEngine.java
Compile the Client:
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
Move the Pi.class into the deploy directory
C:\Users\Public\RMI\> cp client\org\examples\rmi\client\Pi.class client\deploy
Run the rmi registry. If you are using java 7 set the option -J-Djava.rmi.server.useCodebaseOnly=false, as suggested by muyong
C:\Users\Public\RMI\> start rmiregistry -J-Djava.rmi.server.useCodebaseOnly=false
Run the server. If you are using java 7 set the option -J-Djava.rmi.server.useCodebaseOnly=false, as suggested by muyong
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
Run the Client. NOTE:watch out the java.rmi.server.codebase setting (REMEMBER the conclusive /)
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
结构:
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 命令?因为你修改了教程原来的结构...
编译服务器:
C:\Users\Public\RMI\> cd server C:\Users\Public\RMI\server> javac -cp ..\compute.jar org\examples\rmi\engine\ComputeEngine.java
编译客户端:
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
将 Pi.class 移动到部署目录中
C:\Users\Public\RMI\> cp client\org\examples\rmi\client\Pi.class client\deploy
运行 rmi 注册表。如果您使用的是 java 7,请按照 muyong 的建议设置选项 -J-Djava.rmi.server.useCodebaseOnly=false
C:\Users\Public\RMI\> start rmiregistry -J-Djava.rmi.server.useCodebaseOnly=false
运行服务器。如果您使用的是 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
运行客户端。注意:注意 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
Let me know if it works!
让我知道它是否有效!
P.s. I don't use Windows OS but Linux, I could have made confusion between '\' and '/'
Ps 我不使用 Windows 操作系统,而是使用 Linux,我可能会混淆 '\' 和 '/'
回答by t_godd
from jdk 1.7 the default value of useCodebaseOnly is true, that mean it wont look for other codebase unless it is in the same directory.
从 jdk 1.7 开始, useCodebaseOnly 的默认值为 true,这意味着它不会查找其他代码库,除非它在同一目录中。
Set this vm argument -Djava.rmi.server.useCodebaseOnly=falsefor running both server and client and also provide the path to codebase and hostname as well. See below below examples.
设置这个 vm 参数-Djava.rmi.server.useCodebaseOnly=false以运行服务器和客户端,并提供代码库和主机名的路径。请参阅下面的示例。
Below is my implementation and it's according to my directory structure. For running in windows replace : (colon) with ; (semi-colon).
下面是我的实现,它是根据我的目录结构。对于在 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
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
回答by harsha madushan
Press Win + R shortcut keys together on your keyboard. This will open the Run dialog. ... Type the following command in the Run box: rundll32.exe sysdm.cpl,EditEnvironmentVariables
同时按下键盘上的 Win + R 快捷键。这将打开运行对话框。... 在运行框中键入以下命令:rundll32.exe sysdm.cpl,EditEnvironmentVariables
then give you current directory
然后给你当前目录
回答by pvbemmelen62
I think the codebase that you specify for the client is not correct:
我认为您为客户端指定的代码库不正确:
-Djava.rmi.server.codebase="file:/C:/Users/Public/RMI/compute.jar"
-Djava.rmi.server.codebase="file:/C:/Users/Public/RMI/compute.jar"
This will not help the server to find client.Pi .
这不会帮助服务器找到 client.Pi 。
The "Java Tutorial RMI" specifies
“Java 教程 RMI”指定
-Djava.rmi.server.codebase=file:/c:/home/jones/public_html/classes/
-Djava.rmi.server.codebase=file:/c:/home/jones/public_html/classes/
and that's the dir under which there is client/Pi.class (i.e. if one follows the tutorial, where "jones" wrote the client).
这就是 client/Pi.class 所在的目录(即,如果按照教程进行操作,“jones”在其中编写了客户端)。
Unfortunately, even when following my own advice, meaning in my situation specifying
不幸的是,即使遵循我自己的建议,这意味着在我的情况下指定
-Djava.rmi.server.codebase=file:/h:/rmi-example/jones/src/
-Djava.rmi.server.codebase=file:/h:/rmi-example/jones/src/
when I start the client, I get the same exception as you do.
当我启动客户端时,我遇到与您相同的异常。
Haven't solved it yet. I'm hoping java option -verbose:class
will shed some light on the problem.
还没解决呢 我希望 java 选项-verbose:class
能够解决这个问题。