这是我正在努力解决的一个类加载器问题。我了解问题的根本原因(不同的类加载器),但我不确定修复它的最佳方法。
我有一些通用接口(interface)的项目;我们称之为api
。我还有另外两个名为 runner
和 module
的项目,它们都使用 api
作为依赖项。
runner
的工作是动态加载一个 module
工件(从 jar 中;它是一个包含其依赖项的胖工件),然后执行它。 runner
期望 module
从 api
提供某些具体实现。为了确保来自 module.jar
的不同版本的类不会互相干扰,我创建了一个新的类加载器,其 URL 为 module.jar
,并设置了父级classloader 到加载和处理 module.jar
的类的类加载器。这可以正常工作,没有任何问题。
当我使用runner
作为Web应用程序(具体来说是一个Spring Boot应用程序)内的依赖项时,问题就出现了,并且很快发现我无法从模块加载一些类。 jar
因为它们与当前类路径中已存在的类(来自 web 应用程序中的其他依赖项)冲突。
由于module.jar
实际上只需要api
中的类,我认为我可以创建一个新的URLClassLoader
(没有父级)仅包含来自 api.jar 的类,然后在加载模块时将其用作父类加载器。这就是我开始遇到麻烦的地方:
CommonInterface commonInterface = null;
Class<CommonInterface> commonInterfaceClass = null;
ClassLoader myClassLoader = URLClassLoader.newInstance(moduleJarURL, apiClassesClassLoader);
//...
//...
//clazz is a concrete implementation from module.jar
if(myClassLoader.loadClass(CommonInterface.class.getName()).isAssignableFrom(clazz)) {
commonInterfaceClass = clazz;
}
commonInterface = commonInterfaceClass.newInstance(); //ClassCastException
我知道我原来的问题是由于类加载器在尝试加载它之前首先检查该类是否已经加载,这意味着当它使用 module 中的名称解析时。 jar
,它链接到该类的不兼容版本。
有什么好的方法可以解决这个问题吗?与其创建仅包含来自 api
的类的 URL 类加载器,不如创建我自己的实现,仅当请求的类是来自 api
的类时才将其委托(delegate)给父级,这是否有意义? ?
最佳答案
您已从两个不同的类加载器加载了CommonInterface
。具有相同名称但不同类加载器的类对于 JVM 来说是不同的类。 (即使 .class 文件中的类 100% 相同 - 问题不是不兼容,而是它们来自不同的类加载器)
如果你做了
System.out.println(CommonInterface.class == myClassLoader.loadClass(CommonInterface.class.getName()));
您会发现这会打印 false
。
创建类加载器的方式:
ClassLoader myClassLoader = URLClassLoader.newInstance(moduleJarURL, apiClassesClassLoader);
.. 仅当 apiClassesClassLoader
也是包含此代码的类的父类加载器时才有效。
你可以尝试:
ClassLoader myClassLoader = URLClassLoader.newInstance(moduleJarURL,
getClass().getClassLoader());
但是根据您的描述(它是一个包含自己的依赖项的“胖”jar)和复杂的 Web 类加载器(子加载器优先),这可能无法解决您的问题。
在这种情况下,唯一的解决方案是使模块 jar 变得“精简”,以确保仅使用一个类加载器加载每个类一次。
关于java - 从实现公共(public)接口(interface)的 jar 中实例化类,然后将实例分配给该接口(interface)会导致 ClassCastException,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35075150/