如何编写 Java EE/EJB 单例?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/2767460/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How do I write a Java EE/EJB Singleton?
提问by Matt Ball
A day ago my application was one EAR, containing one WAR, one EJB JAR, and a couple of utility JAR files. I had a POJO singleton class in one of those utility files, it worked, and all was well with the world:
一天前,我的应用程序是一个 EAR,包含一个 WAR、一个 EJB JAR 和几个实用程序 JAR 文件。我在其中一个实用程序文件中有一个 POJO 单例类,它工作正常,而且一切都很好:
EAR
|--- WAR
|--- EJB JAR
|--- Util 1 JAR
|--- Util 2 JAR
|--- etc.
Then I created a second WAR and found out (the hard way) that each WAR has its own ClassLoader, so each WAR sees a different singleton, and things break down from there. This is not so good.
然后我创建了第二个 WAR 并发现(艰难的方式)每个 WAR 都有自己的类加载器,所以每个 WAR 看到一个不同的单例,然后事情从那里开始崩溃。这不太好。
EAR
|--- WAR 1
|--- WAR 2
|--- EJB JAR
|--- Util 1 JAR
|--- Util 2 JAR
|--- etc.
So, I'm looking for a way to create a Java singleton object that will work across WARs (across ClassLoaders?). The @SingletonEJB annotation seemed pretty promising until I found that JBoss 5.1 doesn't seem to support that annotation (which was added as part of EJB 3.1). Did I miss something - can I use @Singletonwith JBoss 5.1? Upgrading to JBoss AS 6 is not an option right now.
所以,我正在寻找一种方法来创建一个可以跨 WAR(跨类加载器?)工作的 Java 单例对象。该@SingletonEJB注解似乎很看好,直到我发现,JBoss的5.1似乎不支持注释(其中添加作为EJB 3.1的一部分)。我错过了什么 - 我可以@Singleton与 JBoss 5.1 一起使用吗?升级到 JBoss AS 6 现在不是一个选项。
Alternately, I'd be just as happy to not have to use EJB to implement my singleton. What else can I do to solve this problem? Basically, I need a semi-application-wide* hook into a whole bunch of other objects, like various cached data, and app config info. As a last resort, I've already considered merging my two WARs into one, but that would be pretty hellish.
或者,我很高兴不必使用 EJB 来实现我的单例。我还能做些什么来解决这个问题?基本上,我需要一个半应用程序范围*挂钩到一大堆其他对象中,例如各种缓存数据和应用程序配置信息。作为最后的手段,我已经考虑将我的两个 WAR 合并为一个,但这将是非常糟糕的。
*Meaning: available basically anywhere above a certain layer; for now, mostly in my WARs - the View and Controller (in a loose sense).
*含义:基本上可以在某一层以上的任何地方使用;现在,主要是在我的 WAR 中 - 视图和控制器(在松散的意义上)。
Edit:I should really be calling it Java EErather than J2EE, shouldn't I?
编辑:我真的应该称它为Java EE而不是 J2EE,不是吗?
Edit 2:Many thanks again to @Yishai for all the help. After some trial-and-error it looks like I've figured out how to use a single ClassLoader across WARs under JBoss 5. I'm detailing this below for my own sake, and hopefully others will find this useful as well.
编辑 2:再次感谢@Yishai 的所有帮助。经过一些反复试验,我似乎已经找到了如何在 JBoss 5 下跨 WAR 使用单个 ClassLoader 的方法。为了我自己,我在下面详细介绍了这一点,希望其他人也能发现这很有用。
N.B. this is rather different from doing this under JBoss 4 (see Yishai's answer or my links below).
注意,这与在 JBoss 4 下执行此操作有很大不同(请参阅 Yishai 的回答或下面的链接)。
Instead of writing a jboss-web.xmlfor each WAR, and a jboss.xmlfor ear EJB-JAR, put a jboss-classloading.xmlfile in each WAR, in the same location as the DD (web.xml). The contents of jboss-classloading.xmlshould be:
不是jboss-web.xml为每个 WAR编写一个,并jboss.xml为 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>
This follows from the JBoss CW here, whereas what (I think) works for JBoss 4.x is described here. More general info on JBoss classload(ing/ers):
这是从 JBoss CW here而来的,而(我认为)适用于 JBoss 4.x 的内容在这里描述。关于 JBoss 类加载(ing/ers)的更多一般信息:
As best I can tell, the JBoss community wiki docs are pretty lacking for JBoss 5 in comparison to JBoss 4.
据我所知,与 JBoss 4 相比,JBoss 5 的 JBoss 社区 wiki 文档非常缺乏。
回答by Yishai
Although the EJB3.1 spec introduces singleton and your version of JBoss doesn't support it, you can use the JBoss @Service annotation to create a singleton. Instructions here. Also, it seems that you have JBoss configured to isolate your ejb jars and wars from each other. You don't have to do that. You can look at the loader-repositorytag in the jboss specific xml files so that your whole ear shares one classloader (or perhaps that at least the two wars share one classloader).
尽管 EJB3.1 规范引入了单例并且您的 JBoss 版本不支持它,但您可以使用 JBoss @Service 注释来创建单例。说明在这里。此外,您似乎已将 JBoss 配置为将 ejb jar 和 war 彼此隔离。你不必那样做。您可以查看jboss 特定 xml 文件中的loader-repository标记,以便您的整个耳朵共享一个类加载器(或者至少两场War共享一个类加载器)。
All that being said, I agree with @duffymo, that a singleton which shares state between the two wars is an idea that you should be walking, if not running away from.
话虽如此,我同意@duffymo 的观点,即在两次War之间共享状态的单身人士是一个您应该走路的想法,如果不是逃跑的话。
Edit: Regarding singletons, I suggest you look at questions like this one(which also has some nice balance in the comments).
编辑:对于单身,我建议你看一下这样的问题这一个(其中也有在评论一些很好的平衡)。
The idea of having an object hold cached state in and of itself is ok, especially with EJB3 where you can inject your state instead of statically referencing it (if you use the @Service annotation, then you want the @Depends JBoss specific annotation). That being said, if you were using a "proper" singleton here, then I would expect that your only problem with the fact that your WARs have two separate classloaders is the extra memory footprint. Otherwise you are into the problematic area of singletons (where they have to be initialized to be used, everything that uses them has to ensure they are initialized first, and of course all code gets highly coupled with their being initialized).
让对象本身保存缓存状态的想法是可以的,尤其是在 EJB3 中,您可以在其中注入您的状态而不是静态引用它(如果您使用 @Service 注释,那么您需要 @Depends JBoss 特定的注释)。话虽如此,如果您在这里使用“适当的”单例,那么我希望您的 WAR 具有两个单独的类加载器这一事实的唯一问题是额外的内存占用。否则你就会陷入单例的问题领域(它们必须被初始化才能使用,使用它们的一切都必须确保它们首先被初始化,当然所有的代码都与它们的初始化高度耦合)。
Where Singletons are really really bad is where they store state so that one class can change state and another class picks it up. It is basically a no-no in EJBs until 3.1, and even then it makes a lot of concurrency issues.
单例真正糟糕的地方在于它们存储状态的地方,以便一个类可以更改状态,而另一个类可以获取它。在 3.1 之前,它在 EJB 中基本上是一个禁忌,即便如此,它也会产生很多并发问题。
Edit (further): So you want to go with the classloader repository. I use JBoss 4.2.3, so I don't necessarily know all of the ins and outs of JBoss5 (which did rewrite its classloader although they say it is almost fully backwards compatable), however in 4.2.x by default your configuration causes no problems because all the ears deployed on the server share the same classloader (the "unified classloader"). What I suspect is that the server you are deploying to has the configuration differently, so I'm not quote sure how to interact with it, but what you have to do is add a file called jboss-app.xml in your ear (in the same location as the application.xml) that looks something like this:
编辑(进一步):所以你想使用类加载器存储库。我使用 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>
That is for JBoss 4.2. 5.1 has the same type of tag, here is the xsd. It has the same loader-repository concept.
那是针对 JBoss 4.2 的。5.1 有相同类型的标签,这里是xsd。它具有相同的加载器存储库概念。
That shouldbe it. That is, as long as your ejb-jar, war, etc. don't have it, then they don't need it. However, your wars (in jboss-web.xml - same location as the web.xml) may need the same thing. In this case as long as you name the repository exactly the same way (if I understand correctly - never tried it myself) they will share the same classloader. The same goes for the EJB as configured in the jboss.xml that goes in the same location as the ejb.xml.
这应该是它。也就是说,只要你的ejb-jar、war等没有,那他们就不需要了。然而,你的War(在 jboss-web.xml - 与 web.xml 相同的位置)可能需要同样的东西。在这种情况下,只要您以完全相同的方式命名存储库(如果我理解正确 - 自己从未尝试过),它们将共享相同的类加载器。对于在 jboss.xml 中配置的 EJB 也是如此,它与 ejb.xml 位于相同的位置。
Thismight make it a bit clearer.
这可能会使它更清楚一些。
回答by duffymo
I'd configure a separate object pool on your app server so it only contained the single instance.
我会在您的应用服务器上配置一个单独的对象池,因此它只包含单个实例。
Why you would want to do this is the real question. Sounds like all your apps will be coupled this way. And Google is eradicatingsingleton from its apps. Why are you seeing fit to bring it back?
为什么要这样做才是真正的问题。听起来你所有的应用程序都会以这种方式耦合。而谷歌正在消除其应用单。为什么你认为适合把它带回来?
回答by Guillaume
You can use a MBean and bind it to JNDI, then retrieve it wherever you want to use it.
您可以使用 MBean 并将其绑定到 JNDI,然后在您想要使用它的任何地方检索它。
The MBean might be deployed in a .sar file
MBean 可能部署在 .sar 文件中
回答by Aravind Yarram
If you are using Java EE 6, then it supports singleton EJBs.
如果您使用的是 Java EE 6,那么它支持单例 EJB。
回答by Will Hartung
If practical, simply take the class that has the singleton, put it in a JAR, take the JAR OUT of the the EAR, and add the JAR to the JBoss classloader (via the system classpath, or a some lib directory). This puts the class in a single classloader shared by both WARs.
如果可行,只需将具有单例的类放入 JAR,将 JAR 从 EAR 中取出,然后将 JAR 添加到 JBoss 类加载器(通过系统类路径或某个 lib 目录)。这会将类放在两个 WAR 共享的单个类加载器中。
The singleton will not be able to "see" anything in your applications WARs etc, as they're in a lower classloader.
单例将无法“看到”您的应用程序 WAR 等中的任何内容,因为它们位于较低的类加载器中。
However, there's nothing stopping you from injecting into the singleton (at server startup) a factory class, that originates from the WARs et al, and THAT class has access to all of the apps classes. That makes the singleton more of a simple container.
但是,没有什么可以阻止您将源自 WARs 等的工厂类(在服务器启动时)注入到单例中,并且该类可以访问所有应用程序类。这使得单例更像是一个简单的容器。
But this is straightforward to do.
但这很容易做到。
Also, if you do this, make sure when you shut down you application, that any instances held by this singleton are freed. Any class reference by the singleton will not be GC'd when you undeploy the application. So, if you have a reference to your app stored in the singleton, then the server holds a reference to the singletons classloader, that classloader holds a reference to your singleton class, which holds reference to you apps class, which holds a reference to the apps CLASSLOADER, and THAT holds a reference to all of the classes in your app. Not a nice mess to leave behind.
此外,如果您这样做,请确保在关闭应用程序时,释放此单例持有的所有实例。当您取消部署应用程序时,单例的任何类引用都不会被 GC 处理。因此,如果您有一个对存储在单例中的应用程序的引用,那么服务器保存对单例类加载器的引用,该类加载器保存对您的单例类的引用,该类保存对您的应用程序类的引用,该类保存对应用程序类的引用应用类加载器,并持有对应用程序中所有类的引用。留下来可不是一团糟。

