【问题标题】:How do I implement a plug-in architecture using RMI?如何使用 RMI 实现插件架构?
【发布时间】:2013-06-18 14:48:31
【问题描述】:

我正在尝试在 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

【问题讨论】:

  • 虽然我不完全理解这个问题,但是对于运行时的jar/插件管理,你有没有考虑过OSGi?
  • 不,我完全不了解 OSGi

标签: java tomcat plugins classloader rmi


【解决方案1】:

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

【讨论】:

  • 感谢您的回答。目前,我在客户端上有一个 URLClassLoader,它从 HTTP URL 加载插件类(该部分不使用 RMI)。但是,我不明白在这种情况下客户端 RMIClassLoaderSpi 子类需要做什么。这不是用于解组而不是编组 RMI 参数吗?编组不应该根据我已经在使用的 URLClassLoader 对类进行注释吗?
  • 经过一些调试后,看起来类正在从客户端正确注释 - 调用 'LoaderHandler.getClassAnnotation( theObject.getClass() )' 正在返回一个有效的 HTTP URL,它应该在server: 'apollo.local:8080/MS/downloadExtension/CPSClient.jar' (一切都发生在一台计算机上,所以 .local 是正常的)。听起来服务器忽略了注释?我确实在服务器上安装了一个 SecurityManager(子类化以允许一切)。我还需要做什么才能让服务器使用注释?
  • 找到了答案!我的系统属性 'java.rmi.server.useCodebaseOnly' 是真的(这很奇怪,因为它在 Java 6 中被记录为假,这就是我正在使用的)。我将它设置为 false,现在服务器正在从客户端读取注释并解析类!非常感谢您通过注释为我指明了正确的方向。
【解决方案2】:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-04-03
    • 2020-02-09
    • 2018-07-28
    • 1970-01-01
    • 2011-09-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多