我有一个罐子
/home/cole/lib/a.jar
在这个jar中,我有以下接口/类(可怕的名称,仅用于说明目的!):
CreatorInterface.java
Base.java (implements CreatorInterface.java)
AbstractBase.java (extends Base.java)
Implementation.java (extends AbstractBase.java)
在一个单独的项目中,我有以下代码:
final URL[] jars = new URL[] {
new File("/home/cole/lib/a.jar").toURL();
}
final URLClassLoader classLoader = new URLClassLoader(jars, null);
final Class<?> implementation = classLoader.loadClass("Implementation");
final CreatorInterface object = (CreatorInterface)implementation.newInstance();
但是,当我执行以上操作时,会得到以下信息:
java.lang.ClassCastException: Implementation cannot be cast to CreatorInterface
鉴于Implementation
最终是实现CreatorInterface
的类的实例,为什么我会得到ClassCastException
?
更新1
这不是关于使用URLClassLoader
的问题,可以找到该类,问题似乎出在实例化中。例如,以下代码可以正常工作:
final Object object = implementation.newInstance();
更新2
正如@davidxxx回答的那样,我有两次接口类(一次在jar中,一次在使用它的项目中)。尽管界面相同,但这是问题的原因。
但是要使其正常工作,我需要像这样修复URLClassLoader,以避免出现ClassNotFoundException
:
final ClassLoader parent = this.getClass().getClassLoader();
final URLClassLoader classLoader = new URLClassLoader(jars, parent);
答案 0 :(得分:1)
此例外:
java.lang.ClassCastException:无法将实现强制转换为 CreatorInterface
让我认为您可能有两个截然不同的CreatorInterface
类:一个包含在jar中,另一个来自尝试加载它的客户端程序。
即使两个类具有相同的名称(限定名称),但对于每个类加载器而言,它们都是不同的类,因为您在这里使用了两个未关联的类加载器。
您具有正在运行的程序的当前类加载器,并且已将您指定为null
的另一个类加载器作为父类加载器:
final URLClassLoader classLoader = new URLClassLoader(jars, null);
因此,当您尝试将通过反射创建的对象分配给CreatorInterface
变量时,强制转换失败,因为每个类加载器都加载并使用了两个不同的CreatorInterface
:一个来自您的类加载器客户端代码,另一个来自实例化的类加载器。
使用单个类加载器可以解决此问题,但是最佳实践是将jar包含在项目的类路径中,并确保在jar中提供类的单一版本。
要使事物脱钩,您可能应该将jar分为2个jar:一个仅包含接口的API jar和一个依赖该API jar并包含其他类的实现jar。
在客户端代码中,仅在类路径中添加API jar,以便能够将其分配给具有声明的接口类型的变量。
关于第二点:
这不是有关使用URLClassLoader的问题,可以找到该类,问题似乎出在实例化中。例如, 以下代码可以正常工作:
final Object object = implementation.newInstance();
在这种情况下,您不引用接口类型。
实际上,您确实将Implementation
对象分配给Object
而不是CreatorInterface
变量。
正确/一致的接口和子类由类加载器加载,但是在这里您永远不会招惹ClassCastException
,因为您永远不会将其分配给重复类的类型,而是Object
定义了单个时间。
因此,以前遇到的问题不会发生。
关于第三点:
无论如何,我都需要像这样修复URLClassLoader, 避免ClassNotFoundException:
final ClassLoader parent = this.getClass().getClassLoader();
final URLClassLoader classLoader = new URLClassLoader(jars, parent);
之所以起作用,是因为您在这里创建了与父类加载器关联的类加载器。
事实上,如果您这样做了:
final URLClassLoader classLoader = new URLClassLoader(jars);
它将产生与创建的URLClassLoader
对象相同的结果,该对象默认情况下将使用委派给父类加载器(此处是启动您的应用程序的类加载器)。