如何使用RMI实现插件架构?

时间:2013-06-18 14:48:32

标签: java tomcat plugins classloader rmi

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

2 个答案:

答案 0 :(得分:1)

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

答案 1 :(得分:0)

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