实在抵不住张老师的诱惑,又跳坑了

题目在这里 。真是惭愧啊。前天才给朋友说张老师的题目太长,我没有没有本事一目十行立马解答,还是不玩儿了。而且明天就要做演示了,后天就要交作业了,领导还闹着要我陪她溜冰。但后来忍不住看了张老师的帖子,不能自己地想知道答案啊。心里那个痒啊。偏偏各位老大愤怒声讨张老师,就是不给我们小菜鸟们一个爽快的答案。我那个闷骚啊,难以言表。终于对不起朋友,对不起公司,对不起教授,对不起领导,调出了我心爱的svn,来了一把svn -co http://svn.apache.org/repos/asf/ant/core/tags/ANT_165(因为我用ANT 1.6.5)。靠,后来发现这一步纯属多事。嗯,没有搞出最后答案,但我已经没有兴趣了。目前的发现就算抛砖引玉吧:

张老师说:从第一行打印的内容上可以看到: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
                                                                              
到这个时候我可以肯定三件事了:

  1. sun.misc.Launcher$AppClassLoader是被调用了的。
  2. Classpath是被改动了的。AppClassLoader只能看到[file:/D:/tools/ant/lib/ant-launcher.jar]。也就是说,ANT把系统CLASSPATH里的值用ANT命令行里规定的-classpath里的值替换了。
  3. 前面讨论的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熟悉而已。就像我还对我们部门的代码熟悉呢,不说明什么。但我还是做得很高兴。典型的菜鸟特征啊。

展开阅读全文

没有更多推荐了,返回首页