张老师说: 从第一行打印的内容上可以看到:AuxiliaryClass类的类装载器为MainClass。这个结果与我的预期不同,因为按照类加载器的委托机制,MailClass类加载器将先委托其父级类装载器AppClassLoader加载AuxiliaryClass,而AuxiliaryClass所在的目录f:\project已经在第6步中加入到了Classpath环境变量当中,AppClassLoader可以成功加载AuxiliaryClass,所以,第一行打印出来的类装载器应该是AppClassLoader。
其实嗫,这个结果和张老师的预期是一样的。委托机制仍然在起作用。没法不起啊。源码说话:
01: protected synchronized Class loadClass (String name , boolean resolve ) throws ClassNotFoundException {
02: // First, check if the class has already been loaded
03: Class c = findLoadedClass (name ) ;
04: if (c = = null ) {
05: try {
06: if (parent ! = null ) {
07: c = parent . loadClass (name , false ) ;
08: } else {
09: c = findBootstrapClass0 (name ) ;
10: }
11: } catch (ClassNotFoundException e ) {
12: // If still not found, then invoke findClass in order
13: // to find the class.
14: c = findClass (name ) ;
15: }
16: }
17: if (resolve ) {
18: resolveClass (c ) ;
19: }
20: return c ;
21: }
注意第07行。只要被装载的类没有被装载过,parent.loadClass()就一定会被调用。 我们最后看到的是MainClass,那唯一的结论就是ClassNotFoundException被抛出。第11到第14行被执行。可是我们在CLASSPATH里加入了到AuxiliaryClass的路径啊!张老大可是做了实验的。还好,福尔摩斯老大的话响起来:排除所有不可能的东东。剩下的看起来再不可能发生也是真相。不过呢,人见人耐的高爷爷也说过:小心了,俺只证明了这个算法正确,还没有运行程序来验证。所以俺决定继续下去,找点证据。于是我在MainClass里添加了下面这个方法:
23: protected synchronized Class loadClass(String className, boolean resolveClass) throws ClassNotFoundException {
24: // Ask the VM to look in its cache.
25: Class loadedClass = findLoadedClass(className);
26: // search in getParent() if not found
27: if (loadedClass == null) {
28: System.out.println("loaded class is null");
29: try {
30: if (getParent() == null) {
31: System.out.println("getParent() is null, class name is "+className);
32: System.out.println("using system class loader "+getSystemClassLoader());
33: //System.out.println("URLs: "+java.util.Arrays.asList(getSystemClassLoader().getURLs()));
34: loadedClass = getSystemClassLoader().loadClass(className);
35: System.out.println("now system class loader is: "+getSystemClassLoader());
36: System.out.println("now loaded class is: "+loadedClass.getName());
37: } else{
38: System.out.println("using parent "+getParent()+" to load "+className);
39: System.out.println("parent loader's urls: "+java.util.Arrays.asList(((java.net.URLClassLoader)getParent()).getURLs()));
40: loadedClass = getParent().loadClass(className);
41: System.out.println("getParent() is not null, getParent() loads class: "+loadedClass.getName());
42: }
43: } catch (ClassNotFoundException e) {
44: // don't do anything. Catching this exception is the normal protocol for
45: // getParent() classloaders telling use they couldn't find a class.
46: System.out.println("ClassNotFound by"+ getParent()+e);
47: e.printStackTrace();
48: }
49:
50: // not findLoadedClass or by getParent().loadClass, try locally
51: if (loadedClass == null) loadedClass = findClass(className);
52: }
53:
54: // resolve if required
55: if (resolveClass) resolveClass(loadedClass);
56: return loadedClass;
57: }
也就是说,我写了一个自己的loadClass(),和ClassLoader里的几乎一样。唯一的区别是我加了不少调试句子。再运行一下张老师的build.xml,除原来张老师提到的输出以外,得到如下输出:
[java] found loaded class: null
[java] loaded class is null
[java] using parent sun.misc.Launcher$AppClassLoader@65251c45 to load cn.it cast.AuxiliaryClass
[java] parent loader's urls: [file:/D:/tools/ant/lib/ant-launcher.jar]
[java] ClassNotFound bysun.misc.Launcher$AppClassLoader@65251c45java.lang.ClassNotFoundException: cn.itcast.AuxiliaryClass
[java] java.lang.ClassNotFoundException: cn.itcast.AuxiliaryClass
到这个时候我可以肯定三件事了:
- sun.misc.Launcher$AppClassLoader是被调用了的。
- Classpath是被改动了的。AppClassLoader只能看到[file:/D:/tools/ant/lib/ant-launcher.jar]。也就是说,ANT把系统CLASSPATH里的值用ANT命令行里规定的-classpath里的值替换了。
- 前面讨论的ClassNotFoundException的确被抛出。
嗯,这个显然是ANT的问题。如果在run这个target里调用java这个task时加上fork="true",就可以看到如下的输出。说明ANT新生成的JVM就没有CLASSPATH的问题,因为新生成的JVM直接调用java, 不通过ant launcher。如果在eclipse里用代码调用ANT的Main.main(),也可以看到张老师期望的输出。说明用了自定义的ClassLoader,可以绕开这个问题。
sun.misc.Launcher$AppClassLoader
sun.misc.Launcher$ExtClassLoader
那ANT为什么会改动CLASSPATH蹑?这个俺就不知道了。所以我在开头说我没有找到最后答案。毕竟张老师问的是ANT造成这个结果的机制。不过我们可以肯定这个错误没有发生在AntClassLoader和AntClassLoader2这两个类里(我也许错了)。也就是说,我很怀疑这个bug和ANT的classloader直接相关。
所谓google在手,一个顶九。于是我找到了这个bug report. 看来张老师的确发现了ANT的一个bug。看来这个bug已经在ant 1.7里被修复。造成bug的具体原因我还是不知道。不过这重要么?不重要么?ANT的一个bug而已,我想不出去了解这个bug的意义何在。我还有几百个bug要修补,几百个feature要添加,几百篇论文要读。去关心ANT上百错误中一个对我来说无异于自虐。呵呵,见谅见谅,其实都是菜鸟水平不够的托词而已。我的动力很小,我的借口很多,我。。。。去陪领导溜冰乐。。
其实这个问题的相关内容其实也不深入,无关系统设计,无关算法,无关具体应用。无非是某个编程错误而已。就算谁熟知ANT代码,也是对ANT熟悉而已。就像我还对我们部门的代码熟悉呢,不说明什么。但我还是做得很高兴。典型的菜鸟特征啊。