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

Correct way to lookup local EJB in websphere - Getting 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 {



}

我的应用程序容器是 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

查找 class 只是一个简单的 java class 在同一服务器的不同 EAR 中,代码如下:

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

SamEnqueueLocal samEnqueue = (SamEnqueueLocal) local;

查找正在使用本地的三个 JNDI 名称中的任何一个,但它没有转换为 SamEnqueueLocal。异常跟踪是:

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

... rest ommited

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

更新:

使用依赖注入时:

@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-jar、war 等)不同的 class 加载程序中的 EJB 的引用正在尝试注入它。

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

或者,只需使用 SamEnqueueRemote 界面。

有关详细信息,请参阅 Java EE specification 的第 8 章。

请参阅知识中心 EJB modules 主题的 "Local client views" 部分:

The EJB specification only requires local client views to be supported for EJBs packaged within the same application. This includes local homes, local business interfaces, and the no-interface view. WebSphere® Application Server permits access to local client views to EJBs packaged within a separate application with some restrictions

  • The local interface and all parameter, return, and exception types used by the local interface must be visible to the class loader of both the calling application and the target EJB application. You can ensure this by either using a shared library associated with a server class loader or by using an isolated shared library associated with both applications. Read the Creating shared libraries topic for more information.

...

根据 中提供的 link,这些是我为使其工作所遵循的步骤。

  1. 从我的源 EJB jar 中取出 LocalRemote 接口,即 SamEnqueueRemoteSamEnqueueLocal然后打包成一个单独的jar文件。虽然直接把Local接口拿出来也行。
  2. 创建一个共享库并将这个jar 放入其中。必须隔离共享库,以便调用者和被调用者加载相同版本的 class。
  3. 在调用方 EAR 中,通过查找或注入获取对本地接口的引用。
  4. 将调用者和被调用者都部署到服务器,并确保将共享库添加到两个 EAR 的 class路径中。

this link 中提到的方法之一是相似的。

One way to prevent this is to use a remote interface. As Paul mentioned, there is optimization that happens when the caller and callee are in the same JVM, so it's not as expensive as if they're in separate JVMs. The ORB has classloader machinery that ensures the classes for the caller and callee are loaded using classloaders compatible with each side of the call.

Option 2, including the ejb jar within the new ear, won't solve the problem. Even though the classes will be available in both classloaders, the object passed by-reference from callee back to caller will still be instanciated using the other application's classloader and will not be type-assignable. Same with Option 3.

The 2nd way to make it work is to place the classes used by caller and callee in a "WAS shared library" and configure both applications to use that shared library. The subject of shared libraries and how to configure them is described in the WAS InfoCenter documentation...search on "shared library."

The 3rd way to make it work, which is the least desirable of the three, is to change the WAS server classloader policy to "one classloader per server." The default, as I mentioned at the top, is "one classloader per application (EAR file)." Changing to one classloader per server ensures that everything gets loaded by the same classloader and is thus type-compatible, but deprives your applications of the isolation/security benefits that come from having each app in its own classloader.