当强制转换为相同类时出现ClassCastException

69

我有两个不同的Java项目,其中一个有两个类:dynamicbeans.DynamicBean2dynamic.Validator

在另一个项目中,我动态加载这两个类并将它们存储在一个Object中。

class Form {
    Class beanClass;
    Class validatorClass;
    Validator validator;
}

然后我使用validatorClass.newInstance()创建一个Validator对象,并将其存储在validator中,接下来我还使用beanClass.newInstance()创建了一个bean对象,并将其添加到会话中。

portletRequest.setAttribute("DynamicBean2", bean);
Form 项目的生命周期中,我调用 validator.validate() 方法,它从会话中加载先前创建的 bean 对象(我正在运行 Websphere Portal Server)。当我尝试将此对象强制转换回 DynamicBean2 时,出现了 ClassCastException 错误。
当我使用以下代码将对象从会话中取出时,问题得到解决:
faces.getApplication().createValueBinding("#{DynamicBean2}").getValue(faces);

使用.getClass()检查它的类,我得到了dynamicbeans.DynamicBean2。这是我想将其转换的类,但尝试时出现了ClassCastException异常。

为什么会出现这种情况?

11个回答

67

我不太明白您对程序流程的描述,但通常当您遇到ClassCastExceptions时,无法解释您已使用一个类加载器加载了该类,然后尝试将其转换为另一个类加载器加载的相同类。这是行不通的-它们在JVM内部由两个不同的Class对象表示,并且转换将失败。

有一篇关于WebSphere中类加载的文章。我无法确定它如何适用于您的应用程序,但有许多可能的解决方案。我至少可以想到:

  1. Change the context class loader manually. Requires that you can actually get a reference to an appropriate class loader, which may not be possible in your case.

    Thread.currentThread().setContextClassLoader(...);
    
  2. Make sure the class is loaded by a class loader higher in the hierarchy.

  3. Serialize and deserialize the object. (Yuck!)

不过,对于你的特定情况,可能有更合适的方法。


3
我的猜测是(我没有门户服务器的经验),你正在不同的portlet中加载该类,每个portlet都使用自己的类加载器。然后,你将该实例放入一个会话对象中,当你从错误的portlet(它创建了该实例)检索时,就会出现异常。根据我的经验(基于WAS),我猜想你需要将包含该类的jar文件放置在类加载层次结构更高的位置,以便由两个portlet类加载器的父级加载它。 - Robin
2
2号,带有详细信息。 - Robin
1
@waxwing 这已经有一段时间了。但是,您介意详细说明您提出的第一个可能的解决方案吗?我觉得我遇到的问题可以通过这种方式解决。提前致谢! - Kasun Gajasinghe
1
我曾经遇到过几乎相同的问题,序列化和反序列化似乎是一种解决方案,因为对象被缓存并从先前的运行中序列化,所以与当前类加载器不兼容。 - Asoub

38
在我的Springboot项目中添加了spring-boot-devtools依赖后遇到了这个问题。我移除了依赖,问题消失了。我目前的最佳猜测是spring-boot-devtools引入了一个新的类加载器,在某些情况下会导致不同类加载器之间的类转换问题,在一些线程中没有使用新的类加载器时可能会出现这种问题。
参考:与Spring Boot DevTools相关的Dozer映射异常

1
太准确了!感谢您的答案。这也是我的问题。在删除 devtools 依赖项后,问题得到了解决。 - Yogesh Pitale
1
太棒了,这个救了我。谢谢你提供的链接,它让我找到了“真正”的解决方案,而不是禁用开发工具。 - Marquee
1
非常感谢,这正是答案。 - Hoang Nam
2
谢谢!这在2022年仍然是个问题。不太清楚为什么他们还没有修复它。 - Saminda Peramuna
1
对我来说有效 - Vysakhan Kasthuri

16

由于类对象是在不同的类加载器中加载的,因此从每个类创建的实例被视为“不兼容”。这是在使用许多不同类加载器和传递对象的环境中经常出现的问题。这些问题在Java EE和门户环境中很容易出现。

对类的实例进行强制类型转换要求与当前线程上下文类加载器加载的Class相同。


我应该以什么正确的方式加载类,以防止发生这种情况? - Jaime Garcia
神秘的部分是为什么两个类加载器应该加载相同的类。在任何Java应用程序中,任何类都只应该由一个类加载器加载。每当你遇到这种情况时,你的类加载器就会发生一些非常奇怪的事情。 - Steven Devijver
3
这取决于你所处的环境。如果你使用单例来缓存信息,并且从两个在同一个EAR中的不同web应用程序向此缓存中放入数据,它们可以通过各自的类加载器创建实例。当另一个web应用程序从该缓存中检索项目时,将导致ClassCastException异常。在这些环境中共享内存数据非常棘手,因此你必须注意从哪里加载类才能使其工作。 - Robin

