今日整理記錄內容:
1、類加載器(classLoader)
2、代理類(proxy)
一、類加載器(classLoader)
1、類加載器的作用就是將硬盤中的.class文件中的二進制數據加載進內存並將二進制數據轉換爲字節碼,判斷某個.class文件是否被加載過是基於類加載器的,也就是說不同的類加載器可以加載同一個.class文件,這時候內存中就有同一個類的兩份字節碼。
2、BootStrap—>(Jar/lib/rt.jar) 注意: 這不是一個類,而是C語言編寫的底層代碼
ExtClassLoader—>(Jar/lib/ext/*.jar)
AppClassLoader—>(ClassPath指定的所有jar或目錄)
3、類加載器的向上委託機制:當我們用AppClassLoader加載器加載某個類時,這時AppClassLoader會先委託父類ExtClassLoader加載器去加載,而ExtClassLoader又會委託其父BootStrap去加載,如果BootStrap能加載這個類,就讓BootStrap這個加載器去加載,如果不能就讓其子類ExtClassLoader去加載,同理是否讓AppClassLoader加載。
4、如果A中引用了類B,Java虛擬機將使用加載類A的類加載器來加載類B。
還可以直接調用ClassLoadeer.loadClass()方法來指定某個類加載器去加載某個類。
5、自定義類加載器的用處:
當class文件不在ClassPath路徑下,默認系統類加載器無法找到該class文件,在這種情況下我們需要實現一個自定義的ClassLoader來加載特定路徑下的class文件生成class對象。
當一個class文件是通過網絡傳輸並且可能會進行相應的加密操作時,需要先對class文件進行相應的解密後再加載到JVM內存中,這種情況下也需要編寫自定義的ClassLoader並實現相應的邏輯。
當需要實現熱部署功能時(一個class文件通過不同的類加載器產生不同class對象從而實現熱部署功能),需要實現自定義ClassLoader的邏輯。
6、類加載器間的關係(並非指繼承關係):
啓動類加載器(BootStrap),由C++實現,沒有父類。
拓展類加載器(ExtClassLoader),由Java語言實現,父類加載器爲null
系統類加載器(AppClassLoader),**由Java語言實現,父類加載器爲**ExtClassLoader
7、注意這裏所指的父類並不是Java繼承關係中的那種父子關係
8、自定義類加載器,父類加載器肯定爲AppClassLoader。
解釋:
自定義加載器類:
public class MyClassLoaderTest extends ClassLoader{
private String rootDir;
public MyClassLoaderTest(String rootDir) {
this.rootDir = rootDir;
}
/**
* 編寫findClass方法的邏輯
* @param name
* @return
* @throws ClassNotFoundException
*/
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
// 獲取類的class文件字節數組
System.out.println("調用了findClass()");
byte[] classData = getClassData(name);
if (classData == null) {
throw new ClassNotFoundException();
} else {
//直接生成class對象
return defineClass(name, classData, 0, classData.length);
}
}
/**
* 編寫獲取class文件並轉換爲字節碼流的邏輯
* @param className
* @return
*/
private byte[] getClassData(String className) {
// 讀取類文件的字節
String path = classNameToPath(className);
try {
InputStream ins = new FileInputStream(path);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int bufferSize = 4096;
byte[] buffer = new byte[bufferSize];
int bytesNumRead = 0;
// 讀取類文件的字節碼
while ((bytesNumRead = ins.read(buffer)) != -1) {
baos.write(buffer, 0, bytesNumRead);
}
return baos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
/**
* 類文件的完全路徑
* @param className
* @return
*/
private String classNameToPath(String className) {
return rootDir + File.separatorChar
+ className.replace('.', File.separatorChar) + ".class";
}
public static void main(String[] args) throws IllegalAccessException, InstantiationException{
MyClassLoaderTest myClazzLoader1 = new MyClassLoaderTest("F:/");
MyClassLoaderTest myClazzLoader2 = new MyClassLoaderTest("F:/");
try {
//直接調用findClass方法可以避免向上委託機制的發生和讀取內存的過程;
Class clazz1 = myClazzLoader1.findClass("com.hbbfxy.ClassTest");
Class clazz2 = myClazzLoader2.findClass("com.hbbfxy.ClassTest");
//Class clazz3 = myClazzLoader1.findClass("com.hbbfxy.ClassTest"); //這裏會報錯,因爲內存中已經有了myClazzLoader1對象的類對象,報告重複定義錯誤。
System.out.println("clazz1:"+clazz1.hashCode());
System.out.println("clazz2:"+clazz2.hashCode());
//調用loadClass方法會執行向上委託機制和讀取內存過程
Class clazz4 = myClazzLoader1.loadClass("com.hbbfxy.ClassTest");
Class clazz5 = myClazzLoader2.loadClass("com.hbbfxy.ClassTest");
//Class clazz6 = myClazzLoader2.loadClass("com.hbbfxy.ClassTest");//這裏不會報錯,因爲這是從內存中直接取出,沒有向內存中存儲。
System.out.println("clazz4:"+clazz4.hashCode());
System.out.println("clazz5:"+clazz5.hashCode());
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
輸出結果:
調用了findClass()
調用了findClass()
clazz1:118352462
clazz2:1550089733
clazz4:118352462 //因爲之前myClazzLoader1調用findClass()已經將myClazzLoader1類的類對象存儲到內存,所以這裏直接獲取。
clazz5:1550089733 //因爲之前myClazzLoader2調用findClass()已經將myClazzLoader2類的類對象存儲到內存,所以這裏直接獲取。
上述自定義加載器類(摘自深入理解Java類加載器(ClassLoader))
測試類加載器之間的關係:
public static void main(String[] args) {
// TODO Auto-generated method stub
/*獲得Bootstrap類加載器*/
ClassLoader clazzLoader = System.class.getClassLoader();
System.out.println("返回null就說明使用的是BootStrap類加載器:"+clazzLoader);
/*查看自定義加載器的相關加載器之間的關係*/
MyClassLoaderTest myClazzLoader = new MyClassLoaderTest(" ");
System.out.println("加載自定義加載器類的加載器:"+MyClassLoaderTest.class.getClassLoader());
System.out.println("自定義類加載器的父類:"+myClazzLoader.getParent());
System.out.println("自定義類加載器的父類的父類:"+myClazzLoader.getParent().getParent());
System.out.println("自定義類加載器的父類的父類的父類:"+myClazzLoader.getParent().getParent().getParent());
}
輸出結果:
返回null就說明使用的是BootStrap類加載器:null
加載自定義加載器類的加載器:sun.misc.Launcher$AppClassLoader@73d16e93
自定義類加載器的父類:sun.misc.Launcher$AppClassLoader@73d16e93
自定義類加載器的父類的父類:sun.misc.Launcher$ExtClassLoader@6d06d69c
自定義類加載器的父類的父類的父類:null
二、代理Proxy
JVM動態生成的類所用的接口和生成的類所代理的類用的接口一致,這樣保證方法存在。主要用於想在調用一個方法的前後自動完成某件事情。
解釋:
動態生成代理類:
1、創建功能擴展方法的接口類
publicinterface Advice{
void before();
void after();
}
2、創建代理框架
/*生成代理框架*/
private static Object getProxy(Object target, Advice advice){
Object proxy = Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// TODO Auto-generated method stub
advice.before();
Object retVal = method.invoke(target, args);
advice.after();
return retVal;
}
});
return proxy;
}
3、使用代理類
Collection<String> list = (Collection)getProxy(new ArrayList(), new Advice(){
@Override
public void before() {
// TODO Auto-generated method stub
System.out.println("執行方法前調用的代碼。");
}
@Override
public void after() {
// TODO Auto-generated method stub
System.out.println("執行方法後調用的代碼。");
}
});
list.add("1");
輸出結果:
執行方法前調用的代碼。
執行方法後調用的代碼。
這裏我們需要明白動態生成代理類需要類加載器和要實現的接口,類加載器和要實現的接口和被代理的類相同。
Object invoke(Object proxy, Method method, Object[] args)三個參數:
proxy: 動態生成的代理類;
method:調用的方法;
args:方法中的參數。
注意:被代理的類一定要有接口。
學習心得:觀看資料–》思考問題–》實踐證明–》整理記錄 = 有思想的技術大牛!