开发者

How do I write a Java EE/EJB Singleton?

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 开发者_StackOverflowas 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):

  • Overview
  • History
  • Use cases

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.


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 eradicating singleton from its apps. Why are you seeing fit to bring it back?


You can use a MBean and bind it to JNDI, then retrieve it wherever you want to use it.

The MBean might be deployed in a .sar file


If you are using Java EE 6, then it supports singleton EJBs.


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.

The singleton will not be able to "see" anything in your applications WARs etc, as they're in a lower classloader.

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.

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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