【问题标题】:Unable to cast to interface class with URLClassLoader and reflection无法使用 URLClassLoader 和反射转换为接口类
【发布时间】:2018-06-21 02:50:43
【问题描述】:

我有一个罐子:

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

【问题讨论】:

  • 更新的问题,不这么认为。该类被发现很好,我可以很好地实例化它,但我无法转换它。
  • 如果跨多个项目共享,为什么您的 jar 包含接口?当接口实际位于 jar 中时,您的单独项目如何知道接口?接下来,用两个不同的对等类加载器加载同一个类将导致两个不同的类实际上不能来回转换,这可能就是你在这里看到的。

标签: java


【解决方案1】:

这个例外:

java.lang.ClassCastException:实现无法转换为 创建者界面

让我觉得你很可能有两个不同的 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 对象默认使用委托给父类加载器(这里是启动您的应用程序的类加载器)相同的结果。

【讨论】:

  • 谢谢!你是绝对正确的,但我还在我的代码中发现了另一个问题。更新了原始答案以反映这一点。
  • @imrichardcole 有趣的一点。我错过了一些东西。我更新得更准确了。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-01-11
  • 1970-01-01
  • 2012-01-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多