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.
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
> > > >
> > >
> >
> >
>
>
>
>