5

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

java.lang.ClassCastException: org.paston.certification.data.impl.BRL6000 
cannot be cast to org.paston.certification.data.Certification

BRL6000 扩展了认证。因此,据我了解,我应该能够将 BRL6000 类型的对象转换为 Certification 类型。

这是发生异常的代码。

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

部署

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

我的应用程序(在 Eclipse 中)是一个动态 Web 项目,它引用了另一个项目(Certificationnl),其中包含类CertificationBRL6000. 当我将应用程序部署到 Tomcat 时,项目 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;


}

获取认证数据

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;
}

将代码更新为 Servlet

package org.paston.certification.servlet;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.paston.certification.CertificationHandler;
import org.paston.certification.data.Certification;
import org.paston.certification.data.CertificationI;

/**
 * Servlet implementation class SomeServlet
 */
public class SomeServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    /**
     * @see HttpServlet#HttpServlet()
     */
    public SomeServlet() {
        super();
        // TODO Auto-generated constructor stub
    }

    /**
     * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
     */
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        CertificationHandler ch = new CertificationHandler();
        String process = request.getParameter("p");
        String version = request.getParameter("v");
        Object certification = ch.getCertificationData(process, version);
        Class<?> clazz = certification.getClass();
        while (clazz != null) {
            System.out.println(clazz.getName());
            clazz = clazz.getSuperclass();
        }

        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.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()));

        CertificationI c = (CertificationI) certification;

        PrintWriter out = response.getWriter();
        out.println("Hello World");
    }

    /**
     * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
     */
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // TODO Auto-generated method stub
    }

}

Servlet 的控制台输出:

流程:BRL6000 版本:1.0 org.paston.certification.data.impl.BRL6000 org.paston.certification.data.Certification java.lang.Object c1 是类 org.paston.certification.data.Certification,c2 是类 org.paston .certification.data.Certification c1 == c2 为假 c1.equals(c2) 为假 c1.getName().equals(c2.getName()) 为真 c1.getClassLoader() == c2.getClassLoader() 为假

还有另一个问题可能暗示了这个问题。每隔 10 秒,我会在控制台中看到以下异常:

May 07, 2013 2:09:45 PM org.apache.catalina.loader.WebappClassLoader loadClass
INFO: Illegal access: this web application instance has been stopped already.  Could not load org.ow2.bonita.runtime.tx.StandardTransaction.  The eventual following stack trace is caused by an error thrown for debugging purposes as well as to attempt to terminate the thread which caused the illegal access, and has no functional impact.
java.lang.IllegalStateException
    at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1566)
    at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1526)
    at org.ow2.bonita.util.ReflectUtil.loadClass(ReflectUtil.java:68)
    at org.ow2.bonita.env.descriptor.ObjectDescriptor.construct(ObjectDescriptor.java:195)
    at org.ow2.bonita.env.WireContext.construct(WireContext.java:521)
    at org.ow2.bonita.env.WireContext.create(WireContext.java:498)
    at org.ow2.bonita.env.WireContext.create(WireContext.java:484)
    at org.ow2.bonita.env.WireContext.get(WireContext.java:456)
    at org.ow2.bonita.env.WireContext.get(WireContext.java:343)
    at org.ow2.bonita.env.WireContext.get(WireContext.java:746)
    at org.ow2.bonita.env.BasicEnvironment.get(BasicEnvironment.java:151)
    at org.ow2.bonita.env.BasicEnvironment.get(BasicEnvironment.java:142)
    at org.ow2.bonita.util.EnvTool.getEnvClass(EnvTool.java:175)
    at org.ow2.bonita.util.EnvTool.getTransaction(EnvTool.java:84)
    at org.ow2.bonita.runtime.tx.StandardTransactionInterceptor.execute(StandardTransactionInterceptor.java:42)
    at org.ow2.bonita.services.impl.EnvironmentInterceptor.execute(EnvironmentInterceptor.java:40)
    at org.ow2.bonita.services.impl.RetryInterceptor.execute(RetryInterceptor.java:59)
    at org.ow2.bonita.runtime.event.EventExecutorThread.run(EventExecutorThread.java:61)

更新 2

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

另外,我发现了一个可能用于加载这些类的类ReflectUtil链接)。

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

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

更新 3

@GaborSch 提出的以下代码的结果。我使用它的代码:

    System.out.println("--- Test ClassLoader certification object---");
    ClassLoader cl1 = certification.getClass().getSuperclass().getClassLoader();
    while (cl1 != null) {
        System.out.println(cl1.getClass().getCanonicalName() + " " + cl1.hashCode() + " " + cl1);
        cl1 = cl1.getParent();
    }
    System.out.println("--- Test ClassLoader Certification class---");
    ClassLoader cl2 = Certification.class.getClassLoader();
    while (cl2 != null) {
        System.out.println(cl2.getClass().getCanonicalName() + " " + cl2.hashCode() + " " + cl2);
        cl2 = cl2.getParent();
    }

代码结果:

--- 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
4

2 回答 2

7

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

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

如果你复制Tomcat下的jar文件,它会用自己的类加载器加载它。您的 eclipse 运行代码虽然使用不同的类加载器,但如果它们没有加载此类的共同祖先类加载器Certification,它们将被视为不同的 Class

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

更新:

根据项目部署描述,我相信

  • 该类AccessorUtil由通用类加载器加载
  • 里面的列表由来自 Tomcat 部署的类的Certification实例填充Certificationnl
  • 您可以通过类访问这些对象AccessorUtil,但不能从您的 Eclipse 代码访问这些类定义。

在 JVM 级别从外部访问 Tomcat 定义的对象是个坏主意。要么把你的代码放到同一个容器中,要么使用一个正确定义的接口(例如网络服务)。

如果您想保留当前的项目设置,您还可以使用放入公共类加载器(旁边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存在于您的网络应用程序中(这是您要投射到的位置)
  • org.ow2.bonita.runtime.VirtualCommonClassloader并且org.ow2.bonita.runtime.ProcessClassLoader存在于对象来自的地方(那是它们被实例化的地方)

Bonita似乎正在使用某种类加载机制。

解决方案是让StandardClassloader(或任何其他类加载器)加载您的类。由于Certification(因此BRL6000)依赖于 Bonita 类,因此您必须Bonita_Server.jar放入endorsed。请参阅Tomcat Classloader HOWTO,它可能会给您更多的见解。

更新 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) {
}

请注意,(Certification)强制转换是针对当前加载的类进行的。

更新 5:

最终的解决方案不是使用直接的 Java 类级访问,而是使用适当的 API 来做同样的事情。

于 2013-05-06T14:37:18.720 回答
3

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

  • 您已设法Certification在两个不同的类加载器中加载该类。
  • 您有两个Certification名称看起来相同的类。

这是我建议你做的事情来弄清楚发生了什么。

  • (Certification) 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检查第一行是否为and提供(看起来是)相同的名称c2。(如果不是,我们选择了错误的类进行比较。调整我的代码以获得正确的类。)

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

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

    • 如果名称相同,则表明存在类加载器问题。
    • 如果名称不相等,那么您有两个不同的类,它们的名称看起来相同但实际上并不相同。(这怎么可能?Java 使用 Unicode,而一些欧洲字符集对不同的字符使用相同的“字形”。根据您使用的字体,当您在编辑器/IDE,但实际上不是。
  • 最终测试将确认是否具有相同的类加载器c1c2


这不会解释为什么您的 Web 应用程序处于这种状态,但它会清楚地告诉您问题是类加载器、时髦的类名……还是其他原因。

于 2013-05-06T15:46:21.497 回答