如何编写Java EE / EJB Singleton?

前一天,我的应用程序是一个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运行的Java单例对象(跨ClassLoaders?)。 在我发现JBoss 5.1似乎不支持该注释(作为EJB 3.1的一部分添加)之前,@ @Singleton EJB注释似乎很有希望。 我错过了什么 – 我可以将@Singleton与JBoss 5.1一起使用吗? 目前无法升级到JBoss AS 6。

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

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

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


编辑2:非常感谢@Yishai的所有帮助。 经过一些反复试验后,看起来我已经想出了如何在JBoss 5下使用单个ClassLoader来解决这个问题。我在下面详细说明了这个问题,希望其他人也会发现这个问题很有用。

注意,这与JBoss 4下的这一点完全不同(参见Yishai的回答或下面的链接)。

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

    

这是从JBoss CW 这里得到的 ,而在这里描述了(我认为)适用于JBoss 4.x的内容。 有关JBoss类加载(ing / ers)的更多常规信息:

  • 概观
  • 历史
  • 用例

我可以说,与JBoss 4相比,JBoss社区wiki文档非常缺乏JBoss 5。

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

所有这一切,我同意@duffymo,在两场战争中分享状态的单身人士是一个想法,你应该走路,如果不是逃避。

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

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

单身者真的非常糟糕的地方是他们存储状态的地方,这样一个class级可以改变状态而另一个class级可以改变状态。 在3.1之前,它基本上是禁止使用的,即使这样,它也会产生很多并发问题。

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

      com.yourcomany:archive=yourear   

那是JBoss 4.2。 5.1具有相同类型的标签,这里是xsd 。 它具有相同的loader-repository概念。

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

这可能会让它更清晰一些。

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

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

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

MBean可能部署在.sar文件中

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

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

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

但是,没有什么可以阻止你注入一个工厂类(在服务器启动时),这个工厂类来自WAR等人,而且THAT类可以访问所有的应用程序类。 这使得单身人士更像是一个简单的容器。

但这很简单。

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