【问题标题】:RMI ClassCastException in OSGi client accessing EJB from JavaEE serverOSGi 客户端中的 RMI ClassCastException 从 JavaEE 服务器访问 EJB
【发布时间】:2014-06-04 04:06:16
【问题描述】:

我有一个 OSGi 应用程序。使用 EJB context.lookup 我必须将 Thread 上下文类加载器设置为捆绑类加载器才能进行强制转换。像这样:

Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
Entity entity=bean.getOne();
System.out.println(entity.getClass().getClassLoader());

输出是

org.apache.felix.framework.BundleWiringImpl@7468776f

此代码有效。如果我有 ArrayList 则无法投射的问题

Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
ArrayList<Entity> entities=bean.getMany();

此代码返回 ClassCastException。

检查

ArrayList<Entity> temp=new ArrayList<Entity>();
System.out.println(temp.getClass().getClassLoader());

返回 NULL - 它是 means 引导类。怎么解决?

编辑:

最有趣的是,带有 String 的 ArrayList 可以工作,经典的数组可以工作,但是 ArrayList 和带有 Entiry 的 ArrayList 不行。

Class Bean {
....
  @Override //THIS DOESN'T WORK
    public ArrayList<Entity> readMany() {
        Entity dir1=new Entity();
        dir1.setContent("1 test");
        Entity dir2=new Entity();
        dir2.setContent("2 test");
        ArrayList<Entity> result=new ArrayList<>();
        result.add(dir1);result.add(dir2);
        return result;
    }

    @Override //THIS WORKS
    public ArrayList<String> readMany2() {
        String str1=new String("1 test");
        String str2=new String("2 test");
        ArrayList<String> result=new ArrayList<>();
        result.add(str1);
        result.add(str2);
        return result; 
    }

@Override //THIS WORKS
public Entity[] readArray() {
    ArrayList<Entity> al=readMany();
    Entity[] ar=new Entity[al.size()];
    for (int i = 0; i < al.size(); i++) {
        ar[i]=al.get(i);
    }
    return ar;
}

@Override //THIS DOESN'T WORK
public ArrayList readSimpleArrayList() {
    ArrayList<Entity> gal=readMany();
    ArrayList al= new ArrayList();
    for (Entity obj : gal) {
        al.add(obj);
    }
    return al;
}
...
}

这是日志

java.lang.ClassCastException: com.test.cmn.shd.base.dir.language.LanguageDirEntity 不能转换为 com.test.cmn.shd.base.dir.language.LanguageDirEntity 在 com.test.cmn.dt.base.Activator.start(Activator.java:83) 在 org.apache.felix.framework.util.SecureAction.startActivator(SecureAction.java:645) 在 org.apache.felix.framework.Felix.activateBundle(Felix.java:1977) 在 org.apache.felix.framework.Felix.startBundle(Felix.java:1895) 在 org.apache.felix.framework.BundleImpl.start(BundleImpl.java:944) 在 org.apache.felix.framework.BundleImpl.start(BundleImpl.java:931) 在 com.test.cmn.dt.loader.LoaderModel.startCoreModule(LoaderModel.java:149) 在 com.test.cmn.dt.loader.LoaderModel.access$100(LoaderModel.java:39) 在 com.test.cmn.dt.loader.LoaderModel$InstallAndStartModuleWorker.doInBackground(LoaderModel.java:79) 在 com.test.cmn.dt.loader.LoaderModel$InstallAndStartModuleWorker.doInBackground(LoaderModel.java:73) 在 javax.swing.SwingWorker$1.call(SwingWorker.java:296) 在 java.util.concurrent.FutureTask.run(FutureTask.java:262) 在 javax.swing.SwingWorker.run(SwingWorker.java:335) 在 java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) 在 java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615) 在 java.lang.Thread.run(Thread.java:744)

编辑 2 - 完整代码。 此代码在客户端计算机上执行。 JavaEE (GF4) 在服务器上运行。共有三个 osgi 包:用于服务器、用于客户端和共享。 shared 的副本在服务器和客户端上,包含 LanguageDirBeanRemote 和 LanguageDirEntity。

ClassLoader thatLoader = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
try {
       Properties jndiProps = new Properties();
       jndiProps.put("java.naming.factory.initial", "com.sun.enterprise.naming.impl.SerialInitContextFactory");
       jndiProps.put("java.naming.factory.url.pkgs", "com.sun.enterprise.naming");
       jndiProps.put("java.naming.factory.state", "com.sun.corba.ee.impl.presentation.rmi.JNDIStateFactoryImpl");
       jndiProps.setProperty("org.omg.CORBA.ORBInitialHost", "x.x.x.x");
       jndiProps.setProperty("org.omg.CORBA.ORBInitialPort", "3700");
       InitialContext ctx = new InitialContext(jndiProps);
       LanguageDirBeanRemote bean=(LanguageDirBeanRemote)ctx.lookup("java:global/...");
       ArrayList<LanguageDirEntity> elements=bean.readDirectory();
      System.out.println("HERE I GET THE ERROR:"+elements.get(0).getContent());
} finally {
Thread.currentThread().setContextClassLoader(thatLoader);
}

