天天看點

黑馬程式員-java類加載器總結

---------------------- ASP.Net+Unity開發、.Net教育訓練、期待與您交流! ----------------------

一、類加載器概述

1、類加載器是一種特殊的元件,負責在運作時查找和裝入類檔案。

2、java虛拟機中可以安裝多個類加載器,系統預設三個主要的類加載器,每個類加載器負責加載特定位置的類:BootStrap、ExtClassLoader、AppClassLoader。

3、類加載器也是java類,因為其他是java類的類加載器本身也要被類加載器加載,顯然必須要第一個類加載器不是java類,這正是BootStrap。

4、java虛拟機中的所有類加載器采用具有父子關系的樹形結構進行組織。在執行個體化每個類加載器對象時,需要為其指定一個父級類加載器對象或者預設采用系統類加載器為其父級類加載器。

例1:

package com.cn.itcast;

public class ClassLoaderTest {

	public static void main(String[] args) {
		//放置在不同位置的類由不同的類加載器加載
		System.out.println(
				ClassLoaderTest.class.getClassLoader().getClass().getName());
		//System.out.println(System.class.getClassLoader().getClass().getName());//錯誤,空指針異常
		System.out.println(System.class.getClassLoader());
		
		//類加載器采用具有父子關系的樹形結構
		ClassLoader loader=ClassLoaderTest.class.getClassLoader();
		while(loader!=null){
			System.out.println(loader.getClass().getName());
			loader=loader.getParent();
		}
		System.out.println(loader);
	}
}
           

輸出結果:

sun.misc.Launcher$AppClassLoader
null
sun.misc.Launcher$AppClassLoader
sun.misc.Launcher$ExtClassLoader
null
           

二、類加載器之間的父子關系與管轄範圍

黑馬程式員-java類加載器總結

由例1可知,ClassLoaderTest類是由AppClassLoader類加載器加載的。現在,我們利用MyEclipse的打包工具,将ClassLoaderTest輸出成jre/lib/ext目錄下的itcast.jar包,此時再次運作ClassLoaderTest,輸出結果:

sun.misc.Launcher$ExtClassLoader
null
sun.misc.Launcher$ExtClassLoader
null
           

此時的環境狀态是,classpath的目錄中有ClassLoaderTest.class,而jre/lib/ext/itcast.jar包中也有ClassLoaderTest.class。輸出結果表明,由ExtClassLoader加載了該類。這時候,我們需要繼續了解類加載的具體過程與原理。

三、類加載器的委托機制

1、當java虛拟機要加載一個類時,到底派出哪個類加載器去加載呢?

    1)首先,目前線程的類加載器去加載線程中的第一個類。

    2)其次,如果類A中引用了類B,java虛拟機将使用加載類A的類加載器來加載類B。

    3)另外,可以自定義類加載器并調用loadClass()方法來加載某個類。

2、每個類加載器加載類時,又先委托給其上級類加載器

每個類加載器本身隻能分别加載特定位置和目錄中的類,但它們可以委托給其他的類加載器去加載類,這就是類加載器的委托機制。

類加載器一級級地委托到BootStrap類加載器,當BootStrap無法加載目前所要加載的類時,回退到它的子類加載器。以此類推,就這樣一級級地回退到子孫類加載器。當回退到最初的類加載器時,如果它自己也加載不了,則抛出ClassNotFoundException。注意:發起者類加載器不會再委托給它的子級類加載器。

四、自定義類加載器

1、自定義的類加載器必須繼承ClassLoader類。

2、通過重寫ClassLoader類中的幾個重要方法來實作自定義,如:

    1)findClass(String name):根據二進制類檔案名來查找類

    2)loadClass(String name,boolean resolve):該方法為ClassLoader的入口點,根據指定的二進制名稱來加載類

一般來說,推薦重寫第一個方法。

JDK1.7中提供了自定義類加載器的範例:

class NetworkClassLoader extends ClassLoader {   //繼承ClassLoader類
         String host;
         int port;

         public Class findClass(String name) { //重寫findClass方法
             byte[] b = loadClassData(name);
             return defineClass(name, b, 0, b.length);
         }

         private byte[] loadClassData(String name) {  //這裡自定義了loadClassData方法
             // load the class data from the connection
              . . .
         }
     }
           

例2:自定義類加載器

思路:

1、編寫一個對檔案内容進行簡單加密的程式。

2、編寫一個自己的類加載器,可實作對加密過的類進行裝載和解密。

3、編寫一個程式調用類加載器加載類。注意,在源程式中不能使用該類名定義引用變量,因為編譯器無法識别之歌類。

①首選,讓系統類加載器去加載未被加密的類

/*
 * 被加密、解密及被自定義類加載器加載的類
 */
package Com.cn.ItCast;

import java.util.Date;

