Java 无法通过类型强制转换为类型

Java 无法通过类型强制转换为类型,java,tomcat,classloader,Java,Tomcat,Classloader,当我想将一种类型转换为另一种类型时,会出现以下异常 java.lang.ClassCastException: org.paston.certification.data.impl.BRL6000 cannot be cast to org.paston.certification.data.Certification BRL6000扩展了认证。因此,在我的理解中,我应该能够将BRL6000类型的对象强制转换为认证类型 这是发生异常的代码 Object certification = ch.

当我想将一种类型转换为另一种类型时,会出现以下异常

java.lang.ClassCastException: org.paston.certification.data.impl.BRL6000 
cannot be cast to org.paston.certification.data.Certification
BRL6000扩展了认证。因此,在我的理解中,我应该能够将BRL6000类型的对象强制转换为认证类型

这是发生异常的代码

Object certification = ch.getCertificationData(process, version);
Certification c = (Certification)certification;
部署

应用程序从Eclipse部署到Tomcat7服务器。我的应用程序使用了Tomcat环境中的一些jar(例如,Bonita_Server.jar)

我的应用程序(在Eclipse中)是一个动态web项目,它引用了另一个项目(Certificationnl),该项目包含类
Certification
BRL6000
。当我将应用程序部署到Tomcat时,Project Certificationnl被添加到webproject的WAR中

课程

BRL6000类

package org.paston.certification.data.impl;

import org.paston.certification.data.Certification;
import org.paston.certification.data.CertificationStep;

public class BRL6000 extends Certification{

    /**
     * 
     */
    public static final long serialVersionUID = -8215555386637513536L;
    public static final String processName = "BRL6000";

}
认证班

package org.paston.certification.data;

import java.util.ArrayList;
import java.util.List;

import org.ow2.bonita.facade.runtime.impl.AttachmentInstanceImpl;

public class Certification implements java.io.Serializable{

    public enum Section{
        ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN
    }
    /**
     * SerializationVersionUID
     */
    private static final long serialVersionUID = -5158236308772958478L;


}
getCertificationData

