如何编写Java EE / EJB Singleton?

时间:2010-05-04 17:22:36

标签: java java-ee singleton ejb jboss5.x

前一天,我的应用程序是一个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注释似乎很有希望。我错过了什么 - 我可以在JBoss 5.1中使用@Singleton吗?目前无法升级到JBoss AS 6。

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

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

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


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

N.B。这与JBoss 4下的这一点完全不同(见Yishai的回答或下面的链接)。

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

jboss-classloading.xml

这是来自JBoss CW here,而我认为(适用于JBoss 4.x)的内容是here。有关JBoss类加载(ing / ers)的更多常规信息:

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

5 个答案:

答案 0 :(得分:11)

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

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

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

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

单身人士真的非常糟糕的地方是他们存储状态,以便一个班级可以改变状态而另一个班级可以改变状态。在3.1之前,它基本上是一个禁忌,直到3.1,它甚至会产生很多并发问题。

编辑(进一步):所以你想要使用classloader存储库。我使用的是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。它具有相同的loader-repository概念。

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

This可能会让它更清晰一点。

答案 1 :(得分:1)

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

为什么你想要这样做才是真正的问题。听起来你的所有应用都会以这种方式耦合。并且Google is eradicating来自其应用的单身人士。为什么你认为适合把它带回来?

答案 2 :(得分:1)

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

MBean可能部署在.sar文件中

答案 3 :(得分:1)

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

答案 4 :(得分:0)

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

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

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

但这很简单。

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