编辑 3 我开了一个bug

【问题讨论】:

  • 实际的类转换异常堆栈跟踪会很有帮助。
  • @BJ Hargrave 你好,BJ Hargrave。感谢您的专业关注。我编辑了问题。
  • @BJ Hargrave 我添加了更多信息。
  • 你能检查一下包含你上面发布的客户端代码的包是否嵌入了包 com.test.cmn.shd.base.dir.language 吗?您还应该检查其他捆绑包是否包含或导出此包。确保该软件包没有两个捆绑包。
  • @Christian Schneider 它包含所有包并且没有重复,因为 LanguageDirEntity[] 可以正常工作,但 ArrayList 没有。

标签: java jakarta-ee generics glassfish osgi


【解决方案1】:

"X cannot be cast to X", 在类加载器/OSGi 上下文中,通常意味着您的系统中存在两个类加载器,它们都已加载X 类,并且您正试图将使用一个类加载器的该类副本创建的实例传递给期望另一个类加载器副本的代码。

修复要么确保任何类加载器中只存在该类的一个实例并且由所有代码共享(最简单),要么确保实例永远不会跨越类加载器上下文边界,或者(更脆弱)使确保每个人都完全找到该类的相同实现(该类及其继承的所有内容的字节码完全相同)。

【讨论】:

  • 感谢您的宝贵时间。看,有两个班。实体(我的)和 ArrayList(标准 java)。当我只使用一个实体时,一切正常(此类由 osgi loader 加载)。当我使用实体和 ArratList(AL 由引导程序加载)时,它不起作用。
【解决方案2】:

正如另一个答案所说,您问题的根源几乎可以肯定是您有多个由不同类加载器加载的LanguageDirEntity 副本。最可能的原因是您无意中将物理类打包到不同的包中。所以第一个解决方案是检查你的包中的类。如果它们都在同一个位置,则运行

grep -r LanguageDirEntity *

是一种快速而肮脏的查找方式。 (您是否在构建中使用 Maven 包插件?很容易无意中将依赖项嵌入到包中,除非您的 pom 正确。)

当您在ArrayList 中使用String 时,事情起作用的原因是String 将由系统提供,并且不会有多个副本由不同的类加载器加载。您在使用 ArrayList 时遇到问题但直接使用 Entity 类没有问题的原因是泛型和集合的交互引入了一些在直接使用情况下不需要的额外转换。

在不知道哪些类被打包在哪里的情况下,准确地解包是很棘手的。我猜,从您看到的问题来看,Bean 与 Activator 不在同一个包中。但是,基本思想是every time the ArrayList is used by code (either to add elements in or read elements out), a cast will be done 将内容转换为“实体”。你做的演员越多,在更多不同的包中,命中从不同包不兼容加载的类的机会就越大。在这种情况下,看起来您的 Activator 看到的类副本与您的 Bean 不同,因此当 Activator 尝试(隐式,由编译器添加)强制转换时,它与 ArrayList 的内容不兼容.

【讨论】:

  • 感谢您的宝贵时间。我非常认真地阅读了您的帖子。我检查了该课程的所有捆绑包。没关系。问题出在类加载器上,我完全同意。但是bean是EJB。而且我无法控制它里面发生的事情(至少我不知道)。我添加到我的问题 EDIT 2 FULL CODE。你能指出我的错误吗?我有些不明白。某处出了点问题。但是哪里?我检查了这个代码一百次。
  • 啊,我明白了,我怀疑在这种情况下,两个副本是客户端副本和服务器副本。一般来说,Java EE 技术和 OSGi 不能很好地工作,因为 Java EE 使用了上下文类加载器。这正是编写 Enterprise OSGi 规范的原因(使用 Apache Aries 作为实现)。 Aries 确实具有 OpenEJB 集成,但我不确定它是否对您的情况有所帮助。从本质上讲,我认为您正在尝试做的事情很棘手。
  • 再次感谢您。我不是很有经验的开发人员,但我看到的问题似乎不是很新,因为它是最常见的技术和客户端-服务器架构。也许我必须在 ejb 中使用一些库或其他方法来使我的应用程序工作。你能给点建议吗?如果这个变体不能工作,让我们忘记它。我的问题是连接 OSGi 客户端和 EJB。
【解决方案3】:

谢谢大家。我终于解决了。我正在写我理解的方式。使用 osgi 客户端的 gf-client 有两种方式:

  1. 非官方方式 - 获取 glassfish 包并通过 osgi api 手动安装它们。
  2. 官方方式 - 将 glassfish/lib/gf-client.jar 和 glassfish/modules 复制到客户端并在类路径中添加 gf-clinet.jar。

我不知道怎么做,我是通过第一种方式做到的。我不能说错,因为 glassfish 本身就是 osgi。当我以第二种方式完成时,这个问题就消失了。因此,当您从 java 类路径和 osgi 包加载时,问题出在不同的类加载器中。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-01-03
    • 2016-11-16
    • 2011-12-06
    相关资源
    最近更新 更多