2

当我试图使用Apache Commons Digester从XML创建对象列表时,出现了A2AClassCastException问题。

List<MyTemplate> templates = new ArrayList<MyTemplate>();
Digester digester = new Digester();
digester.addObjectCreate("/path/to/template", MyTemplate.class);
digester.addSetNext("/path/to/template", "add");
// Set more rules...
digester.parse(f); // f is a pre-defined File

for(MyTemplate t : templates) { // ClassCastException: Cannot cast mypackage.MyTemplate to mypackage.MyTemplate
    // Do stuff
}

如上所述,问题的原因是digestor没有使用与程序其余部分相同的ClassLoader。我在JBoss中运行它,结果发现commons-digester.jar不在JBoss的lib目录中,而是在webapp的lib目录中。将该jar文件复制到mywebapp/WEB-INF/lib中也解决了这个问题。另一个解决方案是调用digester.setClassLoader(MyTemplate.class.getClassLoader()),但在这种情况下,这感觉像是一个相当丑陋的解决方案。


1
在WildFly 10.1上遇到了相同的问题my.package.MyClass无法转换为my.package.MyClass,据我所知,我做了与@Emil Lundberg的答案相反的事情。将包含my.package.MyClass模块添加到my.war/WEB-INF/jboss-deployment-structure.xml作为依赖项。
<dependencies>
    ...
    <module name="my.package"/>
</dependencies>

并且从my.war/WEB-INF/lib中删除了相应的jar,重新部署了WAR,然后代码按预期工作。

因此,我们确保解决了问题。现在,我们需要确保问题不会再次出现,例如,在组装和部署更新版本的WAR时。

为此,在这些WAR的源代码中,需要在pom.xml中添加<scope>provided</scope>,以便下一次使用修复/增强代码注入重新组装my.war时,它不会将此jar捆绑到my.war/WEB-INF/lib中。


0

我在使用不同机器上的几个JBoss实例时遇到了相同的问题。很遗憾我没有早点看到这篇文章。
不同机器上部署了不同的构件,其中两个声明了具有相同名称的类加载器。我更改了一个类加载器的名称,一切正常工作 => 小心复制和粘贴!

为什么抛出的ClassCastException未提及涉及的类加载器?-我认为这将是非常有用的信息。
是否有人知道将来是否会有类似的东西可用?需要检查20-30个构件的类加载器并不那么愉快。或者我在异常文本中错过了什么吗?

编辑:我编辑了META-INF / jboss-app.xml文件并更改了加载程序的名称,这样可以拥有唯一的名称。在工作中,我们使用由maven({$version})在构建期间插入的构件ID(唯一)与版本组合。使用动态字段只是可选的,但如果您想部署相同应用程序的不同版本,则会有所帮助。

<jboss-app>
   <loader-repository> 
   com.example:archive=unique-archive-name-{$version}
   </loader-repository> 
</jboss-app>

你可以在这里找到一些信息:https://community.jboss.org/wiki/ClassLoadingConfiguration


0

我曾经遇到同样的问题,最终在java.net上找到了一个解决方法:

glassfish4/glassfish/modules中的所有org.eclipse.persistence jar文件复制到WEB-INF/lib中。然后进入你的glassfish-web.xml,将class-delegate设置为false

对我有用!


0

另一个选项:

在Weblogic中发生过这种情况,但我想在其他服务器上也可能会发生 - 如果您只是“发布”,因此重新加载了一些类。相反,请执行“清理”操作,以便所有类都可以一起重新加载。


0

0

我曾经遇到过从另一个EJB进行EJB查找的问题。我通过在EJB类配置中添加@Remote(MyInterface.class)来解决了这个问题。


不要这样做。我不知道为什么普通的Java开发人员会涉足JavaEE领域,然后突然决定不想再阅读文档了。Remote用于非常特定的目的,95%的JavaEE应用程序永远不需要它,甚至不想要它。这个人所建议的违反了EJB3.0规范,并使用一个改变EJB注入方式的注释来实现它。除非你知道Remote的含义并且确实需要它,否则永远不要这样做。了解你的应用程序,停止在规范周围进行黑客攻击。 - searchengine27

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接