要了解为什么会发生此异常,需要掌握两点:
- 一个 EJB 如何引用另一个 EJB
- EJB 类加载
让我们首先处理第一个,所以让我们命名第一个 EJB(包含FirstEJBRemote
类)EJB_A和第二个 EJB(试图访问 EJB_A 的方法)EJB_B。不深入@LocalBean
讨论注释和类似的东西,EJB_B 访问 EJB_A 方法的唯一方法是通过接口。换句话说,EJB_A 实现了 EJB_B 声明的某个接口(在您的情况下是FirstEJBRemote
),并通过注入检索 EJB_A 的实例。到现在为止还挺好。
接下来,我们必须了解 EJB 类加载器。自然,您的应用程序(在本例中为 EJB)在编译时使用的每个外部类/库也必须在运行时可用。否则,ClassNotFoundException
将被抛出,这正是您的情况,因为 EJB_BFirstEJBRemote
在编译时使用:
private FirstEJBRemote ejb;
要在运行时向 EJB 提供这个外部类/库,必须执行以下操作:
- 把class/library放到应用服务器的lib目录下
- 将类/库与 EJB 打包在一起
- 将类/库放在包含 EJB 的 EAR 类路径中
我们将忽略第三个选项,因为您说您对 EAR 不感兴趣。因此,您可以将FirstEJBRemote
接口(当然是以 .jar 文件的形式)放在 Glassfish 的 lib 目录中,EJB_A 和 EJB_B 都可以使用它(因此,您不必将它与 EJB_A 打包)或者您也可以将此接口与 EJB_B 打包在一起,注意它与 EJB_A 位于同一包中。
在您的评论中,您想知道为什么根本需要这个“复杂”的过程。答案很简单——如果 EJB 类加载器不是相互隔离的,那么使用 EJB_A 部署的每个类都对 EJB_B 可用。在这种特殊情况下,这是希望的行为,但想象一下当 EJB_A 包含某个库(例如日志库)的版本 1 并且 EJB_B 使用相同的库但版本 2 时会发生什么。这两个类都将被加载,您将在任何地方遇到冲突,ClassCastException
等等(或者如果幸运的话,EJB_B 将“仅”被迫使用 version1,因为该类已经被类加载器加载)。
最后,摘自Oracle GlassFish 3.1 指南:
规避类加载器隔离
由于每个应用程序或单独部署的模块类加载器世界都是隔离的,因此应用程序或模块无法从另一个应用程序或模块加载类。这可以防止不同应用程序或模块中的两个类似命名的类相互干扰。