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.
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?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With