7

一天前,我的应用程序是一个 EAR,包含一个 WAR、一个 EJB JAR 和几个实用程序 JAR 文件。我在其中一个实用程序文件中有一个 POJO 单例类,它可以正常工作,并且一切都很好:

EAR
 |--- WAR
 |--- EJB JAR
 |--- Util 1 JAR
 |--- Util 2 JAR
 |--- etc.

然后我创建了第二个 WAR 并发现(很难)每个 WAR 都有自己的 ClassLoader,所以每个 WAR 看到一个不同的单例,然后事情就从那里崩溃了。这不是很好。

EAR
 |--- WAR 1
 |--- WAR 2
 |--- EJB JAR
 |--- Util 1 JAR
 |--- Util 2 JAR
 |--- etc.

所以,我正在寻找一种方法来创建一个可以跨 WAR(跨 ClassLoaders?)工作的 Java 单例对象。EJB 注释似乎很有希望,@Singleton直到我发现 JBoss 5.1 似乎不支持该注释(它是作为 EJB 3.1 的一部分添加的)。我错过了什么吗 - 我可以@Singleton与 JBoss 5.1 一起使用吗?升级到 JBoss AS 6 现在不是一个选项。

或者,我很高兴不必使用 EJB 来实现我的单例。我还能做些什么来解决这个问题?基本上,我需要一个半应用程序范围* 挂钩到一大堆其他对象,如各种缓存数据和应用程序配置信息。作为最后的手段,我已经考虑将我的两个 WAR 合并为一个,但这将是非常糟糕的。

*含义:基本上在某一层以上的任何地方都可用;现在,主要是在我的 WAR 中——视图和控制器(在松散的意义上)。

编辑:我真的应该称它为Java EE而不是 J2EE,不是吗?


编辑2:再次感谢@Yishai 的所有帮助。经过一些试验和错误后,看起来我已经弄清楚如何在 JBoss 5 下跨 WAR 使用单个 ClassLoader。为了我自己,我将在下面详细说明这一点,希望其他人也会发现这也很有用。

注意这与在 JBoss 4 下执行此操作有很大不同(请参阅 Yishai 的回答或我下面的链接)。

不要jboss-web.xml为每个 WAR编写一个文件,也不要jboss.xml为 ear EJB-JAR编写一个文件,而是jboss-classloading.xml在每个 WAR 中放置一个文件,与 DD ( ) 位于相同的位置web.xml。的内容jboss-classloading.xml应该是:

<?xml version="1.0" encoding="UTF-8"?>
<classloading
    xmlns="urn:jboss:classloading:1.0"
    name="mywar.war"
    domain="DefaultDomain"
    parent-domain="Ignored"
    export-all="NON_EMPTY"
    import-all="true">
</classloading>

这来自 JBoss CW here,而(我认为)适用于 JBoss 4.x 的内容在此处描述。有关 JBoss 类加载(ing/ers)的更多一般信息:

据我所知,与 JBoss 4 相比,JBoss 5 的 JBoss 社区 wiki 文档相当缺乏。

4

5 回答 5

11

尽管 EJB3.1 规范引入了单例并且您的 JBoss 版本不支持它,但您可以使用 JBoss @Service 注释来创建单例。说明在这里。此外,您似乎已将 JBoss 配置为将您的 ejb jar 和战争彼此隔离。你不必那样做。您可以查看 jboss 特定 xml 文件中的loader-repository标记,以便您的整个耳朵共享一个类加载器(或者至少两场战争共享一个类加载器)。

话虽如此,我同意@duffymo 的观点,即在两场战争之间共享状态的单身人士是一个你应该走路的想法,如果不逃避的话。

编辑:关于单身人士,我建议你看看这样的问题在评论中也有一些很好的平衡)。

