问题概述:每个ClassLoader本身只能分别加载特定位置和目录中的类,但是,ClassLoader被设计成了一种委托模式,使得某一个ClassLoader可以委托它的父级类装载器去加载类,从而让应用程序可以借助某一个子级的ClassLoader去多个位置和目录中进行类的加载。这就好比“儿子”除了可以花自己的钱,他还可以花“父亲”的钱,“父亲”又可以花“父亲的父亲”的钱,所以,最终能通过“儿子”花出去的钱包括他历代前辈的钱。类装载器一级级委托到BootStrap类加载器,当BootStrap无法加载当前所要加载的类时,然后才一级级回退到子孙类装载器去进行真正的加载。当回退到最初的类装载器时,如果它自己也不能完成类的装载,那就应报告ClassNotFoundException异常。
现在的问题是,我编写了一个类装载器去加载特定目录中的类,使用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。

转载于:https://www.cnblogs.com/javaEEspring/archive/2006/08/27/2523025.html

向真正Java高手请教ant构建工具的类装载器问题相关推荐

  1. Ant构建工具知识概括

    Ant构建工具知识概括题 构建工具对比 Ant简介 构建工具对比 Maven和Ant对比: 相同点:都是项目构建管理工具. 不同点有: ①Maven约定了目录结构,而Ant没有. ②Maven是申明式 ...

  2. Java命令行程序构建工具airlift使用

    package com.ilucky.airlift;import java.util.Arrays;import io.airlift.airline.Cli; import io.airlift. ...

  3. Java技术:项目构建工具Maven最佳替代者gradle介绍

    Maven作为一款非常流行的项目构建工具.基本上是每个Java程序员必备的工具,当然Maven有一些地方不足之处: 1. Maven的配置文件是XML格式的,如果你的项目工程依赖的包很多,那么XML文 ...

  4. Java命令行程序构建工具airlift使用之分组(group)

    闲话少说,直接上代码: package com.ilucky.druid.airlift.test2;import java.util.Arrays;import io.airlift.airline ...

  5. java+构建+工具+Ant+Maven+Gradle

    java+构建+工具+Ant+Maven+Gradle Ant+Maven+Gradle+............ 目前: Ant已经销声匿迹.Maven也没落了,而Gradle的发展则如日中天. M ...

  6. Java学习之常用的Java构建工具

    常用的Java构建工具介绍: Apache Maven – 主要用于 Java 项目的构建自动化工具. Hudson – 用 Ja​​va 编写的持续集成 (CI) 工具. Jenkins – 一个用 ...

  7. 构建工具Gradle

    1.Summary   从Android团队开始宣布放弃Eclipse转投Android Studio时,构建工具Gradle进入了Android开发者的视野.而随着热修复.插件化.编译时注解的流行, ...

  8. C语言(Head First C)-5_3:使用多个源文件:make自动化构建工具

    该系列文章系个人读书笔记及总结性内容,任何组织和个人不得转载进行商业活动! 5_3 使用多个源文件: 保留目标代码.链接目标文件,用make工具自动化构建 创建大程序: 我们已经学到了把程序分解成独立 ...

  9. 【Maven从入门到精通】 01-自动化构建工具:Maven

    笔记来源:Maven零基础入门教程(一套轻松搞定maven工具) 文章目录 自动化构建工具:Maven 1.Maven 到底是啥? 2.什么是构建? 3.构建过程中的各个环节 4.自动化构建 5.安装 ...

  10. Java中的常用开发工具详解

    转载:https://blog.csdn.net/han0373/article/details/79180553 Java开发人员的常用工具 java常用的开发工具.都说工欲善其事必先利其器,要想学 ...

最新文章

  1. jtoken判断是否包含键_c#-确定JToken是否为叶子
  2. python的上下文管理
  3. 高性能流媒体服务器EasyDSS前端重构(二) webpack + vue + AdminLTE 多页面提取共用文件, 优化编译时间...
  4. FormsAuthentication 和 Session 超时时间不一的问题
  5. 【背包】小明逛超市(jzoj 2148)
  6. set注意点map遍历
  7. php异步请求$.post,如何用PHP实现异步请求、忽略返回值
  8. MSDN-MDX#001 - 多维表达式 (MDX) 参考
  9. “The server requested authentication method unknown to the client.”的解决方案
  10. 微软2016 9月笔试
  11. 蚂蚁员工持股平台管理权变更 马云持股降至34%
  12. mysql ken len_MySQL EXPLAIN
  13. Python---HTML表单
  14. 电脑记事本在哪个文件夹
  15. 设计模式——工厂模式 1
  16. 关于cookie的详细讲解
  17. japanhr日语小工具 日文汉字转平假名-japankana
  18. 南邮 OJ 2001 水獭看动漫
  19. 你还在抱怨职场的不公吗?
  20. 矩阵寻找目标值的技巧

热门文章

  1. dubbo kryo序列化_Java后端精选技术:序列化框架的选型和比对
  2. mmap为什么比read快
  3. Snmp4j编程简介之二:PDU
  4. linux多CPU进程负载均衡解析
  5. Linux打印全部的内容,linux提取指定列字符并打印所有内容(awk)
  6. 和foreach的区别和应用场景_介绍下Set、Map、WeakSet 和 WeakMap 的区别?
  7. 图的深度优先遍历和广度优先遍历(附例题)
  8. Output path is shared between the same module error
  9. scala 入门初探
  10. 数组中只出现一次的数