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