问题概述:每个classloader本身只能分别加载特定位置和目录中的类,但是,classloader被设计成了一种委托模式,使得某一个classloader可以委托它的父级类装载器去加载类,从而让应用程序可以借助某一个子级的classloader去多个位置和目录中进行类的加载。这就好比“儿子”除了可以花自己的钱,他还可以花“父亲”的钱,“父亲”又可以花“父亲的父亲”的钱,所以,最终能通过“儿子”花出去的钱包括他历代前辈的钱。类装载器一级级委托到bootstrap类加载器,当bootstrap无法加载当前所要加载的类时,然后才一级级回退到子孙类装载器去进行真正的加载。当回退到最初的类装载器时,如果它自己也不能完成类的装载,那就应报告classnotfoundexception异常。
现在的问题是,我编写了一个类装载器去加载特定目录中的类,使用java.exe测试这个类加载器时,测试结果完全正常,可以看到委托效果。而我使用ant工具去调用测试程序时,结果就有点问题了,我编写的类装载器似乎并没有委托其父级类加载器去加载类,而总是自己加载。由于本人才学疏浅,且实在没有精力去研究ant工具的源码,无法了解其类加载内部细节,现在特针对这个问题,向真正的java高手们请教。为了便于高手们快速了解我的问题所在,也便于一些中手们学习,我写出了详细的实验步骤,对于java新手,建议不要参与讨论了,免得我耽误了您宝贵时间。
1.源程序:mainclass.java
源程序:auxiliaryclass.java
2.源文件及build结果文件的目录结构
3.build.xml文件内容
4.进入project目录中运行ant,执行结果正常,如下:
5.修改build.xml文件,将最后名称为“run”的target(执行目标)修改成如下形式,即不设置其中的<classpath>子元素。
再次执行ant,将报告如下错误信息:
在执行ant的命令行窗口中设置classpath环境变量:
再次执行ant,执行结果正常,如下:
这个实验说明classpath环境变量对ant起了作用,并且在这种情况下,类的加载入器不再是 org.apache.tools.ant.loader.antclassloader2,而是java.net.urlclassloader。
6.修改build.xml文件,让ant生成的auxiliaryclass.class文件与mainclass文件位于不同的目录中,即结果目录如下:
修改后的build.xml文件内容如下:
因为第一个javac任务编译mainclass.java时,也会编译它引用的auxiliaryclass.java文件,所以,增加了delete任务删除掉生成的auxiliaryclass.class文件,然后再使用一个javac任务将auxiliaryclass.java编译到另外一个目录中。java任务中也增加了一个<arg>子元素,用于为java虚拟机传递参数,在这一步暂时不需要这个元素,在下一步的实验中将使用这个元素。
再次执行ant,将报告如下错误信息:
在执行ant的命令行窗口中设置classpath环境变量,将编译后生成的auxiliaryclass.class类所在的目录也加入进classpath环境变量中:
再次执行ant,执行结果正常,如下:
这个实验再次说明classpath环境变量对ant起了作用,将auxiliaryclass.class放在了classpath环境变量指定的另外一个目录中,也可以被ant工具的java任务装载。
7.修改mainclass.java文件,让其扩展成一个类装载器,专门负责从一个特定的目录中去加载类。mainclass同时也作为一个启动运行类,在其main方法中通过mainclass这个类装载器加载auxiliaryclass类。
源程序:mainclass.java
按如下方式执行ant命令,其中第一个参数为要加载的类,第二个参数为到哪个目录中去加载如类。
命令执行的结果为:
从第一行打印的内容上可以看到:auxiliaryclass类的类装载器为mainclass。这个结果与我的预期不同,因为按照类加载器的委托机制,mailclass类加载器将先委托其父级类装载器appclassloader加载auxiliaryclass,而auxiliaryclass所在的目录f:/project已经在第6步中加入到了classpath环境变量当中,appclassloader可以成功加载auxiliaryclass,所以,第一行打印出来的类装载器应该是appclassloader。为了印证我的想法,我改用java.exe来执行上面的程序:
执行结果如下:
可见,使用java.exe执行上面的程序时,auxiliaryclass类的类装载器确实是mailclass类加载器的父级类加载器appclassloader。这就是我这次问题的内容:为什么在ant环境下运行,mailclass类加载器没有委托其父级类装载器appclassloader加载auxiliaryclass类,而是自己加载了呢?就这个问题,本人向真正的java高手们请教?请您帮忙解释一下原因。
8.为了印证类加载器的委托机制,我们重新设置classpath环境变量,该环境变量不再包含auxiliaryclass所在的目录f:/project。
set classpath=f:/project/classes;
用cd命令进入f:/目录(避免当前目录的干扰),接着重复执行如下的java命令:
java cn.itcast.mainclass cn.itcast.auxiliaryclass project/cn/itcast
执行结果如下:
可见,这次auxiliaryclass类的类装载器是mailclass类,这是因为mailclass类装载器的父级类加载器appclassloader找不到auxiliaryclass类,加载过程又退回到mailclass类装载器,mailclass类装载器从project/cn/itcast目录中成功找到auxiliaryclass类,所以,这次打印出的类装载器为mailclass。
现在的问题是,我编写了一个类装载器去加载特定目录中的类,使用java.exe测试这个类加载器时,测试结果完全正常,可以看到委托效果。而我使用ant工具去调用测试程序时,结果就有点问题了,我编写的类装载器似乎并没有委托其父级类加载器去加载类,而总是自己加载。由于本人才学疏浅,且实在没有精力去研究ant工具的源码,无法了解其类加载内部细节,现在特针对这个问题,向真正的java高手们请教。为了便于高手们快速了解我的问题所在,也便于一些中手们学习,我写出了详细的实验步骤,对于java新手,建议不要参与讨论了,免得我耽误了您宝贵时间。
1.源程序:mainclass.java
| package cn.itcast; public class mainclass { public static void main(string [] args) { classloader loader = mainclass.class.getclassloader(); //打印出当前的类装载器,及该类装载器的各级父类装载器 while(loader != null) { system.out.println(loader.getclass().getname()); loader = loader.getparent(); } //加载auxiliaryclass类 system.out.println(auxiliaryclass.class.getname()); } } |
源程序:auxiliaryclass.java
| package cn.itcast; public class auxiliaryclass {} |
2.源文件及build结果文件的目录结构
| f:/project |__src | |__cn | |__itcast | |__mainclass.java | |__auxiliaryclass.java |__build.xml |__classes |__cn |__itcast |__mainclass.class |__auxiliaryclass.class |
3.build.xml文件内容
| <project name="antloader" default="run"> <property name="classes.dir" value="classes" /> <property name="src.dir" value="src" /> <target name="init"> <mkdir dir="${classes.dir}" /> </target> <target name="compile" depends="init"> <javac destdir="${classes.dir}" > <src path="${src.dir}" /> </javac> </target> <target name="run" depends="compile"> <java classname="cn.itcast.myclassloader"> <classpath> <pathelement location="${classes.dir}"/> </classpath> </java> </target> </project> |
4.进入project目录中运行ant,执行结果正常,如下:
| org.apache.tools.ant.loader.antclassloader2 sun.misc.launcher$appclassloader sun.misc.launcher$extclassloader cn.itcast.auxiliaryclass |
5.修改build.xml文件,将最后名称为“run”的target(执行目标)修改成如下形式,即不设置其中的<classpath>子元素。
| <target name="run" depends="compile"> <java classname="cn.itcast.myclassloader"> <!--classpath> <pathelement location="${classes.dir}"/> </classpath--> </java> </target> |
再次执行ant,将报告如下错误信息:
| could not find cn.itcast.mainclass. make sure you have it in your classpath at org.apache.tools.ant.taskdefs.executejava.execute(executejava.java:170) |
在执行ant的命令行窗口中设置classpath环境变量:
| set classpath=f:/project/classes; |
再次执行ant,执行结果正常,如下:
| java.net.urlclassloader sun.misc.launcher$appclassloader sun.misc.launcher$extclassloader cn.itcast.auxiliaryclass |
这个实验说明classpath环境变量对ant起了作用,并且在这种情况下,类的加载入器不再是 org.apache.tools.ant.loader.antclassloader2,而是java.net.urlclassloader。
6.修改build.xml文件,让ant生成的auxiliaryclass.class文件与mainclass文件位于不同的目录中,即结果目录如下:
| f:/project |__src | |__cn | |__itcast | |__mainclass.java | |__auxiliaryclass.java |__build.xml |__classes | |__cn | |__itcast | |__mainclass.class |__cn |__itcast |__auxiliaryclass.class |
修改后的build.xml文件内容如下:
| <project name="antloader" default="run"> <property name="classes.dir" value="classes" /> <property name="src.dir" value="src" /> <property name="mainclass" value="cn.itcast.mainclass" /> <target name="init"> <mkdir dir="${classes.dir}" /> </target> <target name="compile" depends="init"> <javac destdir="${classes.dir}" > <src path="${src.dir}" /> <include name="cn/itcast/mainclass.java" /> </javac> <delete file="${classes.dir}/cn/itcast/auxiliaryclass.class" /> <javac destdir="." > <src path="${src.dir}" /> <include name="cn/itcast/auxiliaryclass.java" /> </javac> </target> <target name="run" depends="clean,compile"> <java classname="${mainclass}"> <!--classpath> <pathelement location="${classes.dir}"/> </classpath--> <arg line="${arg0} ${arg1}" /> </java> </target> <target name="clean"> <delete dir="${classes.dir}" /> </target> </project> |
因为第一个javac任务编译mainclass.java时,也会编译它引用的auxiliaryclass.java文件,所以,增加了delete任务删除掉生成的auxiliaryclass.class文件,然后再使用一个javac任务将auxiliaryclass.java编译到另外一个目录中。java任务中也增加了一个<arg>子元素,用于为java虚拟机传递参数,在这一步暂时不需要这个元素,在下一步的实验中将使用这个元素。
再次执行ant,将报告如下错误信息:
| could not find cn.itcast.auxiliary. make sure you have it in your classpath at org.apache.tools.ant.taskdefs.executejava.execute(executejava.java:170) |
在执行ant的命令行窗口中设置classpath环境变量,将编译后生成的auxiliaryclass.class类所在的目录也加入进classpath环境变量中:
| set classpath=f:/project/classes;f:/project; |
再次执行ant,执行结果正常,如下:
| java.net.urlclassloader sun.misc.launcher$appclassloader sun.misc.launcher$extclassloader cn.itcast.auxiliaryclass |
这个实验再次说明classpath环境变量对ant起了作用,将auxiliaryclass.class放在了classpath环境变量指定的另外一个目录中,也可以被ant工具的java任务装载。
7.修改mainclass.java文件,让其扩展成一个类装载器,专门负责从一个特定的目录中去加载类。mainclass同时也作为一个启动运行类,在其main方法中通过mainclass这个类装载器加载auxiliaryclass类。
源程序:mainclass.java
| package cn.itcast; import java.io.*; public class mainclass extends classloader { private string path = null; public mainclass(string path) { //错误检查省略 this.path = path; } protected class findclass(string name) throws classnotfoundexception { try { file f = new file(path,name.substring(name.lastindexof('.')+1) + ".class"); fileinputstream fis = new fileinputstream(f); bytearrayoutputstream bos = new bytearrayoutputstream(); int b = 0; while((b=fis.read()) != -1) { bos.write(b); } byte [] buf = bos.tobytearray(); fis.close(); bos.close(); return defineclass(name,buf,0,buf.length); }catch(exception e) { throw new classnotfoundexception(name + "is not found!"); } } public static void main(string [] args) throws exception { class cls = new mainclass(args[1]).loadclass(args[0]); classloader loader = cls.getclassloader(); //打印出的动态加载的auxiliaryclass的类装载器,及该类装载器的各级父类装载器 while(loader != null) { system.out.println(loader.getclass().getname()); loader = loader.getparent(); } } } |
按如下方式执行ant命令,其中第一个参数为要加载的类,第二个参数为到哪个目录中去加载如类。
| ant -darg0=cn.itcast.auxiliaryclass -darg1=cn/itcast |
命令执行的结果为:
| cn.itcast.mainclass sun.misc.launcher$appclassloader sun.misc.launcher$extclassloader |
从第一行打印的内容上可以看到:auxiliaryclass类的类装载器为mainclass。这个结果与我的预期不同,因为按照类加载器的委托机制,mailclass类加载器将先委托其父级类装载器appclassloader加载auxiliaryclass,而auxiliaryclass所在的目录f:/project已经在第6步中加入到了classpath环境变量当中,appclassloader可以成功加载auxiliaryclass,所以,第一行打印出来的类装载器应该是appclassloader。为了印证我的想法,我改用java.exe来执行上面的程序:
| java cn.itcast.mainclass cn.itcast.auxiliaryclass cn/itcast |
执行结果如下:
| sun.misc.launcher$appclassloader sun.misc.launcher$extclassloader |
可见,使用java.exe执行上面的程序时,auxiliaryclass类的类装载器确实是mailclass类加载器的父级类加载器appclassloader。这就是我这次问题的内容:为什么在ant环境下运行,mailclass类加载器没有委托其父级类装载器appclassloader加载auxiliaryclass类,而是自己加载了呢?就这个问题,本人向真正的java高手们请教?请您帮忙解释一下原因。
8.为了印证类加载器的委托机制,我们重新设置classpath环境变量,该环境变量不再包含auxiliaryclass所在的目录f:/project。
set classpath=f:/project/classes;
用cd命令进入f:/目录(避免当前目录的干扰),接着重复执行如下的java命令:
java cn.itcast.mainclass cn.itcast.auxiliaryclass project/cn/itcast
执行结果如下:
| cn.itcast.mainclass sun.misc.launcher$appclassloader sun.misc.launcher$extclassloader |
可见,这次auxiliaryclass类的类装载器是mailclass类,这是因为mailclass类装载器的父级类加载器appclassloader找不到auxiliaryclass类,加载过程又退回到mailclass类装载器,mailclass类装载器从project/cn/itcast目录中成功找到auxiliaryclass类,所以,这次打印出的类装载器为mailclass。
闽公网安备 35060202000074号