JVM類加載:如何手寫自定義類加載器,命名空間詳解
二進制名字
如java.net.URLClassLoader$3$1 表示URLClassLoader的第三個匿名內部類中的第一個匿名內部類。
- 類加載器是負責加載類的對象。類加載器是一個抽象類。給定類的二進制名,類加載器應該嘗試定位或生成構成類定義的數(shù)據(jù)(回去查找對應的class文件如果沒有解析成class文件)。一個典型的策略是將名稱轉換為文件名,然后從文件中讀取該名稱的“類文件”系統(tǒng)。(我們編寫的java程序類都是這樣運行)。
- 每個類對象都包含對定義它的類加載器的引用(getClassLoader()獲取當前類的類加載器)。
- 數(shù)組類的類對象不是由類加載器創(chuàng)建的,而是由類加載器創(chuàng)建的根據(jù)Java運行時的要求自動執(zhí)行(數(shù)組類對象是由jvm虛擬機動態(tài)創(chuàng)建的)。數(shù)組類的類加載器,如 getclassloader()返回的類與它的元素類型的類裝入器相同(數(shù)組元素類型是什么類加載器就是什么類型);如果 元素類型是基本(原生8種)類型,因此數(shù)組類沒有類裝入器。
- 應用程序實現(xiàn)的ClassLoader類加載器為了擴展Java虛擬機動態(tài)加載的方式類。
示例:
public class Test15 {
public static void main(String[] args) {
String[] strings = new String[2];
System.out.println(strings.getClass().getClassLoader()); //string[]數(shù)組元素類型是String 在rt.jar包中是由根類加載器加載的
System.out.println("----------------");
Test15[] test15s = new Test15[2];
System.out.println(test15s.getClass().getClassLoader());//同理 該元素是由AppClassLoader系統(tǒng)類加載器加載的 但是數(shù)組本身不是由類加載器加載
System.out.println("----------------");
int[] ints = new int[2];//如果 元素類型是基本(原生8種)類型,因此數(shù)組類沒有類裝入器。
System.out.println(ints.getClass().getClassLoader());
}
}
打印:
/*
null 根類加載器
----------------
sun.misc.Launcher$AppClassLoader@18b4aac2
----------------
null 為空
*/
- 安全管理器通常會使用類裝入器來指示安全域(類加載始終伴隨著安全管理器所以類加載是安全的)
- ClassLoader類使用委托模型進行搜索類和資源。ClassLoader的每個實例都有一個關聯(lián)的父類裝入器。當請求查找一個類或資源,一個類加載器實例將委托父類搜索類或資源,然后再嘗試查找類或資源本身。虛擬機的內置類加載器,稱為“啟動/根類加載器”,本身沒有父類,但可以充當類裝入器實例的父類。
- 支持類的并發(fā)加載的類加載器稱為 并行能力類加載器,需要注冊類的初始化時間registerAsParallelCapable。ClassLoader.registerAsParallelCapable這個方法。注意,類裝入器類被注冊為并行類默認為able。但是,它的子類仍然需要注冊它們自己如果他們是并行的能力。
- 委托模型不嚴格的環(huán)境層次結構,類加載器需要能夠并行,否則類加載可能導致死鎖,因為加載器鎖被持有類加載過程的持續(xù)時間(參見{@link #loadClass)方法。
- 通常,Java虛擬機從本地文件加載類平臺相關的系統(tǒng)。例如,在UNIX系統(tǒng)中在CLASSPATH環(huán)境變量定義的目錄加載類
- 然而,有些類可能不是起源于一個文件;他們可能會產生從其他來源,如網絡,或它們可以由一個應用程序(動態(tài)代理)。方法{@link #defineClass(String, byte[], int, int)defineClass}將字節(jié)數(shù)組轉換為類的實例可以使用以下命令創(chuàng)建這個新定義的類的實例 {@link Class#newInstance Class.newInstance}。
- 類加載器創(chuàng)建的對象的方法和構造函數(shù)可以引用其他類。要確定所引用的類即Java虛擬機調用{@link #loadClass loadClass}方法(這個方法解決這個問題)最初創(chuàng)建類的類加載器。
ClassLoader loade = new NetworkClassLoader(host,port);
Object main = loader.loadClass("Main", true).newInstance();
自定義加載器子類必須定義兩個方法{@link#findClass findClass}和loadClassData加載類來自網絡。一旦它下載了構成類的字節(jié),應該使用方法{@link #defineClass defineClass} to創(chuàng)建一個類實例。一個示例實現(xiàn)是:
class NetworkClassLoader extends ClassLoader {
String host;
int port;
public Class findClass(String name) {
byte[] b = loadClassData(name);
return defineClass(name, b, 0, b.length);//通過名字將Class對象返回給調用者
}
private byte[] loadClassData(String name) {
// load the class data from the connection
.
}
}
編寫自定義類加載器
自定一 此時因為在ClassPath下 所以會調用父類AppClassLoader的系統(tǒng)類加載器 所以自定義的的findClass不會被執(zhí)行。
public class Test16 extends ClassLoader {
private String classLoaderName;
private final String fileExtension = ".class";
public Test16(String classLoaderName) {
// this(checkCreateClassLoader(), getSystemClassLoader());
// ClassLoader中當創(chuàng)建新的類加載器返回的的是系統(tǒng)類加載器, 所以當創(chuàng)建新的類加載器 默認父加載器為系統(tǒng)類加載器
super();//可加可不加
this.classLoaderName = classLoaderName;
}
public Test16(ClassLoader parent, String classLoaderName) {
// this(checkCreateClassLoader(), parent);
//ClassLoader中當創(chuàng)建新的類加載器自定義父加載器 如 :
//a繼承b b繼承ClassLoader 此時a可以拿這個構造方法將b作為自己的雙親 不一定都交給系統(tǒng)類加載器
super(parent);
this.classLoaderName = classLoaderName;
}
/**
* 查找指定二進制名字的class 這個方法應該被子類加載器實現(xiàn)重寫,再檢查完對應父加載器之后該方法會被loaderClass()方法調用 ,
* 在父類中 throw new ClassNotFoundException(name); 只是拋出來一個異常必須重寫
* 如:
* java.lang.String
*
* @param className
* @return
* @throws ClassNotFoundException
*/
@Override
protected Class<?> findClass(String className) throws ClassNotFoundException {
//對應二進制名字對應的字節(jié)數(shù)組
byte[] data = this.loadClassData(className);
System.out.println("findClass invoked" + className);
System.out.println("class loader name" + classLoaderName);
//defineClass(類名,字節(jié)數(shù)據(jù),起,末) 創(chuàng)建類實例
return this.defineClass(className, data, 0, data.length);
}
//獲取文件字節(jié)數(shù)據(jù)
private byte[] loadClassData(String name) {
InputStream is = null;
byte[] data = null;
ByteArrayOutputStream baos = null;
try {
//傳過來的文件名加上后綴
is = new FileInputStream(new File(name + this.fileExtension));
baos = new ByteArrayOutputStream();
int ch = 0;
while (-1 != (ch = is.read())) {
baos.write(ch);
}
data = baos.toByteArray();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
is.close();
baos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return data;
}
public static void main(String[] args) throws Exception {
Test16 test16 = new Test16("test16");
test(test16);
}
public static void test(ClassLoader classLoader) throws IllegalAccessException, Exception {
//改方法會調用我們重寫之后的findClass方法
Class<?> clasz = classLoader.loadClass("com.example.demo.com.jvm.Test1");
Object o = clasz.newInstance();
System.out.println(o);
}
}
打印結果:
//只輸出了
com.example.demo.com.jvm.Test1@1eb44e46
基于上例重構 新增自定義路徑將class字節(jié)碼路徑放在其他位置 此時父類加載器appClassLoader無法加載 此時就會調用自己的findClass() 需要將classpath 下的需要加載的.class刪除。
public class Test16 extends ClassLoader {
private String classLoaderName;
//路徑
private String path;
private final String fileExtension = ".class";
public Test16(String classLoaderName) {
// this(checkCreateClassLoader(), getSystemClassLoader());
// ClassLoader中當創(chuàng)建新的類加載器返回的的是系統(tǒng)類加載器, 所以當創(chuàng)建新的類加載器 默認父加載器為系統(tǒng)類加載器
super();//可加可不加
this.classLoaderName = classLoaderName;
}
public void setPath(String path) {
this.path = path;
}
public Test16(ClassLoader parent, String classLoaderName) {
// this(checkCreateClassLoader(), parent);
//ClassLoader中當創(chuàng)建新的類加載器自定義父加載器 如 :
//a繼承b b繼承ClassLoader 此時a可以拿這個構造方法將b作為自己的雙親 不一定都交給系統(tǒng)類加載器
super(parent);
this.classLoaderName = classLoaderName;
}
/**
* 查找指定二進制名字的class 這個方法應該被子類加載器實現(xiàn)重新,再檢查完對應父加載器之后該方法會被loaderClass()方法調用 ,
* 在父類中 throw new ClassNotFoundException(name); 只是拋出來一個異常必須重寫
* 如:
* java.lang.String
*
* @param className
* @return
* @throws ClassNotFoundException
*/
@Override
protected Class<?> findClass(String className) throws ClassNotFoundException {
//對應二進制名字對應的字節(jié)數(shù)組
byte[] data = this.loadClassData(className);
System.out.println("findClass invoked" + className);
System.out.println("class loader name= " + classLoaderName);
//defineClass(類名,字節(jié)數(shù)據(jù),起,末) 創(chuàng)建類實例
return this.defineClass(className, data, 0, data.length);
}
//獲取文件字節(jié)數(shù)據(jù)
private byte[] loadClassData(String className) {
InputStream is = null;
byte[] data = null;
ByteArrayOutputStream baos = null;
className = className.replace(".", "\\");
try {
//傳過來的文件名加上后綴
is = new FileInputStream(new File(this.path + className + this.fileExtension));
baos = new ByteArrayOutputStream();
int ch = 0;
while (-1 != (ch = is.read())) {
baos.write(ch);
}
data = baos.toByteArray();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
is.close();
baos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return data;
}
public static void main(String[] args) throws Exception {
Test16 test16 = new Test16("test16");
// test16.setPath("D:\\workspaces\\zookeeper\\target\\classes\\");
test16.setPath("E:\\cx\\");
//改方法會調用我們重寫之后的findClass方法
Class<?> clasz = test16.loadClass("com.example.demo.com.jvm.Test1");
System.out.println("class: " + clasz.hashCode());
Object o = clasz.newInstance();//對象內存地址有哈希值
System.out.println(o);
}
}
此時findClass中的打印語句執(zhí)行了
findClass invokedcom.example.demo.com.jvm.Test1
class loader name= test16
class: 1365202186
com.example.demo.com.jvm.Test1@626b2d4a
當我們在編寫完自定義類加載器時重寫了loadClassData和findClass方法。在main方法中實例化Test16對象調用返回系統(tǒng)類加載器的構造函數(shù),因為在classpath路徑以上(雙親委托下并沒有找到對應的.class文件 所以自定義加載器去加載 此時調用classLoader的loadClass方法獲取對應的Class實例 此時自定義類加載器并沒有直接調用findClass方法 而是在loadClass方法中ClassLoader幫我們直接調用了我們自己重寫好的findclass方法。
調用findClass
方法只是拋出了一個異常所有我們在自定義類加載器時必須重寫對應方法。
重寫方法
當我們調用對應的方法完畢。
調用完畢
重寫loadClassData方法將獲取對應二進制類名文件字節(jié)數(shù)組。
重寫loadClassData
在通過方法獲取對應二進制名稱的Class對象。
通過defineClass
而在ClassLoader中的defineClass方法調用了重載的defineClass方法多加了個ProtectionDomainProtectionDomain 類封裝域的特征,域中包裝一個類集合,在代表給定的主體集合執(zhí)行這些類的實例時會授予它們一個權限集合。主要是支持支持動態(tài)安全策略。
在這個方法里面才是真正獲取對應二進制名字的Class對象。
protected final Class<?> defineClass(String name, byte[] b, int off, int len,
ProtectionDomain protectionDomain)
throws ClassFormatError
{
//前置處理
protectionDomain = preDefineClass(name, protectionDomain);
String source = defineClassSourceLocation(protectionDomain);
//此時調用底層本地C++代碼獲取Class
Class<?> c = defineClass1(name, b, off, len, protectionDomain, source);
//后置處理拼接對象后綴名
postDefineClass(c, protectionDomain);
return c;
}
自此程序運行結束 返回Class對象。
ClassLoader 中l(wèi)oadClass 詳解
- classLoader.lordClass和forName的區(qū)別(主動加載和被動加載的區(qū)別)。
- class.forName()除了將類的.class文件加載到jvm中之外,還會對類進行解釋,執(zhí)行類中的static塊。
- 而classLoader.lordClass只干一件事情,就是將.class文件加載到jvm中,不會執(zhí)行static中的內容,只有在newInstance才會去執(zhí)行static塊。(不初始)。
- Class.forName(name, initialize, loader)帶參函數(shù)也可控制是否加載static塊。并且只有調用了newInstance()方法才用調用構造函數(shù),來創(chuàng)建類的對象(初始)。
ClassLoader 中l(wèi)oadClass 此時獲取的Class還沒有鏈接 只是剛加載到JVM中。
加載指定的二進制名的類此方法的實現(xiàn)會默認按照以下的順序尋找類
- 調用{@link #findLoadedClass(String)}檢查類是否已經加載(一個類只能被加載一次)。
- 調用父類的{@link #loadClass(String) loadClass}方法,如果父類是null 就會使用虛擬機內置的根類加載器。
- 調用{@link #findClass(String)}方法查找。
如果類被發(fā)現(xiàn)使用上述步驟,和解析標志為真,此方法將調用{@link#resolveClass(Class)}方法的結果類對象。
子類ClassLoader被鼓勵重寫{@link#findClass(String)},而不是這個方法。
在整個類裝入過程中除非被覆蓋,否則此方法對的結果進行同步{@link #getClassLoadingLock getClassLoadingLock}方法。
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 檢查類是否已經加載(一個類只能被加載一次)
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
//如果父類不是null 就會使用虛擬機內置的根類加載器去加載二進制名(name對應的數(shù)據(jù)),
//子類ClassLoader被鼓勵重寫
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
// 如果類被發(fā)現(xiàn)使用上述步驟,和解析標志為真,此方法將調用{@link#resolveClass(Class)}方法的結果類對象。
if (resolve) {
resolveClass(c);
}
//返回Class
return c;
}
}
基于上例Test16繼續(xù)重構。
public class Test16 extends ClassLoader {
private String classLoaderName;
//路徑
private String path;
private final String fileExtension = ".class";
public Test16(String classLoaderName) {
// this(checkCreateClassLoader(), getSystemClassLoader());
// ClassLoader中當創(chuàng)建新的類加載器返回的的是系統(tǒng)類加載器, 所以當創(chuàng)建新的類加載器 默認父加載器為系統(tǒng)類加載器
super();//可加可不加
this.classLoaderName = classLoaderName;
}
public void setPath(String path) {
this.path = path;
}
public Test16(ClassLoader parent, String classLoaderName) {
// this(checkCreateClassLoader(), parent);
//ClassLoader中當創(chuàng)建新的類加載器自定義父加載器 如 :
//a繼承b b繼承ClassLoader 此時a可以拿這個構造方法將b作為自己的雙親 不一定都交給系統(tǒng)類加載器
super(parent);
this.classLoaderName = classLoaderName;
}
/**
* 查找指定二進制名字的class 這個方法應該被子類加載器實現(xiàn)重新,再檢查完對應父加載器之后該方法會被loaderClass()方法調用 ,
* 在父類中 throw new ClassNotFoundException(name); 只是拋出來一個異常必須重寫
* 如:
* java.lang.String
*
* @param className
* @return
* @throws ClassNotFoundException
*/
@Override
protected Class<?> findClass(String className) throws ClassNotFoundException {
//對應二進制名字對應的字節(jié)數(shù)組
byte[] data = this.loadClassData(className);
System.out.println("findClass invoked:" + className);
System.out.println("class loader name: " + classLoaderName);
//defineClass(類名,字節(jié)數(shù)據(jù),起,末) 創(chuàng)建類實例
return this.defineClass(className, data, 0, data.length);
}
//獲取文件字節(jié)數(shù)據(jù)
private byte[] loadClassData(String className) {
InputStream is = null;
byte[] data = null;
ByteArrayOutputStream baos = null;
className = className.replace(".", "\\");
try {
//傳過來的文件名加上后綴
is = new FileInputStream(new File(this.path + className + this.fileExtension));
baos = new ByteArrayOutputStream();
int ch = 0;
while (-1 != (ch = is.read())) {
baos.write(ch);
}
data = baos.toByteArray();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
is.close();
baos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return data;
}
public static void main(String[] args) throws Exception {
Test16 test16 = new Test16("test16");
// test16.setPath("D:\\workspaces\\zookeeper\\target\\classes\\");
test16.setPath("E:\\cx\\");
//改方法會調用我們重寫之后的findClass方法
Class<?> clasz = test16.loadClass("com.example.demo.com.jvm.Test1");
System.out.println("class: " + clasz.hashCode());
Object o = clasz.newInstance();//對象內存地址有哈希值
System.out.println(o);
Test16 test162 = new Test16("test17");
Class<?> clasz2 = test16.loadClass("com.example.demo.com.jvm.Test1");
System.out.println("class: " + clasz2.hashCode());
Object o2 = clasz2.newInstance();//對象內存地址有哈希值
System.out.println(o2);
}
}
/*
當classPath下有對應的加載的.class時 第二次交給父類加載器發(fā)現(xiàn)已經加載所以字節(jié)拿過來用 所以此時獲取的Class類時一致的
class: 515132998
com.example.demo.com.jvm.Test1@6504e3b2
class: 515132998
com.example.demo.com.jvm.Test1@515f550a
當classPath下沒有對應的加載的.class 制定了對應的路徑 此時類獲取幾次就會加載幾次 涉及到了命名空間的問題
findClass invoked:com.example.demo.com.jvm.Test1
class loader name: test16
class: 1365202186
com.example.demo.com.jvm.Test1@626b2d4a
--------------兩個不同的命名空間------------------
findClass invoked:com.example.demo.com.jvm.Test1
class loader name: test17
class: 932583850
com.example.demo.com.jvm.Test1@cac736f
*/
總結:同一個命名空間不會出現(xiàn)兩個完全相同的類,不同的命名空間會出現(xiàn)兩個完全相同的類,父加載器加載的類不可以看到子類加載器加載的類,但是子類加載器加載的類可以看到父類加載器加載的類。
解釋:
命名空間
上例繼續(xù)改造://將loader1作為loader2的父類加載器。
public class Test16 extends ClassLoader {
private String classLoaderName;
//路徑
private String path;
private final String fileExtension = ".class";
public Test16(String classLoaderName) {
// this(checkCreateClassLoader(), getSystemClassLoader());
// ClassLoader中當創(chuàng)建新的類加載器返回的的是系統(tǒng)類加載器, 所以當創(chuàng)建新的類加載器 默認父加載器為系統(tǒng)類加載器
super();//可加可不加
this.classLoaderName = classLoaderName;
}
public void setPath(String path) {
this.path = path;
}
public Test16(ClassLoader parent, String classLoaderName) {
// this(checkCreateClassLoader(), parent);
//ClassLoader中當創(chuàng)建新的類加載器自定義父加載器 如 :
//a繼承b b繼承ClassLoader 此時a可以拿這個構造方法將b作為自己的雙親 不一定都交給系統(tǒng)類加載器
super(parent);
this.classLoaderName = classLoaderName;
}
/**
* 查找指定二進制名字的class 這個方法應該被子類加載器實現(xiàn)重新,再檢查完對應父加載器之后該方法會被loaderClass()方法調用 ,
* 在父類中 throw new ClassNotFoundException(name); 只是拋出來一個異常必須重寫
* 如:
* java.lang.String
*
* @param className
* @return
* @throws ClassNotFoundException
*/
@Override
protected Class<?> findClass(String className) throws ClassNotFoundException {
//對應二進制名字對應的字節(jié)數(shù)組
byte[] data = this.loadClassData(className);
System.out.println("findClass invoked:" + className);
System.out.println("class loader name: " + classLoaderName);
//defineClass(類名,字節(jié)數(shù)據(jù),起,末) 創(chuàng)建類實例
return this.defineClass(className, data, 0, data.length);
}
//獲取文件字節(jié)數(shù)據(jù)
private byte[] loadClassData(String className) {
InputStream is = null;
byte[] data = null;
ByteArrayOutputStream baos = null;
className = className.replace(".", "\\");
try {
//傳過來的文件名加上后綴
is = new FileInputStream(new File(this.path + className + this.fileExtension));
baos = new ByteArrayOutputStream();
int ch = 0;
while (-1 != (ch = is.read())) {
baos.write(ch);
}
data = baos.toByteArray();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
is.close();
baos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return data;
}
public static void main(String[] args) throws Exception {
Test16 loader1 = new Test16("loader1");
// test16.setPath("D:\\workspaces\\zookeeper\\target\\classes\\");
loader1.setPath("E:\\cx\\");
//改方法會調用我們重寫之后的findClass方法
Class<?> clasz = loader1.loadClass("com.example.demo.com.jvm.Test1");
System.out.println("class: " + clasz.hashCode());
Object o = clasz.newInstance();//對象內存地址有哈希值
System.out.println(o);
// System.out.println("------------兩個不同的命名空間--------------------");
Test16 loader2 = new Test16(loader1,"loader2");//將loader1作為loader2的父類加載器
loader2.setPath("E:\\cx\\");
Class<?> clasz2 = loader2.loadClass("com.example.demo.com.jvm.Test1");
System.out.println("class: " + clasz2.hashCode());
Object o2 = clasz2.newInstance();//對象內存地址有哈希值
System.out.println(o2);
}
}
-------------------------------------------
當classPath下沒有對應的加載的.class時
Test16 loader2 = new Test16(loader1,"loader2");//將loader1作為loader2的父類加載器
findClass invoked:com.example.demo.com.jvm.Test1
class loader name: loader1
class: 1365202186
com.example.demo.com.jvm.Test1@626b2d4a //此時父加載器loader1已經加載完畢 loader2直接拿來使用
class: 1365202186
com.example.demo.com.jvm.Test1@5e91993f
通過上例繼續(xù)改造: 新增一個類加載器 父類設置為loader2。
public class Test16 extends ClassLoader {
private String classLoaderName;
//路徑
private String path;
private final String fileExtension = ".class";
public Test16(String classLoaderName) {
// this(checkCreateClassLoader(), getSystemClassLoader());
// ClassLoader中當創(chuàng)建新的類加載器返回的的是系統(tǒng)類加載器, 所以當創(chuàng)建新的類加載器 默認父加載器為系統(tǒng)類加載器
super();//可加可不加
this.classLoaderName = classLoaderName;
}
public void setPath(String path) {
this.path = path;
}
public Test16(ClassLoader parent, String classLoaderName) {
// this(checkCreateClassLoader(), parent);
//ClassLoader中當創(chuàng)建新的類加載器自定義父加載器 如 :
//a繼承b b繼承ClassLoader 此時a可以拿這個構造方法將b作為自己的雙親 不一定都交給系統(tǒng)類加載器
super(parent);
this.classLoaderName = classLoaderName;
}
/**
* 查找指定二進制名字的class 這個方法應該被子類加載器實現(xiàn)重新,再檢查完對應父加載器之后該方法會被loaderClass()方法調用 ,
* 在父類中 throw new ClassNotFoundException(name); 只是拋出來一個異常必須重寫
* 如:
* java.lang.String
*
* @param className
* @return
* @throws ClassNotFoundException
*/
@Override
protected Class<?> findClass(String className) throws ClassNotFoundException {
//對應二進制名字對應的字節(jié)數(shù)組
byte[] data = this.loadClassData(className);
System.out.println("findClass invoked:" + className);
System.out.println("class loader name: " + classLoaderName);
//defineClass(類名,字節(jié)數(shù)據(jù),起,末) 創(chuàng)建類實例
return this.defineClass(className, data, 0, data.length);
}
//獲取文件字節(jié)數(shù)據(jù)
private byte[] loadClassData(String className) {
InputStream is = null;
byte[] data = null;
ByteArrayOutputStream baos = null;
className = className.replace(".", "\\");
try {
//傳過來的文件名加上后綴
is = new FileInputStream(new File(this.path + className + this.fileExtension));
baos = new ByteArrayOutputStream();
int ch = 0;
while (-1 != (ch = is.read())) {
baos.write(ch);
}
data = baos.toByteArray();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
is.close();
baos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return data;
}
public static void main(String[] args) throws Exception {
Test16 loader1 = new Test16("loader1");
// test16.setPath("D:\\workspaces\\zookeeper\\target\\classes\\");
loader1.setPath("E:\\cx\\");
//改方法會調用我們重寫之后的findClass方法
Class<?> clasz = loader1.loadClass("com.example.demo.com.jvm.Test1");
System.out.println("class: " + clasz.hashCode());
Object o = clasz.newInstance();//對象內存地址有哈希值
System.out.println(o);
System.out.println();
// System.out.println("------------兩個不同的命名空間--------------------");
Test16 loader2 = new Test16(loader1, "loader2");//將loader1作為loader2的父類加載器
loader2.setPath("E:\\cx\\");
Class<?> clasz2 = loader2.loadClass("com.example.demo.com.jvm.Test1");
System.out.println("class: " + clasz2.hashCode());
Object o2 = clasz2.newInstance();//對象內存地址有哈希值
System.out.println(o2);
System.out.println();
Test16 loader3 = new Test16(loader2,"loader3");
loader3.setPath("E:\\cx\\");
//改方法會調用我們重寫之后的findClass方法
Class<?> clasz3 = loader3.loadClass("com.example.demo.com.jvm.Test1");
System.out.println("class: " + clasz3.hashCode());
Object o3 = clasz3.newInstance();//對象內存地址有哈希值
System.out.println(o3);
}
}
命名空間一致。
命名空間一致
findClass invoked:com.example.demo.com.jvm.Test1
class loader name: loader1
class: 1365202186
com.example.demo.com.jvm.Test1@626b2d4a loader1 先去加類加載
class: 1365202186
com.example.demo.com.jvm.Test1@5e91993f loader2 交給父類父類交給appClassLoader加載發(fā)現(xiàn)已經加載直接拿來用
class: 1365202186
com.example.demo.com.jvm.Test1@1c4af82c loader3 同上