程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> ant構建工具的類裝載器問題

ant構建工具的類裝載器問題

編輯:關於JAVA

問題概述:每個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。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved