无法使用URLClassLoader和反射强制转换为接口类

时间:2018-06-19 18:12:51

标签: java

我有一个罐子

/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);

1 个答案:

答案 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对象相同的结果,该对象默认情况下将使用委派给父类加载器(此处是启动您的应用程序的类加载器)。