創建並使用自定義的類加載器
JVM中除了根加載器之外的所有類加載器都是ClassLoader子類的實例,開發者可以通過擴展ClassLoader的子類,並重寫該ClassLoader所包含的方法來實現自定義的類加載器
ClassLoader類有如下三個關鍵方法:
loaderClass(String name,Boolean resolve):該方法爲ClassLoader的入口點,根據指定的二進制名稱來加載類,系統就是調用ClassLoader的該方法來獲取指定類對應的Class對象
findClass(String name):根據二進制名稱來查找類
如果需要實現自定義的ClassLoader,可以通過重寫以上兩個方法來實現,推薦重寫findClass()方法,而不是重寫loadClass()方法,因爲loadClass()方法的執行步驟如下:
1. 用findLoadedClass(String )來檢查是否已經加載類,如果已經加載則直接返回。
2. 在父類加載器上調用loadClass方法,如果父類加載器爲null,則使用根類加載器來加載
3. 調用findClass(String)方法查找類
從上面方法可以看出,重寫findClass()方法可以避免覆蓋默認類加載器的父類委託,緩衝機制兩種策略,如果重寫loadClass()方法,則實現邏輯更爲複雜
ClassLoader類還有一個核心方法:Class defineClass(String name,byte[] b,int off,int len)該方法負責將指定類的字節碼文件(即Class文件)讀入字節數組:byte[]b,並把它轉化爲Class對象,該字節碼文件可以來源於文件,網絡等。
findSystemClass(String name)從本地文件系統中裝入文件,在本地文件中尋找類文件,如果存在,就用defineClass將原始字節轉化成Class對象,以將該文件轉化成類
static getSystemClassloader():這是一個靜態方法,用於返回系統類加載器
getParent():獲取該類加載器的父類加載器
resolveClass(Class<?> c):鏈接指定的類,類加載器可以使用此方法來鏈接類c.
findLoadedClass(String name):如果此Java虛擬機已經裝在了名爲name的類,則直接返回該類對應的Class實例,否則,返回null,該方法是java類加載裏緩存機制的體現
自定義類加載器:
Public class CompileClassLoader extends ClassLoader
{
//讀取一個文件的內容
Private byte[] getBytes(String filename) throws IOException
{
File file = new File(filename);
Long len = file.length();
Byte[] raw = new byte[(int)len];
FileInputStream fin = new FileInuptStream(file);
//一次讀取class文件的全部二進制數據
Int r = fin.read(raw);
If(r!=len)
Throw new IOException(“無法讀取全部文件”+r+”!=”+len);
Fin.close();
Return raw;
}
//定義編譯指定java文件的方法
Private Boolean compile(String javaFile) throws IOException
{
System.out.println(“CompileClassLoader:正在編譯”+javaFile+”……..”);
//調用系統的javac命令
Process p = Runtime.getRuntime().exec(“javac”+javaFile);
Try{
//其它線程都等待這個線程完成
p.waitFor();
}catch(InterruptedException ie)
{
System.out.println(ie);
}
//獲取javac 的線程的退出值
Int ret =p.exitValue();
//返回編譯是否成功
Return ret==0;
}
//重寫Classloader的findCLass方法
Protected Class<?> findClass(String name)throws ClassNotFoundException
{
Class clazz = null;
//將包路徑中的.替換成斜線/
String fileStub = name.replace(“.”,”/”);
String javaFilename = fileStub+”.java”;
String classFilename = fileStub+”.class”;
File javaFile= new File(javaFilename);
File classFile = new File(classFilename);
//當指定Java源文件存在,且class文件不存在,或者Java源文件的修改時間比class文件//修改時間晚時,重新編譯
If(javaFile.exists()&&(!classFIle.exists())||javaFIle.lastModified()>classFile.lastModified())
{
Try{
//如果編譯失敗,或該Class文件不存在
If(!compile(javaFilename)||!classFile.exists())
{
Throw new ClassNotFoundException(“ClassNotFoundException:”+javaFilename);
}
}catch(IOException ex)
{
Ex.printStackTrace();
}
}
//如果class文件存在,系統負責將該文件轉化成class對象
If(classFile.exists())
{
Try{
//將class文件的二進制數據讀入數組
Byte[] raw= getBytes(classFilename);
//調用Classloader的defineClass方法將二進制數據轉換成class對象
Clazz = defineClass(name,raw,0.raw.length);
}catch(IOException ie)
{
Ie.printStackTrace();
}
}
//如果claszz爲null,表明加載失敗,則拋出異常
If(clazz==null){
Thow new ClassNotFoundException(name);
}
Return clazz;
}
//定義一個主方法
Public static void main(String [] args )throws Exception
{
//如果運行該程序時沒有參數,即沒有目標類
If(args.length<1){
System.out.println(“缺少運行的目標類,請按如下格式運行java源文件:”);
System.out.println(“java CompileClassLoader ClassName”);
}
//第一個參數是需要運行的類
String progClass = args[0];
//剩下的參數將作爲運行目標類時的參數,所以將這些參數複製到一個新數組中
String progargs[] = new String [args.leng-1];
Sytem.arraycopy(args,1,progargs,0,progArgs.length);
CompileClassLoader cl = new CompileClassLoader();
//加載需要運行的類
Class<?> clazz = cl.loadClass(progClass);
//獲取需要運行的類的主方法
Method main= clazz.getMethod(“main”,(new String[0]).getClass());
Object argsArray[] = {progargs};
Main.invoke(null,argsArray);
}
}
自定義加載器實現如下功能:
執行代碼前自動驗證數字簽名
根據用戶提供的密碼解密代碼,從而可以實現代碼混淆器來避免反編譯class文件
根據用戶需求來動態的加載類
根據應用需求把其他數據以字節碼的形式加載到應用中
URLClassLoader類
Java爲ClassLoader提供了一個URLClassLoader實現類,該類也是系統類加載器和擴展類加載器的父類(此處是父類,而不是父類加載器,這裏是類與類之間的繼承關係),URLClassLoader功能比較強大,既可以從本地文件系統中獲取二進制文件來加載類,也可以從遠程主機上獲取二進制文件來加載類
URLClassLoader(URL[] urls):使用默認的父類加載器創建一個ClassLoader對象,該對象將從urls所指定的系列路徑來查詢並加載類
URLClassLoader(URL[] urls,ClassLoader parent):使用指定的父類加載器創建一個Classloader對象,其它功能與前一個構造器相同
一旦得到URLClassloader對象之後,就可以調用該對象的loadClass方法來加載指定的類
下列程序示範瞭如何直接從文件系統中加載Mysql驅動,並使用該驅動獲取數據庫連接,通過這種方式取得數據庫連接就可以無需將mysql驅動添加到CLASSPATH環境變量中
Public class URLClassLoaderTest
{
Private static Connection conn;
//定義一個獲取數據庫連接方法
Public static Connection getConn(String url,String user,String pass) throws Exception
{
If(conn == null){
//創建一個URL數組
URL[] urls = {new URL(“file:mysql-connector-java-3.1.10-bin.jar”)};
//以默認的ClassLoader作爲父ClassLoader,創建URLClassLoader
URLClassLoader myClassLoader= new URLClassLoader(urls);
//加載mysql的JDBC驅動,並創建默認實例
Driver driver = (Driver)myClassLoader.loadClass(“com.mysql.jdbc.Driver”).newInstance();
//創建一個設計JDBC連接屬性的Properties對象
Properties props = new Properties();
//至少需要爲該對象傳入user和password兩個屬性
Props.setProperty(“user”,user);
Props.setProperty(“password”,pass);
//調用Driver 對象的connect方法來取得數據庫連接
Conn = driver.connect(“jdbc:mysql://localhost:3306/mysql”,props);
}
Return conn;
Public static void main(String[] args)throws Exception
{
System.out.println(getConn(“jdbc:mysql///j2ee”,”root”,”root”));
}
}
}
上面程序創建了URLCLassLoader對象,該對象使用默認的父類加載器,該類加載器的類加載路徑是當前路徑下的mysql-connector-java-3.1.10-bin.jar文件,我們將mysql的驅動複製到該路徑下,這樣保證該ClassLoader可以正常加載到com.mysql.jdbc.Driver類
使用ClassLoader的loadClass加載指定類,並調用Class對象的newInstance()方法來創建一個該類的實例----也就是得到com,mysql.jdbc.Driver類的對象,當然該對象的實現類實現類java.sql.Driver接口,所以程序將其強制類型轉化爲Driver,最後程序通過Driver而不是DriverManager來獲取數據庫連接,當我們創建URLClassLoader時傳入了一個URL數組參數,則該ClassLoader就可以從這系列URL指定的資源中加載指定的類,這裏的URL可以以file:爲前綴,表明從本地文件系統加載,可以以http:爲前綴,表明從互聯網通過http訪問來加載,也可以以ftp:爲前綴,表明從互聯網通過FTP訪問來加載