9 Replies Latest reply on Feb 27, 2007 10:20 AM by dkrleza

    A new understanding of ClassLoaders...

    mbabauer

      Recently I have been playing a lot with the new JBoss 4.0.x, Spring, and the like. As an excercise in learning, I have constructed a little "project" to build a Hash Generator (MD5, SHA, etc). This little project uses:
      - Hibernate for persistance of data
      - Spring for DI (dependency injection) and convenience classes
      - JBossMQ for messaging from the code that generated the permutations of text to hash and the code doing that hashing
      - GWT (Google Web Toolkit) for the purpose of learning GWT

      I seem to have run into a snag recently, however: My complete and utter lack of understanding concerning Class Loaders. More specifically, my confusion as to why its not working how I intuitively think it should.

      I come from a JBoss 3 world, which seemed to favor the "Unified" Class Loader. In those days, anyone could see anyone...total visibility of all classes across the board. This came with some problems, however, when two deployment units required different versions of the same library. Most of the time, you couldn't tell WHERE a class was comming from, or whether it was the one you thought you had.

      Along comes JBoss 4 with its "Hierarchical" Class Loader. As a CS major, I am all to familiar with the term Hierarchical, and it conjurs visions of trees and such on mention. Intuitively, this is how I picture it working:

      JBoss Libs (lib folder at root of JBoss)
       \
       <server> Libs (lib folder under your server
       \
       EAR (everything at root of EAR file)
       \
       WAR (everything in WEB-INF/lib
       \
       JAR etc
       EJB
       SAR


      In this illustration, the WAR can see everything in his lib, plus have visibility up the hierarchy to see everything above it. The WAR would not have visibility accross to his siblings (EJB and SAR). This would be great, except for the fact this doesn't work. Let me illustrate why.

      In my little project mentioned above, I have a library file that has my DAOs/VOs, long with the parent Spring context file. It gets placed at the root of the EAR along with all the supporting JARs from Spring and the WAR. In my mind, the WAR should now have visiblity to the DAO layer, since the DAOs are in the root of the EAR. When it ran, however, I found that the WAR could NOT see the Spring classes at all. I have to admit, I am new to EAR deployment, so I am trusting Maven to put the libs where it thinks it needs them.

      To make matters worse, when I put the Spring JAR into the JBoss lib dir, it STILL didn't see it! I finally had to add the Spring JAR to the WAR's lib folder, which got past this hurdle.

      The next problem is in the DAO layer. As I stated, I am using Spring, and inside the JAR containing the DOA/VO classes is a "parent" Spring context. When the WAR deploys, it uses the ContextLoaderListener in the web.xml to load the WAR's Spring context, using a classpath to a file. It also loads the parent context, also defined as a classpath to a file. When I deployed the DAO JAR into the server lib and the WAR solo, everything worked fine. But, with the DAO JAR in the root of the EAR along with the WAR, the ContextLoaderListener never finds the parent context, I.E. it can't find the file in the classpath. The thing is, the DAO JAR is designed to be a shared DAO library layer, so visibility across the EAR is important.

      Once again, admittedly I am VERY weak on EAR deployment, which is one of the reasons I choose to do this. In my JBoss 3.x past, we simply deployed everything, WARs/JARs/SARs/etc, directly in the deploy dir of the server. We also loaded the servers lib folder with all libraries.

      I call out to you, my JBoss brothers and sisters...help those like me that have no idea whats going on in the Class Loader. Help us to understand as best we can how this stuff is working.

        • 1. Re: A new understanding of ClassLoaders...
          peterj

          You have to be very careful on who is is invoking whom. Spring tends to want to instantiate classes, and if those classes are defined in your war file, then unless you place the Spring jars in your war file, it will not be able to see them. What I have been able to do is place all of the classes that Spring cares about into a jar file, which I usually place in an ear file but at times have placed in the deploy directory, and have that work even with the Spring jar files in the lib directory (by default, jars in the lib directory, deploy directory and in an ear or sar file all have visibility to each other, only the classes in the war file are 'hidden').

          • 2. Re: A new understanding of ClassLoaders...
            mbabauer

            So, let me see if I can sum up what you are saying. Stuff in the JBoss lib and the server lib "should" be visible globally (JBoss lib across all servers, server lib across just that server), and things in the EAR should be visible to everything in the EAR *Except* the WAR(s).

            This leads into another question...how is one to access shared resources from within an EAR-deployed WAR if you can't see out? Is everything typically done via remoting of some sorts? This seems grossly inefficient.

            • 3. Re: A new understanding of ClassLoaders...
              peterj

              Not quite. Things in the WAR can see everything, but nothing can see into the WAR. Other than that, everybody can see everybody else.

              Everything in the lib, deploy, ear, sar are in the same class repository (thus they all have visibility to each other) by default, though you can configure an ear or sar to use a separate class repository (see the JBoss Messaging sar for an example) in which case things in that ear and sar are hidden in much the same way things in a war are hidden.

              By the way, I created some simple classes that referenced each other and deployed them different locations to verify the above.

              • 4. Re: A new understanding of ClassLoaders...
                mbabauer

                This is were my confussion lies.

                In my example I have 4 units:
                - HG-DataAccess.jar, which has the DOAs (model) and a spring context called componentContext.xml defining the "parent" context for Spring, and a beenRefFactory.xml that setups up the DataSources and all the hibernate stuff, in the root.
                - HG-Service.jar that defines some service util classes (controller)
                - HG-Service.war, which is the WAR stuff, and has a web.xml that uses the ContextLoaderListener to bootstrap the Spring stuff. This is done using an Spring context locally to build the requred stuff from the HG-Service.jar and the HG-Service.war, as well as defining the parent context as classpath:componentContext.xml
                - HG.ear which comprises some combination of the above.

                Upon deploy, the WAR kicks off the web.xml, which in tern loads the ContextLoaderListener, which then loads the local applicationContext.xml (local to the WAR) and the componentContext.xml (which is defined as "classpath:componentContext.xml", and should be found). I have tried putting HG-DataAccess.jar in the EAR, in the Server lib, and in the JBoss lib. No matter where I put it, I get the folliowing spewage:

                13:00:07,109 INFO [XmlBeanDefinitionReader] Loading XML bean definitions from class path resource [applicationContext.xml]
                13:00:07,117 ERROR [ContextLoader] Context initialization failed
                org.springframework.beans.factory.BeanDefinitionStoreException: IOException parsing XML document from class path resource [applicationContext.xml]; nested exception is java.io.FileNotFoundException: class path resource [applicationContext.xml] cannot be opened because it does not exist
                Caused by:
                java.io.FileNotFoundException: class path resource [applicationContext.xml] cannot be opened because it does not exist
                 at org.springframework.core.io.ClassPathResource.getInputStream(ClassPathResource.java:135)
                ...


                Hence the confussion. If the WAR did indeed have visibility to the EAR, server lib, and JBoss lib, any one of the above combinations would not throw this error since anyone one of them would be visible *from* the WAR, which would then find the aformentioned componentContext.xml in the classpath.

                When I put the HG-DataAccess.jar into the WAR, it loads just fine, but that defeats the whole "shared library" excercise.

                My next step, I think, will be to add a dummy service to load the HG-DataAccess.jar's Spring context, have it put the classes that need to be shared in JNDI, and then pull them out on the WAR side from JNDI. This is not what I consider ideal.

                • 5. Re: A new understanding of ClassLoaders...
                  peterj

                  Now I am confused. Your text seems to imply that applicationContext.xml is OK but componentContext.xml cannot be found. But the error message states that applicationContext.xml is not found, and since you indicate this file is located in the WAR file, I would not expect Spring to find this file unless Spring is also in the war file.

                  • 6. Re: A new understanding of ClassLoaders...
                    mbabauer

                    Your right! Guess I should read better. Earlier I had it complaining about the componentContext.xml when I had it sitting in the EAR. I think I know whats happening, at least with the applicationContext.xml...

                    Even though the ContextLoaderListener is started from the web.xml, it seems that the "ClassLoader" it runs with does not have visibility to the WAR deploy. This I can understand, and makes sense now.

                    What I am still having a hard time understanding is why everything at the WAR level can't see whats in the EAR level. When I place the HG-DataAccess.jar in the EAR (this has the componentContext.xml), it blows up with the same error as above, but indicating it can't find the componentContext.xml instead. This seems to indicate that the WAR can't see into the EAR. Furthermore, when I have Spring jar in the EAR, it complains upon deployment of the web.xml that it can't find the ContextLoaderListener class. Finally, everything works fine if I take the EAR out of the equation, and put the HG-DataSource.jar in the Server /lib and the HG-Service.WAR (which contains the HG-Service.jar) in the Server /deploy.

                    • 7. Re: A new understanding of ClassLoaders...
                      peterj

                      Sorry, but without knowing more details about the relationships among your classes in the various jar files, I really cannot answer your last question.

                      There is something you might try to learn a little more about what is going on: start the JVM passing it the -verbose:class option. This causes the JVM to state where it found each class. Sometimes looking that this output clears up what is going on, especially if you see a class being loaded from an unexpected location.

                      • 8. Re: A new understanding of ClassLoaders...
                        mbabauer

                        I have tried to lick problem every which way from Sunday, but I keep running into the same classloader problems trying to find either classes or the Spring contexts.

                        Riddle me this, Batman: What is the recommended route for deploying a single, self-contained EAR housing a shared DAO layer and one or more WARs? Just as a refernce, here is a link to my lastest attempt in a nice TARBALL: http://codechimp.net/files/HashGrabber.tar.gz.

                        I have everything building with Maven 2, and it *should* build from scratch. Not much really.

                        • 9. Re: A new understanding of ClassLoaders...
                          dkrleza

                          OK, this is very good topic which explains a lot to those who are using using MVC patterns and multiple WEB modules per enterprise application. You need to place MVC library into WEB-INF/lib of every module. If you don't do so and use your MVC library as utility jar inside enterprise application, your MVC controller will not be able to load and use classes placed inside WEB modules.

                          Let me ask another question - more JBoss related. WAS5 (and higher) has ability to create single classloader for whole enterprise application. This means that all WEB modules and additional utility libraries are loaded in single classloader.
                          Does JBoss support this funcionality?