1

我正在尝试在 Java Swing 客户端和 Tomcat 服务器之间实现 RMI 工作流。我遇到的困难是创建插件架构,以便购买我们产品的用户可以将新的 jar 文件放入服务器上的插件文件夹中,然后让这些类对客户端和服务器。

现在,我的客户端部分正在工作:它向服务器询问插件 URL 列表,服务器扫描插件文件夹并返回与它在那里找到的 jar 文件相对应的 URL,然后客户端使用网络类加载器使用这些提供的 URL 来动态加载插件类。

当客户端通过使用插件类的 RMI 向服务器发送对象时,就会出现问题。服务器的类路径中没有这些类(因为它们只是放在硬盘驱动器上的一个文件夹中,而不是在 webapp 或 Tomcat 文件夹中),因此它会引发错误。我需要为此找到解决方案。

有一些限制需要注意:

  • 我们不希望用户在将新插件放入服务器上的插件文件夹时必须重新启动 Tomcat。

  • 我们不想更改 Tomcat 安装本身的任何内容,例如启动参数或其他 jar 文件,因为我们的解决方案需要由已经运行自己的 Tomcat 的客户安装。他们只是想把我们的产品放到他们的 webapp 目录中然后去。

以下是我尝试过的一些潜在解决方案,但没有奏效:

  • 我无法使用服务器上的 'java.rmi.server.codebase' 系统属性来解决问题,因为插件 jar 列表在运行时会发生变化,并且此属性在启动时会被缓存。我也希望不必安装此方法所需的 SecurityManager。

  • 定义我们自己的类加载器并设置“java.rmi.server.RMIClassLoaderSpi”系统属性看起来正是我们需要的,但是当使用这种方法时,类加载器需要由系统类加载器加载。因为我们在使用 WebAppClassLoaders 的 Tomcat 中运行,所以这不起作用。

  • 我考虑过使用反射和蛮力将我们的自定义类加载器设置为 RMIClassLoader 类中的“defaultProvider”私有静态变量,但它是最终的,所以我假设我不能在上面设置一个新值(实际上还没有测试)。

[更新 - 显示来自服务器的堆栈跟踪] 这是当客户端尝试在对服务器的 RMI 调用中使用插件类之一时服务器产生的堆栈跟踪:

Jun 19, 2013 7:20:58 AM sun.rmi.server.UnicastServerRef logCallException
FINE: RMI TCP Connection(4)-192.168.1.107: [192.168.1.107] exception: 
java.rmi.UnmarshalException: error unmarshalling arguments; nested exception is: 
    java.lang.ClassNotFoundException: com.prosc.cps.CPSProperties
    at sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:294)
    at sun.rmi.transport.Transport$1.run(Transport.java:159)
    at java.security.AccessController.doPrivileged(Native Method)
    at sun.rmi.transport.Transport.serviceCall(Transport.java:155)
    at sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:535)
    at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:790)
    at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:649)
    at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:895)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:918)
    at java.lang.Thread.run(Thread.java:680)
Caused by: java.lang.ClassNotFoundException: com.prosc.cps.CPSProperties
    at java.net.URLClassLoader$1.run(URLClassLoader.java:202)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:190)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:306)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:247)
    at java.lang.Class.forName0(Native Method)
    at java.lang.Class.forName(Class.java:249)
    at sun.rmi.server.LoaderHandler.loadClass(LoaderHandler.java:432)
    at sun.rmi.server.LoaderHandler.loadClass(LoaderHandler.java:163)
    at java.rmi.server.RMIClassLoader$2.loadClass(RMIClassLoader.java:620)
    at java.rmi.server.RMIClassLoader.loadClass(RMIClassLoader.java:247)
    at sun.rmi.server.MarshalInputStream.resolveClass(MarshalInputStream.java:201)
    at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1589)
    at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1494)
    at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1748)
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1327)
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:349)
    at sun.rmi.server.UnicastRef.unmarshalValue(UnicastRef.java:306)
    at sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:288)
    ... 9 more
4

2 回答 2

1

在客户端使用 RMIClassLoaderSPI 方法这相当于在客户端动态设置代码库属性:类在通过 RMI 发送到服务器时得到注释,以便服务器可以加载它们。

于 2013-06-18T23:32:10.483 回答
0

找到了答案!我在服务器上的系统属性“java.rmi.server.useCodebaseOnly”设置为 true(这很奇怪,因为它在 Java 6 中被记录为 false,而这正是我正在使用的)。如果这是真的,服务器会忽略来自客户端的类注释中嵌入的类路径 URL。我将它设置为 false,现在服务器正在从客户端读取注释并解析类!

于 2013-06-19T12:15:50.947 回答