java基礎加強_類加載器


類加載器:負責加載類的工具

Java虛擬機中可以安裝多個類加載器,系統默認三個主要類加載器,每個類負責加載特定位置的類:
BootStrap(根加載器)
ExtClassLoader(擴展類加載器)
AppClassLoader(系統類加載器)
還有一種是用戶自定義加載器

獲取類文件所使用的加載器名稱
System.out.println(ClassLoaderTest.class.getClassLoader().getClass()
				.getName());

類加載器也是Java類,因爲java類的類加載器本身也要被類加載器加載,顯然第一個類加載器不是java類,這正是BootStrap(存在java虛擬機中)。

Java虛擬機中的所有類裝載器採用具有父子關係的樹形結構進行組織,在實例化每個類裝載器對象時,需要爲其指定一個父級類裝載器對象或者默認採用系統類裝載器爲其父級類加載。 


類加載器之間的父子關係和管轄範圍圖



總結:
當Java虛擬機要加載一個類時,到底派出哪個類加載器去加載呢?
首先當前線程的類加載器去加載線程中的第一個類。
如果類A中引用了類B,Java虛擬機將使用加載類A的類裝載器來加載類B。(類A繼承了類B,那麼他們的加載器是同一個加載器
還可以直接調用ClassLoader.loadClass()方法來指定某個類加載器去加載某個類。

類加載流程:
當加載一個類文件時,首先到AppClassLoader加載器中查找是否有此類文件,無論有沒有都到上級加載器(ExtClassLoader)中查找,無論有無都向上級加載器BootStrap中查找,有則加載此類,無則再依次向下查找,如果下面的所有加載器中沒有找到該類文件則報ClassNotFoundException異常

規則總結:
每次都從低級加載器開始向高級加載器查找,如果在最高級加載器中查找到就直接執行該類文件,沒找到就再依次向下級加載器查找,找到後就加載,否則就報ClassNotFoundExcepiton異常。

當類文件在高級加載器和低級加載器中同時存在時:高級加載器總是優先於低級加載器加載

相關面試題:能不能自己寫個類叫java.lang.System
可以寫,但是這個類永遠不會被加載,因爲在加載器BootStrap中存在此類,所以加載器優先加載BootStrap負責範圍中的System的類

自定義類加載器

類加載器中的loadClass方法內部實現了父類委託機制,因此我們沒有必要自己覆蓋loadClass,否則需要自己去實 現父類委託機制。我們只需要覆蓋findClass方法。loadClass方法中調用了findClass方法,使用的是模板設計模 式。我們得到了Class文件後,就可以通過defineClass方法將二進制數據轉換成字節碼。這就是自定義類加載器的 編寫原理。

編寫一個加密文件的程序

要加密的java文件代碼
package cn.itheima.ClassLoader;

public class ClassLoaderAttachment
{
	public String toString()
	{
		return "Hello,Class loader!";
	}
}

加密程序:其實就是講一個文件寫入到指定目錄,在寫入的時候加入一些規則即可。

package cn.itheima.ClassLoader;

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


public class MyClassLoader extends ClassLoader
{

	/**
	 * @param args
	 * 文件加密其實就是在寫入文件的時候加上特意的規則,本程序是使用反碼來進行加密的
	 */
	public static void main(String[] args) throws Exception
	{
		//將class文件目錄和寫入目錄指定給主函數,分別用args[0]和args[1]存儲
		String srcPath = args[0];
		String destDir = args[1];
		//讀取指定目錄中的文件
		InputStream ips = new FileInputStream(srcPath);
		//獲取指定目錄文件的文件名
		String destFileName = srcPath.substring(srcPath.lastIndexOf("\\")+1);
		//定義存放加密後的文件目錄
		String destPath = destDir + "\\"+ destFileName;
		//寫入目標目錄
		OutputStream ops = new FileOutputStream(destPath);
		encrypt(ips,ops);
		ips.close();
		ops.close();
		
	}
	//加密其實就是將類文件讀取後,在寫入的時候進行非正常的寫入
	public static void encrypt(InputStream ips,OutputStream ops) throws Exception
	{
		//單個字符字節寫入,將每個字節進行加密
		int a = 0;
		while((a = ips.read())!= -1)
		{
			ops.write(a^0xff);//加密的一種方式,反碼加密
		}
	}
}

注意:
要加密某個文件,首先要讀取文件,在讀取文件的時候必須要傳入文件的目錄路徑,此程序中時直接用的主函數傳入的參數進行讀取的,
具體的步驟如下:
右鍵-->RunAs-->Run Configurations-->Arguments-->在Program Arguments中輸入要讀取的文件路徑,和寫入的文件目錄。注意:這兩個參數間用空格分開,絕對不可以使用","。
當然你也可以直接輸入他們各自的目標路徑,個人覺得這樣更省事。

加密後的類文件,如果按照正常的類進行加載的話,是不可能正常加載成功的,這時候我們就需要寫一個解密的類加載程序。

解密類加載程序

步驟:
1.加載器繼承ClassLoader
2.複寫findClass()方法
3.使用defineClass()方法

加密文件
package cn.itheima.ClassLoader;

import java.util.Date;

public class ClassLoaderAttachment extends Date
{
	public String toString()
	{
		return "Hello,Class Loader!";
	}
}
加密解密程序
package cn.itheima.ClassLoader;

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


public class MyClassLoader extends ClassLoader
{

	/**
	 * @param args
	 * 文件加密其實就是在寫入文件的時候加上特意的規則,本程序是使用反碼來進行加密的
	 */
	private String srcPath;
	public MyClassLoader(){}
	public MyClassLoader(String srcPath)
	{
		this.srcPath = srcPath;
	}
	@SuppressWarnings("deprecation")
	@Override
	//複寫findClass方法,自定義加載器
	protected Class<?> findClass(String name) throws ClassNotFoundException
	{
		//讀取加密後的文件,進行解密,得到原來的類文件
		String classFileName = srcPath + "\\"+name.substring(srcPath.lastIndexOf("\\")+1)+".class";
		try
		{
			InputStream ips = new FileInputStream(classFileName);
			ByteArrayOutputStream bos = new ByteArrayOutputStream();
			encrypt(ips,bos);//解密文件
			ips.close();
			byte[] bytes = bos.toByteArray();
			return defineClass(bytes,0,bytes.length);	
		} catch (Exception e)
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return super.findClass(name);
	}
	public static void main(String[] args) throws Exception
	{
		//將class文件目錄和寫入目錄指定給主函數,分別用args[0]和args[1]存儲
		String srcPath = args[0];
		String destDir = args[1];
		//讀取指定目錄中的文件
		InputStream ips = new FileInputStream(srcPath);
		//獲取指定目錄文件的文件名
		String destFileName = srcPath.substring(srcPath.lastIndexOf("\\")+1);
		//定義存放加密後的文件目錄
		String destPath = destDir + "\\"+ destFileName;
		//寫入目標目錄
		OutputStream ops = new FileOutputStream(destPath);
		encrypt(ips,ops);
		ips.close();
		ops.close();
		
	}
	//加密其實就是將類文件讀取後,在寫入的時候進行非正常的寫入
	public static void encrypt(InputStream ips,OutputStream ops) throws Exception
	{
		//單個字符字節寫入,將每個字節進行加密
		int a = 0;
		while((a = ips.read())!= -1)
		{
			ops.write(a^0xff);//加密的一種方式,反碼加密
		}
	}	
}

使用自定義加載器
package cn.itheima.ClassLoader;

import java.util.Date;

public class ClassLoaderTest2
{

	/**
	 * @param args
	 */
	public static void main(String[] args) throws Exception
	{
		// 加載文件獲得對應的class文件
		Class clazz = new MyClassLoader("DestFolder").loadClass("ClassLoaderAttachment"); 
		//創建類文件對象
		Date d = (Date)clazz.newInstance(); 
		//調用該對象的toString()方法
		System. out.println(d.toString());
	}

}

注意: 

1、之所以讓ClassLoaderAttachment類繼承Date是因爲,如果直接寫 
ClassLoaderAttachment d = (ClassLoaderAttachment)clazz.newInstance();這條語句,編譯的時候就會報錯,因爲此時的 
ClassLoaderAttachment在AppClassLoader加載進內存後就無法識別。所以需要通過藉助一個父類對象繞過編譯器。也就 
是:Date d1 = (Date)clazz.newInstance();。 

2、如果想讓父類加載器AppClassLoader加載ClassLoaderAttachment類,則需要執行下面的語句: 
Class clazz = new MyClassLoader("ClassLoaderLib").loadClass("com.itheima.day2.ClassLoaderAttachment"); 
但是父類加載可能會出錯。 

3、在創建ClassLoaderAttachment類時,在父類選項中,要選擇 Date類,否則之後再繼承Date類會報錯。

4、在測試類加載器是否是自定義類加載器時,一定要先把ClassPath路徑下的ClassLoaderAttachment.class文件刪除,否則類加載器會優先使用其父類加載器加載(類加載器的委託機制),而不是自定義的加載器。

類加載器的一個高級問題的實驗分析(我覺得這個問題是涉及到Web方面的類加載的問題處理和操作過程)
過程描述
編寫一個能打印出自己的類加載器名稱和當前類加載器的父子結構關係鏈的MyServlet,正常發佈後,看到打印結果爲WebAppClassloader。
把MyServlet.class文件打jar包,放到ext目錄中,重啓tomcat,發現找不到HttpServlet的錯誤。
把servlet.jar也放到ext目錄中,問題解決了,打印的結果是ExtclassLoader 。
原理:




步驟:
1.新建一個Web工程:new-->Web Project-->命名工程文件名itheimaweb,這裏要注意Java EE Version版本,默認的是6.0的。點擊finish完成創建;

2.在該工程文件中創建severlet文件:右鍵src-->new-->servelet-->命名包名,cn.itheima.itheimaweb.web.Servelets;命名文件名,MyServelet-->勾選doGet方法-->Next-->單擊finish完成創建;

3.在MyServlet文件中錄入代碼
package cn.itheima.itheimaweb.web.servlets;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class MyServlet extends HttpServlet
{

	/**
	 * The doGet method of the servlet. <br>
	 *
	 * This method is called when a form has its tag value method equals to get.
	 * 
	 * @param request the request send by the client to the server
	 * @param response the response send by the server to the client
	 * @throws ServletException if an error occurred
	 * @throws IOException if an error occurred
	 */
	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException
	{

		response.setContentType("text/html");
		PrintWriter out = response.getWriter();
		ClassLoader loader =this.getClass().getClassLoader(); 
		while(loader!=null)
		{ 
			out.println(loader.getClass().getName()+"<br/>"); 
			loader = loader.getParent(); 
		} 
		out.close(); 	
	}
}

4.右鍵MyServlet.java的源文件-->Run As-->MyEclipse Server Application-->在跳出的對話框選擇Tomcat服務器-->點擊OK
運行結果:
org.apache.catalina.loader.WebappClassLoader
org.apache.catalina.loader.StandardClassLoader
org.apache.catalina.loader.StandardClassLoader
sun.misc.Launcher$AppClassLoader
sun.misc.Launcher$ExtClassLoader

可以看到MyServlet是由WebappClassLoader類加載器加載的

5.將MyServlet文件進行導包,目標目錄爲D:\ProgramFiles\Java\jdk1.7.0_05\jre\lib\ext中,這時候再運行該服務器時,將會出現找不到HeepServlet問題
java.lang.ClassNotFoundException: javax.servlet.http.HttpServlet

6.將Tomcat的lib目錄中的servlet-api.jar包放入ext目錄中,即可正常運行,但是此時的類加載器是ExtClassLoader

總結:
造成這個問題的原因:
因爲MyServlet繼承了HttpServlet,所以加載器在加載MyServlet的同時也會加載HttpServlet,且他們用的是同一個加載器。當MyServlet被導入ext目錄中是,根據委託機制的原理,加載器變爲ExtClassLoader,當加載HttpServlet時,再ext目錄下將找不到該jar包,於是就出現了以上問題。









發佈了57 篇原創文章 · 獲贊 4 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章