WAS5 classloader and EJBs on different EARs.Now, this looks like a tough one:
I am talking about EJB 2.0, specifically about WebSphere 5.
When a web application (WAR) resides in the same EAR of an EJB (i.e. EAR contains both WAR and ejb-JAR), I understand that the classloader hierarchy is as follows:
EAR classloader --> EJB classloader --> WAR classloader
This means that the web application can "see" the EJB interfaces (and all the other classes as well), and there is no need to do anything with the WAR manifest file at all. Since these interfaces exist on a single classloader, there will be no ClassCastException when the web application typecasts (or narrows) the JNDI lookup result.
EJBs with local AND remote interfaces can have only one JNDI name on WebSphere, the possible conflict is resolved by the explicit EJB reference stated on web.xml (ejb-ref or ejb-local-ref). So the web application in t he same EAR can use any of the two interfaces. The web app *must* use the indirect JNDI name (java:comp/env/...) - using the real JNDI name returns always the remote stub.
This is somewhat expected behaviour, and it's ok for me.
Now, let us imagine another web application in another EAR (but on the same server). The questions are: How can this WAR see the EJB interfaces (let us pretend the interfaces themselves use only JRE commom classes, so there are no other dependencies)? How can another application (in another EAR) use this EJB? Can local interfaces be used, or are only the remote available?
I have built a web app (WAR2) referencing both the EJB interfaces of an EJB inside another EAR. So I have:
EAR1 -> (WAR1, EJB-JAR1) -> this EJB is used in WAR1 and WAR2
EAR2 -> (WAR2)
Let us assume these filenames (remember the EARs turn into folder names in "installedApps" directory):
EAR1 = EAR1App.ear
EAR2 = EAR2App .ear
WAR1 = webapp1.war
WAR2 = webapp2.war
EJB-JAR1 = ejb-jar1.jar
FIRST ATTEMPT: DIRTY TRICK
My first attempt was to use a dirty trick: editing EAR2 manifest file and inserting the Class-Path entry below:
Class-Path: ../EAR1App.ear/ejb-jar1.jar
Well, it partially works. This way WAR2 can do a successful lookup for the EJB remote home (both the "java:comp/env/..." and real JNDI name work), and everything works fine. But I cannot use the EJB local interface (even though we are at the same JVM), because typecasting the lookup result raises a ClassCastException.
It makes some sense, because "identical" classes in different classloaders are considered different, and the ClassCastException is expected. You see, it doesn't matter if the manifest Class-Path points to the very same JAR file that holds the EJB, it is still beeing loaded by a different classloader (the EAR2 classloader).
SECOND ATTEMPT: MINIMAL EJB-CLIENT.JAR
My sec ond idea was to package in a simple "ejb-client.jar" *only* the EJB interfaces, and I mean ONLY the interfaces. This "ejb-client-JAR" can can be deployed in WEB-INF/lib of WAR2 (or anywhere else, but referenced in the WAR2 manifest file). This approach works with WebLogic (WebLogic can even generate this client JAR for you).
Now *nothing* works in WAR2. I get a ClassCastException after a local home lookup (this is expected, see the "first attempt". Strangely, I get ClassCastException also after a remote home lookup. Apparently WebSphere complains about not beeing able to load the remote stub class - shouldn't this stub be loaded transparently to the web app (the EJB client)? Is this a side effect of the "remote-interface-but-same-JVM-so-let's-make-it-local" optimization? Very weird.
THIRD ATTEMPT: MINIMAL EJB-CLIENT.JAR IN COMMON CLASSLOADER (lib/ext)
Just to be sure I understood what was happening, I moved the minimal "ejb-client.jar" up in the classloader hierarc hy. This can be done by moving the "ejb-client.jar" file to the WebSphere "lib/ext" folder, or by declaring a Shared Library in the admin console and associating it to the server instance. This way the JAR classes (i.e. the EJB interfaces) is loaded by a higher classloader - and this will be the *only* classloader that loads these classes (remember the PARENT delegation rule, the standard classloader behaviour).
The problem here is that any changes in the EJB interfaces will require a server restart, but this is only for testing purposes anyway.
Guess what? EVERYTHING worked. WAR2 can do both the remote and local lookups (and typecasting) successfully. As expected in WebSphere, local lookups only work with the indirect name ("java:comp/env/..."), and remote lookups work with both the indirect or real JNDI name. There was no complaint about missing the stub class (like in the "second attempt").
One might argue that a change in a highly reused EJB interfaces would al so require the redeployment (or refactoring) of its client apps, so a server restart in this case might be acceptable. I don't know, it smells bad.
FOURTH ATTEMPT: EJB-CLIENT.JAR WITH STUBS
Opening the ejb-jar file in WinZip it is easy to pick wich classes are the EJB stubs: their names always are "_NameOfEJBInterface_Stub.class" (ex: for an EJB with "MyEJB.class" and "MyEJBHome.class" remote/home interfaces there will be the "_MyEJB_Stub.class" and "_MyEJBHome_Stub.class" stubs). So I included in the minimal "ejb-client.jar" these few stub classes, producing a "ejb-client-stub.jar" with both the interfaces and the chosen stubs.
My plan was to retry the "second attempt" with the "ejb-client-stub.jar" in place of the simpler "ejb-client.jar".
Now the remote lookups (and typecasts) work fine, but the local ones don't (as expected, because of the same-class-different-classloader issue). The "ejb-client-stub.jar" file can be distibuted to all web applications the use this EJB (WEB-INF/lib folder), or the manifest file of all these applications can point to a single path (ex: "C:/libs/ejb-client-stub.jar", yes, WebSphere accepts it).
CONCLUSIONS:
It seems that the only way to explicitly use EJB local interfaces in different EARs is to create a minimal "ejb-client.jar" (with the interfaces and its dependencies, like VOs) and "deploy" it at a higher classloader (like "%WAS_HOME%/lib/ext").
Another option is to assume that remote interfaces in the same JVM perform as well as local interfaces (I heard it is true with WebLogic and JBoss, but I haven't done any stress test at all on any server to prove it): the "fourth" and "first" attempt solutions are good enough then. Note that if this is true, it is a *VERY* bad idea to use local interfaces anywhere but in CMR entity beans (where they are required by the spec). I am one of those who believe that local interfaces were a silly idea from the very beginning, since every serious a pp server at that time already optimized the use of "remote" EJBs within the same JVM.
QUESTIONS TO WEBSPHERE EXPERTS:
Are these conclusions correct?
I am not bashing WebSphere (at least not this time). I am just trying to figure out what is the _best_ way to make an EJB reusable across different EARs in the same server.
Source: www.bing.com
Images credited to www.bing.com and