OSGi客户端中的java RMI ClassCastException从JavaEE服务器访问EJB
我有一个OSGi应用程序。使用EJB上下文。查找我必须将线程上下文类加载器设置为bundle类加载器,以便能够强制转换。像这样:
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-itmeans引导类。怎么能修好呢
编辑:
最有趣的是,带字符串的ArrayList有效,经典数组有效,但ArrayList和带Entry的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 cannot be cast to com.test.cmn.shd.base.dir.language.LanguageDirEntity at com.test.cmn.dt.base.Activator.start(Activator.java:83) at org.apache.felix.framework.util.SecureAction.startActivator(SecureAction.java:645) at org.apache.felix.framework.Felix.activateBundle(Felix.java:1977) at org.apache.felix.framework.Felix.startBundle(Felix.java:1895) at org.apache.felix.framework.BundleImpl.start(BundleImpl.java:944) at org.apache.felix.framework.BundleImpl.start(BundleImpl.java:931) at com.test.cmn.dt.loader.LoaderModel.startCoreModule(LoaderModel.java:149) at com.test.cmn.dt.loader.LoaderModel.access$100(LoaderModel.java:39) at com.test.cmn.dt.loader.LoaderModel$InstallAndStartModuleWorker.doInBackground(LoaderModel.java:79) at com.test.cmn.dt.loader.LoaderModel$InstallAndStartModuleWorker.doInBackground(LoaderModel.java:73) at javax.swing.SwingWorker$1.call(SwingWorker.java:296) at java.util.concurrent.FutureTask.run(FutureTask.java:262) at javax.swing.SwingWorker.run(SwingWorker.java:335) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615) at java.lang.Thread.run(Thread.java:744)
编辑2-完整代码此代码在客户端计算机上执行。JavaEE(GF4)正在服务器上运行。有三个osgi捆绑包:用于服务器、用于客户端和共享。共享的副本位于服务器和客户端上,包含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
# 1 楼答案
谢谢大家。我终于解决了。我在写我所理解的。有两种方法可以将gf客户端与osgi客户端一起使用:
我不知道怎么做,我是用第一种方法做的。我不能说这是错的,因为glassfish本身就是osgi。当我用第二种方法做的时候,这个问题消失了。因此,当您从java类路径和osgi包加载时,问题出现在不同的类加载器中
# 2 楼答案
正如另一个答案所说,问题的根源几乎肯定是不同的类加载器加载了
LanguageDirEntity
的多个副本。最可能的原因是您无意中将物理类打包到不同的包中。因此,第一个解决方案是检查类的bundle。如果它们都在同一个位置,运行grep -r LanguageDirEntity *
这是一种快速而肮脏的找到它们的方法。(您的构建是否使用Maven bundle插件?除非您的POM正确,否则很容易不经意地将依赖项嵌入到bundle中。)
当您在
ArrayList
中使用String
时,事情会起作用的原因是String
将由系统提供,并且不同的类加载器永远不会加载它的多个副本。使用ArrayList有问题但直接使用实体类没有问题的原因是泛型和集合的交互引入了一些在直接使用情况下不需要的额外强制转换在不知道将哪些类打包在何处的情况下,准确地解包正在发生的事情是很棘手的。我想,从您看到的问题来看,这个Bean与激活器不在同一个包中。然而,基本思想是every time the ArrayList is used by code (either to add elements in or read elements out), a cast will be done将内容强制转换为“实体”。在更多不同的捆绑包中执行的强制转换越多,命中从不同捆绑包不兼容地加载的类的机会就越大。在本例中,看起来您的
Activator
看到的类的副本与您的Bean
不同,因此当激活器尝试(隐式,由编译器添加)强制转换时,它与ArrayList
的内容不兼容# 3 楼答案
“X不能强制转换为X”,在类加载器/OSGi上下文中,通常意味着您的系统中有两个类加载器,它们都加载了类X,并且您正试图将使用一个类加载器的副本创建的实例传递给期望另一个类加载器副本的代码
修复方法是确保任何类加载器中只存在该类的一个实例,并由所有代码共享(最简单),或者确保实例从不跨越类加载器上下文边界,或者(更脆弱),以确保每个人都能找到该类的完全相同的实现(该类及其继承的所有内容的完全相同的字节码)