How do I write a Java EE/EJB Singleton?
Asked Answered
T

5

7

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.

Trinitrotoluene answered 4/5, 2010 at 17:22 Comment(1)
@Bears, re: the edit: Only if you work in Oracle's marketing department.Notary
N
11

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.

Notary answered 4/5, 2010 at 17:47 Comment(6)
I am definitely open to being convinced on this point, but I am teh noob when it comes to J2EE (I have no formal training and have basically just done JIT learning since I started working on my company's J2EE apps). So: why is it bad to share state between WARs? The way I see it, each WAR is just a different view into my application. One is for everyday users, and the other is for app admin functions (like create/edit/delete users, access permissions, etc).Trinitrotoluene
I expanded my answer to discuss Singletons, but the issues with them are not J2EE specific (although J2EE level applications have some issues when you talk about clustering etc.).Notary
After consulting with the higher-ups, the goal for now is just to get things working, and worry about Doing It Right later (aka, the next project). I'm going to try to use one classloader for the whole EAR to minimize the code I need to change, but it looks like there are words missing from that loader-repository link. Any idea where I can find a complete document on what I need to do?Trinitrotoluene
Well, I'm pretty sure that I have the correct contents of the various DDs, but it's still using different classloaders for each WAR, as best I can tell... to be continued tomorrow, I suppose.Trinitrotoluene
@Bears, if you get stuck the classloader woods, you might make faster progress going with the @Service (and @Management to expose an interface and fill the singleton with its start method) and injecting that with @Depends in your EJB. Or you might end up in classloader hell.Notary
I may try that approach after all. Since my singleton (right now) is in a Utility project, not an EJB JAR, rewriting it as an @Service also has its headaches. Anyway, thanks for all your help! I'll post another question if I run into something specific.Trinitrotoluene
W
1

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?

Wallache answered 4/5, 2010 at 17:27 Comment(2)
Could you elaborate on creating a separate object pool? I'm pretty new to J2EE so I don't really know what that means, particularly in practical/implementation terms. Why do I want a singleton? It seemed like a reasonable solution to my needs - a centralized place for persistent state over the life of the app. These include app config info, and large amounts of data loaded once from a database at app startup (cached during runtime afterwards). I'm not opposed to using something other than a singleton, but I don't know of a better solution.Trinitrotoluene
"It seemed like a reasonable solution to my needs - a centralized place for persistent state over the life of the app." - that's what databases should be for. Do you know that it's necessary to have it all in memory? Is performance such a problem, or are you assuming that it'll be an issue? A better solution would be to use a distributed cache like Terracotta that's built for this. It's not easy to do - don't assume that you know better.Wallache
E
1

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

Etherealize answered 4/5, 2010 at 17:35 Comment(2)
Unfortunately, JNDI lookups are slow (as I understand it). Is that really a viable, scalable option?Trinitrotoluene
It shouldn't be too slow in the same JVM. In case the performance cost it too high, you can still store the lookup result somewhereEtherealize
O
1

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

Oleaceous answered 4/5, 2010 at 17:47 Comment(1)
I'm pretty sure I'm not. Did you miss the part about JBoss 5.1 vs 6?Trinitrotoluene
L
0

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.

Levkas answered 4/5, 2010 at 18:8 Comment(2)
I think that last paragraph gives a good reason to avoid this. Another is that you lose the ability to hot-deploy.Notary
You lose the ability to hot deploy the actual Singleton, but not the rest of the application. So, once primary development has occurred, yea, it's a truth about the implementation, but likely not an actual real world issue. The key is ensuring that the application lifecycle works properly in terms of starting up and tearing down the Singleton and the resources it holds. It's not a horrible burden. Finally, the Singleton can be little more than a static reference pointing to the actual, sophisticated logic within the application itself.Levkas

© 2022 - 2024 — McMap. All rights reserved.