在websphere中查找本地EJB的正确方法 – 获取ClassCastException

我有一个EJB,它由本地和远程接口公开

package com.sam.enqueue; import javax.ejb.Local; import javax.ejb.Remote; import javax.ejb.Singleton; @Singleton @Local(SamEnqueueLocal.class) @Remote(SamEnqueueRemote.class) public class SamEnqueue implements SamEnqueueRemote, SamEnqueueLocal { } // remote interface package com.sam.enqueue; import javax.ejb.Remote; @Remote public interface SamEnqueueRemote { } // local interface package com.sam.enqueue; @Local public interface SamEnqueueLocal { } 

我的app容器是websphere 8.0,我没有覆盖服务器分配的默认JNDI名称。 在服务器启动期间,我在日志中获得以下默认绑定:

 CNTR0167I: The server is binding the com.sam.enqueue.SamEnqueueRemote interface of the SamEnqueue enterprise bean in the SAM_ENQUEUE.jar module of the SAM_ENQUEUE application. The binding location is: ejb/SAM_ENQUEUE/SAM_ENQUEUE.jar/SamEnqueue#com.sam.enqueue.SamEnqueueRemote CNTR0167I: The server is binding the com.sam.enqueue.SamEnqueueRemote interface of the SamEnqueue enterprise bean in the SAM_ENQUEUE.jar module of the SAM_ENQUEUE application. The binding location is: com.sam.enqueue.SamEnqueueRemote CNTR0167I: The server is binding the com.sam.enqueue.SamEnqueueRemote interface of the SamEnqueue enterprise bean in the SAM_ENQUEUE.jar module of the SAM_ENQUEUE application. The binding location is: java:global/SAM_ENQUEUE/SamEnqueue!com.sam.enqueue.SamEnqueueRemote CNTR0167I: The server is binding the com.sam.enqueue.SamEnqueueLocal interface of the SamEnqueue enterprise bean in the SAM_ENQUEUE.jar module of the SAM_ENQUEUE application. The binding location is: ejblocal:SAM_ENQUEUE/SAM_ENQUEUE.jar/SamEnqueue#com.sam.enqueue.SamEnqueueLocal CNTR0167I: The server is binding the com.sam.enqueue.SamEnqueueLocal interface of the SamEnqueue enterprise bean in the SAM_ENQUEUE.jar module of the SAM_ENQUEUE application. The binding location is: ejblocal:com.sam.enqueue.SamEnqueueLocal CNTR0167I: The server is binding the com.sam.enqueue.SamEnqueueLocal interface of the SamEnqueue enterprise bean in the SAM_ENQUEUE.jar module of the SAM_ENQUEUE application. The binding location is: java:global/SAM_ENQUEUE/SamEnqueue!com.sam.enqueue.SamEnqueueLocal 

lookup类只是同一服务器中不同EAR中的一个简单java类,代码如下:

 Context ctx = new InitialContext(); Object local = ctx.lookup("java:global/SAM_ENQUEUE/SamEnqueue!com.sam.enqueue.SamEnqueueLocal"); SamEnqueueLocal samEnqueue = (SamEnqueueLocal) local; 

查找正在使用本地的三个JNDI名称中的任何一个,但它没有被SamEnqueueLocalSamEnqueueLocal 。 exception跟踪是:

 SystemErr R java.lang.ClassCastException: com.sam.enqueue.EJSLocal0SGSamEnqueue_cf56ba6f incompatible with com.sam.enqueue.SamEnqueueLocal ... rest ommited 

我创建了一个共享库,并将目标EAR的存根放入其中。 该库是源查找EAR的Classes loaded with local class loader first (parent last)路径,其中Classes loaded with local class loader first (parent last)策略。 图书馆不是孤立的。 如果我删除存根,我会得到一个java.lang.ClassNotFoundException: com.sam.enqueue.SamEnqueueLocal按预期方式。

更新:

