Vincent, can you send me (privately) a WAR file that illustrates your
difficulties?  But with the code below, and JUnit not visible to the web
app, I would expect to get the error when instantiating B, due to its
direct reference to D, which has a reference to JUnit.

From my experiments (and experience on other apps), it doesn't make any
difference whether the application classes are packed into JARs in
/WEB-INF/lib or unpacked in /WEB-INF/classes (since these are all loaded
by the web app class loader).

Craig

On Fri, 27 Jul 2001, Vincent Massol wrote:

> Hi Craig,
> 
> Thanks again. I have tried it again and it still doesn't work. I'll narrow a
> bit more my example.
> 
> C.java
> ---
> public class C extend D
> {
> ...
> }
> ---
> 
> D.java
> ---
> import junit.framework.*;
> 
> public class D extends TestCase
> {
> ...
> }
> ---
> 
> and the exact code in class B:
> 
> B.java
> ---
> public class B
> {
>   public B()
>   {
>   }
>   public doSomething()
>   {
>      D testInstance = null;
>       try {
>           testClass = Class.forName("C");
>           Constructor constructor = testClass.getConstructor(new Class[] {
> String.class });
>           testInstance = (D)constructor.newInstance(new Object[] {
> "something" });
>       } catch (Exception e) {
>         log("I would have thought here");
>       }
>   }
> }
> 
> The error I am getting between "before error" and "after error" is
> "java.lang.NoClassDefFoundError: junit/framework/TestCase".
> 
> ... so yes, class D (which depends on JUnit) is explicitely mentionned in
> class B. So, what you said in your first email is that it is normal
> (although yo have to agree it is very hard to debug and track and means I'll
> have to put a try catch around "B myB = new B()" in class A, which looks at
> bit surnatural ... :-) (I'll need to put a good comment to explain it at
> least!).
> 
> ... then in your second email you say it is working fine with the "I would
> have thought here" message printed ... so that's why I'm resending these
> additional details.
> 
> Classes A, B, and D are in a jar under WEB-INF/lib
> Class C is in WEB-INF/classes
> 
> Any idea. If not, I'll try to make the most simple example that reproduces
> what I have and I'll send it over.
> Thanks a lot.
> -Vincent
> 
> ----- Original Message -----
> From: "Craig R. McClanahan" <[EMAIL PROTECTED]>
> To: <[EMAIL PROTECTED]>
> Cc: "Vincent Massol" <[EMAIL PROTECTED]>
> Sent: Thursday, July 26, 2001 10:08 PM
> Subject: Re: WebappClassLoader question
> 
> 
> 
> 
> On Thu, 26 Jul 2001, Craig R. McClanahan wrote:
> 
> > Hmm, I just tried a case like what you have below.  As long as B does not
> > explicitly reference class C, then it works.  In other words, in my
> > example where you've got the "// call the method by reflection" comment, I
> > added
> >
> >   Object c = myClass.newInstance();
> >
> > and got the class not found exception at "I would have thought here".  On
> > the other hand, if I changed the above line to:
> >
> >   C c = (C) myClass.newInstance();
> >
> > (and compiled with class C on the compiler classpath, but not in the
> > webapp), then I get the error in between "before error" and "after error".
> >
> > This makes sense, because the latter statement makes B explicitly
> > dependent on C, where the former doesn't.
> >
> > I'll mess around some more, playing with JAR-ing up some but not all the
> > classes involved.
> >
> 
> One more follow-up ... this works correctly for me with A and B in a jar
> file under /WEB-INF/lib, and C unpacked under /WEB-INF/classes as well.
> 
> > Craig
> >
> 
> Craig
> 
> 
> >
> > On Thu, 26 Jul 2001, Vincent Massol wrote:
> >
> > > Thanks Craig,
> > >
> > > However I am still not sure this mechanism explains the problem I had.
> It is
> > > not easy to describe in word so I'll write it in java code instead.
> > >
> > > A.java
> > > ----
> > > public class A implements HttpServlet
> > > {
> > >   public void doGet()
> > >   {
> > >     log("before error");
> > >     B myB = new B();
> > >     log("after error");
> > >   }
> > > }
> > > ----
> > >
> > > B.java
> > > ----
> > > public class B
> > > {
> > >   public B()
> > >   {
> > >   }
> > >   public doSomething()
> > >   {
> > >     try {
> > >       Class myClass = Class.forName("C");
> > >       // call the method by reflection ...
> > >     } catch (Exception e) {
> > >       log("I would have thought here");
> > >     }
> > >   }
> > > }
> > > ----
> > >
> > > C.java
> > > ----
> > > import junit.framework.*;
> > >
> > > public class C extends TestCase
> > > {
> > > ...
> > > }
> > > ----
> > >
> > > Now all of this is packaged in a war, classes A and B and in a jar put
> in
> > > WEB-INF/lib and class C is put in WEB-INF/classes. The junit jar is
> *not*
> > > put in WEB-INF/lib.
> > >
> > > Calling the servlet A result in an error occurring between the logs
> "before
> > > error" and "after error" and the log "I would have thought here" is
> never
> > > printed because the error happens _before_ ... This is what I don't
> > > understand.
> > >
> > > Any idea ?
> > > Thanks a lot
> > > -Vincent
> > >
> > > ----- Original Message -----
> > > From: "Craig R. McClanahan" <[EMAIL PROTECTED]>
> > > To: <[EMAIL PROTECTED]>; "Vincent Massol"
> <[EMAIL PROTECTED]>
> > > Sent: Thursday, July 26, 2001 6:18 PM
> > > Subject: Re: WebappClassLoader question
> > >
> > >
> > > On Thu, 26 Jul 2001, Vincent Massol wrote:
> > >
> > > > Thanks Alex,
> > > >
> > > > I don't think the standard classloader mechanism is involved here. I
> > > believe
> > > > it is a 'feature' of Tomcat and more specifically of it's
> > > WebappClassLoader.
> > > > When you do standard java code and you have the following situation :
> > > >
> > > > 1st -> 2nd --- (using reflection) --> 3rd (not in classpath)
> > > >
> > > > then the error always happen in the 2nd class and you can put the code
> > > > between a try catch block and you'll be able to catch the
> > > > ClassNotFoundException. However what happens here is that the error
> > > happens
> > > > when calling a method on the 1st class that instanciates the 2nd class
> !
> > > > This is what I don't understand.
> > > >
> > > > Am I dreaming or is this a behaviour of Tomcat 4 ?
> > > >
> > >
> > > The web app class loader in Tomcat 4 is based on
> java.net.URLClassLoader,
> > > and has the same basic class loading behavior.  In particular, when a
> > > class A is loaded, all the classes that A directly references (i.e.
> listed
> > > in import statements, used as a variable declaration, and so on) are
> also
> > > loaded.  This is done recursively on the referenced classes, until the
> > > entire tree of references is resolved.  If the load for class A fails,
> you
> > > will get ClassNotFoundException.  However, if the load for one of the
> > > referenced classes fails, you will typically get NoClassDefError instead
> > > (and the class named in the error message may or may not be the one that
> > > is actually missing, which complicates debugging this problem
> tremendously
> > > :-).
> > >
> > > A good rule of thumb to avoid this kind of problem -- if you need to add
> > > classes to your CLASSPATH at compile time (when rebuilding the entire
> > > app), be sure all of those classes are visible to the web app at
> runtime.
> > >
> > > Using reflection, on the other hand, lets you defer loading of
> "dependent"
> > > classes until runtime, and you can deal with ClassNotFoundException
> errors
> > > that you might run into.  Note however that, even if you load a class
> > > dynamically, *that* class still contains direct references to other
> > > classes that must all be resolved in the manner described above.
> > >
> > >
> > > > Thanks
> > > > -Vincent
> > > >
> > >
> > > Craig McClanahan
> > >
> > > PS:  One place where the Tomcat 4 class loader *does* vary from the
> usual
> > > class loader behavior is in the order of places it looks to load a
> > > class.  The usual pattern in Java2 is to delegate to the parent class
> > > loader first, then look locally.  Tomcat 4 does the opposite -- it
> checks
> > > in /WEB-INF/classes and /WEB-INF/lib of your web application *before*
> > > looking up the parent class loader chain.  This means that, if you have
> a
> > > class in the $CATALINA_HOME/lib directory (shared across web apps), and
> a
> > > version of that same class in your web app, the version in your webapp
> > > wins.
> > >
> > >
> > > > ----- Original Message -----
> > > > From: "Alex Fernández" <[EMAIL PROTECTED]>
> > > > To: <[EMAIL PROTECTED]>
> > > > Sent: Wednesday, July 25, 2001 3:44 PM
> > > > Subject: Re: WebappClassLoader question
> > > >
> > > >
> > > > > Hi Vincent!
> > > > >
> > > > > I've run into the same situation a couple of times, when one class
> uses
> > > > > a second class, and this second class uses a third one that is not
> > > > > present.
> > > > >
> > > > > 1st -> 2nd -> 3rd (missing)
> > > > >
> > > > > One would think that instantiating the 2nd should give an error, but
> > > > > that loading the 2nd and/or instantiating the 1st should be ok. In
> fact,
> > > > > all of the behaviors raise exceptions.
> > > > >
> > > > > The following paragraph in ClassLoader javadoc might be of help:
> > > > >
> > > > > "The methods and constructors of objects created by a class loader
> may
> > > > > reference other classes. To determine the class(es) referred to, the
> > > > > Java virtual machine calls the loadClass method of the class loader
> that
> > > > > originally created the class."
> > > > >
> > > > > Or, to find out what the JVM is doing, the spec is here:
> > > > > http://java.sun.com/docs/books/vmspec/
> > > > >
> > > > > Hope it helps.
> > > > >
> > > > > Un saludo,
> > > > >
> > > > > Alex.
> > > > >
> > > > > > Vincent Massol wrote:
> > > > > >
> > > > > > Hi,
> > > > > >
> > > > > > Here is the situation :
> > > > > >
> > > > > > * I have a class that makes use of JUnit (by extending the JUnit
> > > > > > TestCase class). Let's call it ServletTestCase
> > > > > > * I have a second class that is used to call a method in
> > > > > > ServletTestCase, let's call it MyProxyClass
> > > > > > * I have a third class (a servlet) that does _not_ make use of
> JUnit.
> > > > > > Let's call it ServletTestRedirector. This class actually
> instanciate
> > > > > > MyProxyClass and calls one of its method.
> > > > > > * I package these classes in a war file and I _don't_ include
> > > > > > junit.jar in this war file
> > > > > >
> > > > > > When I access the servlet, I get a ClassNotFoundException on a
> JUnit
> > > > > > class. So far it is normal ...
> > > > > > When I debugged it, I have actually found that the error was
> happening
> > > > > > when ServletTestRedirector was instancianting MyProxyClass (which
> does
> > > > > > _not_ make use of JUnit) and before it was calling its method.
> > > > > >
> > > > > > Here is the stack trace I got :
> > > > > >
> > > > > > java.lang.NoClassDefFoundError: junit/framework/TestCase
> > > > > >  at java.lang.ClassLoader.defineClass0(Native Method)
> > > > > >  at java.lang.ClassLoader.defineClass(ClassLoader.java:486)
> > > > > >  at
> > > > > >
> > > java.security.SecureClassLoader.defineClass(SecureClassLoader.java:111)
> > > > > >  at
> > > > > >
> > > >
> > >
> org.apache.catalina.loader.WebappClassLoader.findClassInternal(WebappClassLo
> > > > ader.java:1475)
> > > > > >  at
> > > > > >
> > > >
> > >
> org.apache.catalina.loader.WebappClassLoader.findClass(WebappClassLoader.jav
> > > > a:836)
> > > > > >  at
> > > > > >
> > > >
> > >
> org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.jav
> > > > a:1215)
> > > > > >  at
> > > > > >
> > > >
> > >
> org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.jav
> > > > a:1098)
> > > > > >  at java.lang.ClassLoader.loadClassInternal(ClassLoader.java:313)
> > > > > >  at
> > > > > >
> > > >
> > >
> org.apache.commons.cactus.server.ServletTestRedirector.doPost(ServletTestRed
> > > > irector.java:143)
> > > > > > Here is what I imagined is happening (tell me if this correct or
> wrong
> > > > > > !) :
> > > > > >
> > > > > > As MyProxyClass is within the war file, the WebappClassLoader gets
> > > > > > called to load it. The WebappClassLoader, in trying to find out
> the
> > > > > > correct class, actually loads some other class in memory, and thus
> the
> > > > > > ServletTestCase, which fails to load because the junit jar is not
> in
> > > > > > the classpath.
> > > > > >
> > > > > > Is that correct ?
> > > > > > Don't you find it strange that the error about the missing class
> is
> > > > > > reported when calling a class that has nothing to do with the
> problem
> > > > > > ? It gets very hard to catch errors ...
> > > > > >
> > > > > > For example, in MyProxyClass, the code that calls the
> ServletTestCase
> > > > > > method is as follows :
> > > > > >
> > > > > >         ServletTestCase testInstance = null;
> > > > > >         try {
> > > > > >             testClass = Class.forName(theClassName);
> > > > > >             Constructor constructor = testClass.getConstructor(new
> > > > > > Class[] { String.class });
> > > > > >             testInstance =
> > > > > > (ServletTestCase)constructor.newInstance(new Object[] {
> theMethod });
> > > > > >         } catch (Exception e) {
> > > > > >             logger.debug("Error instanciating class [" +
> theClassName
> > > > > > + "]", e);
> > > > > >             e.printStackTrace();
> > > > > >             throw new ServletException("Error instanciating class
> [" +
> > > > > > theClassName + "]", e);
> > > > > >         }
> > > > > > And there is never any exception caught here .... because the
> error
> > > > > > happens earlier in the call stack, when the ServletTestRedirector
> > > > > > instanciates MyProxyClass ...
> > > > > >
> > > > > > ... or am I missing something ? :)
> > > > > >
> > > > > > Thanks
> > > > > > -Vincent Massol
> > > > > >
> > > > >
> > > >
> > > >
> > >
> > >
> > >
> > >
> >
> >
> 
> 
> 
> 


Reply via email to