让对象本身保持缓存状态的想法是可以的,尤其是在 EJB3 中,您可以注入您的状态而不是静态引用它(如果您使用 @Service 注释,那么您需要 @Depends JBoss 特定的注释)。话虽如此,如果您在这里使用“正确的”单例,那么我希望您的 WAR 具有两个单独的类加载器这一事实的唯一问题是额外的内存占用。否则你会进入单例的问题区域(它们必须被初始化才能使用,使用它们的所有东西都必须确保它们首先被初始化,当然所有代码都与它们的初始化高度耦合)。

单身人士真正糟糕的地方是它们存储状态的地方,以便一个类可以更改状态,而另一个类可以获取它。在 3.1 之前,它在 EJB 中基本上是一个禁忌,即便如此,它也会产生很多并发问题。

编辑(进一步):所以你想使用类加载器存储库。我使用 JBoss 4.2.3,所以我不一定知道 JBoss5 的所有细节(它确实重写了它的类加载器,尽管他们说它几乎完全向后兼容),但是在 4.2.x 中默认情况下你的配置不会导致问题是因为部署在服务器上的所有耳朵共享同一个类加载器(“统一类加载器”)。我怀疑您要部署的服务器具有不同的配置,所以我不确定如何与它交互,但您需要做的是在您的耳朵中添加一个名为 jboss-app.xml 的文件(在与 application.xml 相同的位置)看起来像这样:

 <?xml version="1.0"?>
 <!DOCTYPE jboss-app PUBLIC "-//JBoss//DTD J2EE Application 4.2//EN"
        "http://www.jboss.org/j2ee/dtd/jboss-app_4_2.dtd">
 <jboss-app>
      <loader-repository>
      com.yourcomany:archive=yourear
      </loader-repository>
 </jboss-app>

这适用于 JBoss 4.2。5.1 有相同类型的标签,这里是xsd。它具有相同的加载器存储库概念。

应该是这样的。也就是说,只要你的 ejb-jar、war 等没有它,那么它们就不需要它。然而,你的战争(在 jboss-web.xml - 与 web.xml 相同的位置)可能需要同样的东西。在这种情况下,只要您以完全相同的方式命名存储库(如果我理解正确 - 我自己从未尝试过),它们将共享相同的类加载器。与 ejb.xml 位于相同位置的 jboss.xml 中配置的 EJB 也是如此。

可能会使其更清晰一些。

于 2010-05-04T17:47:36.290 回答
1

您可以使用 MBean 并将其绑定到 JNDI,然后在您想使用它的任何地方检索它。

MBean 可能部署在 .sar 文件中

于 2010-05-04T17:35:21.733 回答
1

我会在您的应用服务器上配置一个单独的对象池,因此它只包含单个实例。

你为什么要这样做是真正的问题。听起来你所有的应用程序都会以这种方式耦合。谷歌正在从其应用程序中消除单例。为什么你认为适合把它带回来?

于 2010-05-04T17:27:10.283 回答
1

如果您使用的是 Java EE 6,那么它支持单例 EJB。

于 2010-05-04T17:47:57.250 回答
0

如果可行,只需将具有单例的类放入 JAR 中,从 EAR 中取出 JAR,然后将 JAR 添加到 JBoss 类加载器(通过系统类路径或某个 lib 目录)。这会将类放在两个 WAR 共享的单个类加载器中。

单例将无法“看到”您的应用程序 WAR 等中的任何内容,因为它们位于较低的类加载器中。

但是,没有什么可以阻止您向单例(在服务器启动时)注入源自 WAR 等的工厂类,并且该类可以访问所有应用程序类。这使得单例更像是一个简单的容器。

但这很简单。

此外,如果您这样做,请确保当您关闭应用程序时,此单例持有的所有实例都被释放。当您取消部署应用程序时,单例的任何类引用都不会被 GC。因此,如果您有对存储在单例中的应用程序的引用,则服务器保存对单例类加载器的引用,该类加载器保存对您的单例类的引用,该类保存对您的应用程序类的引用,该类保存对应用程序类加载器,它包含对应用程序中所有类的引用。留下一个不好的烂摊子。

于 2010-05-04T18:08:34.160 回答