public Object getCertificationData(String process, String version) {
    if (loginContext == null)
        login();

    System.out.println("Process: "+ process + " Version: "+ version);
    ProcessDefinitionUUID pdu = new ProcessDefinitionUUID(process, version);

    QueryRuntimeAPI queryRuntimeAPI = AccessorUtil
            .getQueryRuntimeAPI();

    try {
        Set<ProcessInstance> processInstances = queryRuntimeAPI
                .getProcessInstances(pdu);

        if (processInstances.size() != 1)
            System.out.println("Best number of instances is 1. Found: "
                    + processInstances.size());

        for (ProcessInstance processInstance : processInstances) {
            Map<String, Object> variables = processInstance
                    .getLastKnownVariableValues();
            if (((Boolean) variables.get("active")) == true) {              
                return variables.get("certification");
            }
        }
    } catch (ProcessNotFoundException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    return null;
}
更新2

我对这个问题有了更好的理解。Bonitaserver(
AccessorUtil
也会加载认证对象。它从
Certification.jar1620768823629427276.tmp
加载类,该类是Bonitaserver在将进程上载到服务器时创建的

此外,我还发现了一个类
reflectil
(),它可能用于加载这些类

我尝试的是在doGet的开头为这两个(servlet)加载类,作为
AccessorUtil
ClassLoader
。两者都有相同的旧结果

    ArrayList<String> classesNames = new ArrayList<String>();
    classesNames.add("org.paston.certification.data.Certification");
    classesNames.add("org.paston.certification.data.CertificationI");
    classesNames.add("org.paston.certification.data.impl.BRL6000");
    ClassLoader cl = this.getClass().getClassLoader();
    Class<?>[] classes =  ReflectUtil.loadClasses(cl, classesNames);
守则的结果:

--- Test ClassLoader certification object---

org.ow2.bonita.runtime.ProcessClassLoader 451656
org.ow2.bonita.runtime.ProcessClassLoader@6e448
org.ow2.bonita.runtime.VirtualCommonClassloader 1182018350
org.ow2.bonita.runtime.VirtualCommonClassloader@46742b2e
org.apache.catalina.loader.StandardClassLoader 318536939
org.apache.catalina.loader.StandardClassLoader@12fc7ceb
sun.misc.Launcher.AppClassLoader 1667514408
sun.misc.Launcher$AppClassLoader@63644028
sun.misc.Launcher.ExtClassLoader 1253061906
sun.misc.Launcher$ExtClassLoader@4ab03512

--- Test ClassLoader Certification class--- 

org.apache.catalina.loader.WebappClassLoader 2136824254
WebappClassLoader   context: /Certification   delegate: false  
repositories:
    /WEB-INF/classes/
----------> Parent Classloader: org.apache.catalina.loader.StandardClassLoader@12fc7ceb

org.apache.catalina.loader.StandardClassLoader 318536939
org.apache.catalina.loader.StandardClassLoader@12fc7ceb
sun.misc.Launcher.AppClassLoader 1667514408
sun.misc.Launcher$AppClassLoader@63644028
sun.misc.Launcher.ExtClassLoader 1253061906
sun.misc.Launcher$ExtClassLoader@4ab03512

我怀疑问题的根源在于运行时环境

您的
认证
数据最终来自
AccessorUtil.getQueryRuntimeAPI()
,这是一个由Tomcat运行的静态方法,因此来自它的所有对象实例都可能由Tomcat类加载器加载

如果在Tomcat下复制jar文件,它将使用自己的类加载器加载它。尽管eclipse运行代码使用不同的类加载器,但如果它们没有加载此
认证
类的公共祖先类加载器,它们将被视为不同的类

我建议查看运行时类路径,从Tomcat中删除lib,或者(最坏的情况)在Tomcat中运行代码(例如,作为同一应用程序中的Servlet)

更新:

基于项目部署描述,我认为

  • AccessorUtil
    类由公共类加载器加载
  • 里面的列表由Tomcat部署中的
    Certificationnl
    类中的
    Certification
    实例填充
  • 您可以通过
    AccessorUtil
    类访问这些对象,但是不能从Eclipse代码访问类定义
在JVM级别从外部访问Tomcat定义的对象是个坏主意。将代码放入同一容器中,或者使用正确定义的接口(例如web服务)

如果您想继续使用当前的项目设置,还可以使用放入公共类加载器(在
AccessorUtil
旁边)并强制转换到接口的接口

更新2:

要检查类加载器层次结构,请执行如下代码:

Class c1 = certification.getClass().getSuperclass().getClassLoader();
while (c1 != null) {
    System.out.println(c1.getClass().getCanonicalName() + " " + c1.hashCode() + " " + c1);
    c1 = c1.getParent();
}

Class c2 = Certification.class.getClassLoader();
while (c2 != null) {
    System.out.println(c2.getClass().getCanonicalName() + " " + c2.hashCode() + " " + c2);
    c2 = c2.getParent();
}
您可以通过比较堆栈来找到共同的祖先。最好的方法仍然是调试

更新3:

可见,您的通用类加载器是
org.apache.catalina.loader.StandardClassLoader
。除此之外

  • org.apache.catalina.loader.WebappClassLoader
    已为您的webapp提供(这是您希望转换为的地方)
  • org.ow2.bonita.runtime.VirtualCommonClassloader
    org.ow2.bonita.runtime.ProcessClassLoader
    是对象的来源(即实例化对象的
Bonita似乎正在使用某种类加载机制

解决方案是使用
标准类加载器
(或任何其他类加载器)来加载类。由于
Certification
(因此
BRL6000
)依赖于Bonita类,因此您必须将
Bonita_Server.jar
放入已认可的。请参阅,它可能会给您更多的洞察力

更新4:

要执行我的评论中建议的序列化/反序列化,可以使用以下代码:

Certification certification = null;
try {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    ObjectOutputStream oos = new ObjectOutputStream(baos);
    oos.writeObject(ch.getCertificationData(process, version));
    oos.flush();
    certification = (Certification) new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray())).readObject();
} catch (IOException | ClassNotFoundException ex) {
}
请注意,
(认证)
转换是对当前加载的类进行的

更新5:


最终的解决方案是不使用直接Java类级访问,而是使用适当的API来执行相同的操作

我认为有两种可能的解释:

  • 您已经设法在两个不同的类加载器中加载了
    认证
  • 您有两个
    Certification
    类,它们的名称看起来完全相同
下面是我建议你做的事情,看看到底发生了什么

  • 在进行
    (认证)认证的语句之前插入以下内容:

    // Get the classes that are being compared in the typecast.
    Class c1 = certification.getClass().getSuperclass();
    Class c2 = Certification.class;
    
    System.out.println("c1 is " + c1 + ", c2 is " + c2);
    System.out.println("c1 == c2 is " + c1 == c2);
    System.out.println("c1.equals(c2) is " + c1 == c1.equals(c2));
    System.out.println("c1.getName().equals(c2.getName()) is " + 
                       c1.getName().equals(c2.getName()));
    System.out.println("c1.getClassLoader() == c2.getClassLoader() is " +
                       c1.getClassLoader() == c2.getClassLoader());
    
  • 检查第一行给出的
    c1
    c2
    的名称是否相同。(如果没有,我们选择了错误的类进行比较。请调整我的代码以获得正确的类。)

  • c1==c2
    c2.equals(c2)
    测试应该给出相同的答案,我预测它将是
    false

  • 比较名称是区分两种不同解释的测试:

    • 如果名称相等,则表示类加载器出现问题
    • 如果名称不相等,则有两个不同的类,它们的名称看起来相同,但实际上不同。(怎么可能呢?Java使用Unicode,还有一些
      Class c1 = certification.getClass().getSuperclass().getClassLoader();
      while (c1 != null) {
          System.out.println(c1.getClass().getCanonicalName() + " " + c1.hashCode() + " " + c1);
          c1 = c1.getParent();
      }
      
      Class c2 = Certification.class.getClassLoader();
      while (c2 != null) {
          System.out.println(c2.getClass().getCanonicalName() + " " + c2.hashCode() + " " + c2);
          c2 = c2.getParent();
      }
      
      Certification certification = null;
      try {
          ByteArrayOutputStream baos = new ByteArrayOutputStream();
          ObjectOutputStream oos = new ObjectOutputStream(baos);
          oos.writeObject(ch.getCertificationData(process, version));
          oos.flush();
          certification = (Certification) new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray())).readObject();
      } catch (IOException | ClassNotFoundException ex) {
      }
      
      // Get the classes that are being compared in the typecast.
      Class c1 = certification.getClass().getSuperclass();
      Class c2 = Certification.class;
      
      System.out.println("c1 is " + c1 + ", c2 is " + c2);
      System.out.println("c1 == c2 is " + c1 == c2);
      System.out.println("c1.equals(c2) is " + c1 == c1.equals(c2));
      System.out.println("c1.getName().equals(c2.getName()) is " + 
                         c1.getName().equals(c2.getName()));
      System.out.println("c1.getClassLoader() == c2.getClassLoader() is " +
                         c1.getClassLoader() == c2.getClassLoader());