且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

如何编写Java EE / EJB Singleton?

更新时间:2023-12-03 14:33:34

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



所有这一切,我同意@duffymo,一个单一的共享状态两个战争是一个想法,你应该走路,如果没有逃跑。



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



拥有一个对象保持缓存状态本身的想法是确定的,尤其是EJB3,您可以在其中注入状态而不是静态引用它(如果使用@Service注释,那么你想要@Depends JBoss特定注释)。话虽如此,如果你在这里使用一个适当的单身,那么我会期望你的WAR有两个独立的类加载器的唯一问题是额外的内存占用。否则,您将进入单个问题区域(必须初始化它们才能被使用,所有使用它们的事情都必须首先被初始化,当然所有代码都被初始化得很好地耦合起来)。



单身人士真的很糟糕的地方在于他们存储状态,以便一个类可以改变状态,另一个类选择它。直到3.1,它基本上是EJB中的no-no,即使这样也会产生很多并发问题。



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

 <?xml version =1.0?&gt ; 
<!DOCTYPE jboss-app PUBLIC - // JBoss // DTD J2EE应用程序4.2 // EN
http://www.jboss.org/j2ee/dtd/jboss-app_4_2。 DTD&GT;
< jboss-app>
< loader-repository>
com.yourcomany:archive = yourear
< / loader-repository>
< / jboss-app>

这是针对JBoss 4.2的。 5.1具有相同类型的标签,以下是 xsd 。它具有相同的加载程序库概念。



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



这可能会更清楚一些。


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
 |--- 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.

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 @Singleton EJB 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 @Singleton with JBoss 5.1? Upgrading to JBoss AS 6 is not an option right now.

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.

*Meaning: available basically anywhere above a certain layer; for now, mostly in my WARs - the View and Controller (in a loose sense).

Edit: I should really be calling it Java EE rather than J2EE, shouldn't I?


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.

N.B. this is rather different from doing this under JBoss 4 (see Yishai's answer or my links below).

Instead of writing a jboss-web.xml for each WAR, and a jboss.xml for ear EJB-JAR, put a jboss-classloading.xml file in each WAR, in the same location as the DD (web.xml). The contents of jboss-classloading.xml should be:

<?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):

As best I can tell, the JBoss community wiki docs are pretty lacking for JBoss 5 in comparison to JBoss 4.

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-repository tag 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).

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.

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).

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.

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:

 <?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.

That should be 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.

This might make it a bit clearer.