public class ClassLoaderAttachment extends Date {
	public String toString(){
		return "hello,itcast";
	}
}
           
/*
*測試類,調用類加載器去加載類
*/
package Com.cn.ItCast;

public class ClassLoaderTest {

	public static void main(String[] args) {	
		//首先讓類加載器加載未被加密的ClassLoaderAttachment類
		System.out.println(new ClassLoaderAttachment().toString());	
	}
}
           

輸出結果:

hello,itcast
           

②讓系統類加載器去加載被加密了的類

首先,對被加密檔案進行加密:

/*
 * 自定義類加載器。同時也負責對類檔案進行加密、解密
 */
package Com.cn.ItCast;

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;

public class MyClassLoader extends ClassLoader{
	String classDir;
	
	public MyClassLoader(){}
	public MyClassLoader(String classDir){
		this.classDir=classDir;
	}
	
	public static void main(String[] args) throws Exception{
		String srcPath=args[0]; //被加密類Class檔案的位址
		String destDir=args[1]; //加密後新生成的類檔案放置的目錄
		FileInputStream fis=new FileInputStream(srcPath);
		String destFileName=srcPath.substring(srcPath.lastIndexOf('\\')+1);//擷取被加密類的名字
		String destPath=destDir+"\\"+destFileName;//獲得加密後新生成的類檔案的位址
		FileOutputStream fos=new FileOutputStream(destPath);
		cypher(fis,fos);
		fis.close();
		fos.close();
	}
	//加密程式,采用異或方式
	private static void cypher(InputStream is,OutputStream os)throws Exception {
		int b=-1;
		while((b=is.read())!=-1){
			os.write(b^0xff);
		}
	}
	//重寫findClass方法
	@Override
	@SuppressWarnings("deprecation")
	protected Class<?> findClass(String name){
		String classFileName=classDir+"\\"+name+".class";//擷取加密後的類檔案位址
		try {
			FileInputStream fis=new FileInputStream(classFileName);
			ByteArrayOutputStream bos=new ByteArrayOutputStream();
			cypher(fis,bos);
			byte[] bytes=bos.toByteArray();
			return defineClass(bytes,0,bytes.length);
		}catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}
	
}
           

運作時傳遞參數:

黑馬程式員-java類加載器總結

其次,将加密後的類檔案替換classpath下未加密的同名類檔案,并再次運作ClassLoaderTest程式。

輸出結果:

ClassFormatError: Incompatible magic value 889275713 in class file Com/cn/ItCast/ClassLoaderAttachment
           

③讓自定義類加載器加載被加密的類檔案

/*
*測試類,調用類加載器去加載類
*/
package Com.cn.ItCast;

public class ClassLoaderTest {

	public static void main(String[] args) {	
		//讓自定義類加載器加載加密後的類檔案
		/*讓自定義類加載itcastlib/ClassLoaderAttachment.class,此時雖然有委托機制,
		但系統類加載器在bin目錄下未找到ClassLoaderAttachment.class檔案,故由自定義類加載器加載。
		如果在bin目錄下拷貝一份加密了的ClassLoaderAttachment.class,則會先被系統類加載器加載,進而出錯*/
		Class clazz=new MyClassLoader("itcastlib").loadClass("ClassLoaderAttachment");
		//不能用ClassLoaderAttachment名字建立執行個體對象,否則會讓編譯器自動調用系統類加載器,進而出錯
		Date d=(Date)clazz.newInstance();
		System.out.println(d);
	}
}
           

輸出結果:

五、用類加載器管理資源和配置檔案

1、擷取配置檔案的第一種方法:

InputStream ips = new FileInputStream("config.properties");
Properties props = new Properties();
props.load(ips);
ips.close();
           

注意:此方法要求必須用絕對路徑。但完整的路徑不是寫死寫死的,而是通過計算得出的,例如,調用getRealPah方法得到。

2、擷取配置檔案的第二中方法:

類加載器可以加載.class檔案,也可以加載普通檔案。

InputStream ips = ReflectTest.class.getClassLoader().getResourceAsStream("cn/itcast/config.properties");
           

通過類的.class對象獲得類加載器,然後用類加載器加載配置檔案。

注意:

配置檔案必須放在classpath目錄下,這樣類加載器才能找到檔案。

此方法中需要寫上包名,預設從classpath的根目錄下開始查找。

3、獲得配置檔案的第三種方法:

InputStream ips = ReflectTest.class.getResourceAsStream(“config.properties”);
           

Class對象本身也自帶一種加載資源檔案的方法,其實該方法内部調用了getClassLoader()方法。

注意:此方法中隻需要寫配置檔案的名字,不需要寫上包名。因為預設在類自己所在包下面找。

---------------------- ASP.Net+Unity開發、.Net教育訓練、期待與您交流! ----------------------