使用dependency injection时:

 @EJB(lookup="ejblocal:com.sam.enqueue.SamEnqueueLocal") private SamEnqueueLocal samEnqueueLocal; 

我得到的错误是:

 javax.ejb.EJBException: Injection failure; nested exception is: java.lang.IllegalArgumentException: Can not set com.sam.enqueue.SamEnqueueLocal field com.some.SomeBean.samEnqueueLocal to com.sam.enqueue.EJSLocal0SGSamEnqueue_cf56ba6f Caused by: java.lang.IllegalArgumentException: Can not set com.sam.enqueue.SamEnqueueLocal field com.some.SomeBean.samEnqueueLocal to com.sam.enqueue.EJSLocal0SGSamEnqueue_cf56ba6f 

所以它基本相同。

您将获得java.lang.ClassCastException因为您正在检索对EJB中的引用,该EJB存在于与尝试注入它的部署单元(ejb-jar,war等)不同的类加载器中。

如果可能的话,在应用程序之间使用本地EJB引用依赖于供应商。 您可以将SamEnqueue bean部署在单独的EJB模块中,并尝试通过每个应用程序的清单Class-Path:条目引用它。 确保在任一EAR文件中都没有SamEnqueueLocal副本。

或者,只需使用SamEnqueueRemote接口。

有关更多信息,请参阅Java EE规范的第8章。

请参阅知识中心中EJB模块主题的“本地客户端视图”部分:

EJB规范仅要求在同一应用程序中打包的EJB支持本地客户机视图。 这包括本地家庭,本地业务接口和无界面视图。 WebSphere®ApplicationServer允许对具有某些限制的单独应用程序中打包的EJB的本地客户机视图进行访问

  • 本地接口以及本地接口使用的所有参数,返回和exception类型必须对调用应用程序和目标EJB应用程序的类加载器可见。 您可以通过使用与服务器类加载器关联的共享库或使用与这两个应用程序关联的隔离共享库来确保这一点。 有关更多信息,请阅读创建共享库主题。

从bkail的回答中提供的链接,这些是我为使其工作所遵循的步骤。

  1. 从我的源EJB jar和包中取出本地远程接口,即SamEnqueueRemoteSamEnqueueLocal ,然后进入一个单独的jar文件。 虽然只是取出Local接口也可以。
  2. 创建一个共享库并将此jar放入其中。 必须隔离共享库,以便调用者和被调用者加载相同版本的类。
  3. 在调用者EAR中,使用查找或注入来获取对本地接口的引用。
  4. 将调用者和被调用者都部署到服务器,并确保在两个EAR的类路径中添加共享库。

此链接中提到的方法之一是类似的。

防止这种情况的一种方法是使用远程接口。 正如Paul所提到的那样,当调用者和被调用者在同一个JVM中时会发生优化,所以它并不像在单独的JVM中那样昂贵。 ORB具有类加载器机制,可确保使用与调用的每一侧兼容的类加载器加载调用者和被调用者的类。

选项2,包括新耳内的ejb jar,将无法解决问题。 尽管这两个类在两个类加载器中都可用,但是从被调用者传递给调用者的对象仍将使用另一个应用程序的类加载器进行实例化,并且不能是类型可分配的。 与选项3相同。

使其工作的第二种方法是将调用者和被调用者使用的类放在“WAS共享库”中,并配置两个应用程序以使用该共享库。 WAS InfoCenter文档中描述了共享库的主题以及如何配置它们…搜索“共享库”。

使其工作的第三种方法(三者中最不可取的方法)是将WAS服务器类加载器策略更改为“每个服务器一个类加载器”。 正如我在顶部提到的,默认情况是“每个应用程序一个类加载器(EAR文件)”。 每台服务器更改为一个类加载器可确保所有内容都由同一个类加载器加载,因此类型兼容,但会剥夺应用程序在每个应用程序在其自己的类加载器中的隔离/安全性优势。