java反射基礎
Java 基礎 - 反射機制詳解 | Java 全棧知識體系 (pdai.tech)
類的動態加載
參考鏈接:類的動態加載
構造是和實例化也就是對象相關的。
靜態代碼塊是在初始化的時候就調用的 Class.forName();就會調用靜態代碼塊
forName,加載類時默認初始化
Class.forName(); //默認初始化
ClassLoader classLoader = ClassLoader.getSystemClassLoader();Class.forName("Person",false,classLoader);//不進行初始化
類加載器的研究
類加載器,加載類時默認不初始化。
ClassLoader classLoader = ClassLoader.getSystemClassLoader();Class<?> person = classLoader.loadClass("Person");Class<?> person = Class.forName("Person", false, classLoader); //兩個代碼作用相同
底層的原理,實現加載任意的類
java雙親委派
在Class<?> person1 = classLoader.loadClass("Person");
處打個斷點進行調試。
首先調用ClassLoader.classLoader(a)因為AppClassLoader中的classLoader參數是兩個,所以調用到了其父類ClassLoader.classLoader(a,b)
之后ClassLoader.classLoader(a,b)調用AppClassLoader.classLoader(a), —> ClassLoader.classLoader(a,b)
在ClassLoader.classLoader(a,b)中就涉及到了雙親委派模型
protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException
{synchronized (getClassLoadingLock(name)) {// First, check if the class has already been loadedClass<?> c = findLoadedClass(name); //檢查類有沒有被加載if (c == null) { //類沒有被加載 進入long t0 = System.nanoTime();try {if (parent != null) { //還有父加載器的話,讓父加載器loadClass 這里也就是ExtClassLoaderc = 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 statssun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);sun.misc.PerfCounter.getFindClasses().increment();}}if (resolve) {resolveClass(c);}return c;}
}
進入ExtClassLoader.loadCLass, 因為其中沒有loadCLass所以又直接調用到了,上面CLass.loadClass方法。
這次
protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException
{synchronized (getClassLoadingLock(name)) {// First, check if the class has already been loadedClass<?> c = findLoadedClass(name);if (c == null) {long t0 = System.nanoTime();try {if (parent != null) { //找不到父加載器了,因為bootstrap ClassLoader 不在java中c = parent.loadClass(name, false);} else {c = findBootstrapClassOrNull(name); //走到這一步 也不回找到 因為是一個普通的類 不會調用BootstrapClassLoader去加載}} 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); //之后走到findClass("Person") 因為最后是在App CLassLoader中加載的 所以ExtClassLoader中先不跟了 返回的是null// this is the defining class loader; record the statssun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);sun.misc.PerfCounter.getFindClasses().increment();}}if (resolve) {resolveClass(c);}return c;}
}
之后return c=null邏輯又回到了 AppCLassLoader的loadCLass
protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException
{synchronized (getClassLoadingLock(name)) {// First, check if the class has already been loadedClass<?> c = findLoadedClass(name); //檢查類有沒有被加載if (c == null) { //類沒有被加載 進入long t0 = System.nanoTime();try {if (parent != null) { //還有父加載器的話,讓父加載器loadClass 這里也就是ExtClassLoaderc = parent.loadClass(name, false); //ExtCLassLoader返回null。所以c=null} else {c = findBootstrapClassOrNull(name);}} catch (ClassNotFoundException e) {// ClassNotFoundException thrown if class not found// from the non-null parent class loader}if (c == null) { //因為C=null 進入// If still not found, then invoke findClass in order// to find the class.long t1 = System.nanoTime();c = findClass(name); //進到findClass("Person") 跟一下這里// this is the defining class loader; record the statssun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);sun.misc.PerfCounter.getFindClasses().increment();}}if (resolve) {resolveClass(c);}return c;}
}
下面跟進findClass(“Person”)
因為AppClassLoader總沒有findClass方法,所以找到了其父類URLClassLoader的findClass
protected Class<?> findClass(final String name)throws ClassNotFoundException
{final Class<?> result;try {result = AccessController.doPrivileged(new PrivilegedExceptionAction<Class<?>>() {public Class<?> run() throws ClassNotFoundException {String path = name.replace('.', '/').concat(".class");Resource res = ucp.getResource(path, false); //ucp是類的路徑 URLClassPath類if (res != null) { //res不為空 進入try {return defineClass(name, res); //主要跟一下defindClass} catch (IOException e) {throw new ClassNotFoundException(name, e);}} else {return null;}}}, acc);} catch (java.security.PrivilegedActionException pae) {throw (ClassNotFoundException) pae.getException();}if (result == null) {throw new ClassNotFoundException(name);}return result;
}
下圖可以觀察到 AppClassLoader調用findCLass時,ucp(查找路徑)里面加入了file:/H:/java_des/target/classes/(我們項目Class路徑),所以可以查到Person類,res不為空。
跟一下URLCLassLoader.defineClass
return 調用的是URLClassLoader的父類SecureClassLoader的defineClass方法
return 調用的是CLassLoader的defineClass方法
protected final Class<?> defineClass(String name, byte[] b, int off, int len,ProtectionDomain protectionDomain) //這里name是類名,b是字節碼throws ClassFormatError
{protectionDomain = preDefineClass(name, protectionDomain);String source = defineClassSourceLocation(protectionDomain);Class<?> c = defineClass1(name, b, off, len, protectionDomain, source); //在defineClass1完成類的加載(字節碼) 是個native類postDefineClass(c, protectionDomain);return c;
}
之后一層一層返回加載的類,加載到了URLCLass.findClass中return defineClass(name, res);處。
下一步,也就是最終返回到了我們寫的loadClass方法調用代碼處,可以看到返回了Person類
總結
ClassLoader -> SecureClassLoader -> URLCLassLoader -> AppClassLoader (繼承關系 父->子)
ClassLoader.loadClass -> URLCLass.findClass(重寫方法)(路徑中能找到類才進入defineCLass) ->SecureClassLoader.defineClass(從字節碼加載類)->ClassLoader.defineClass
利用
先編寫并編譯一個彈計算器的代碼,放到一個指定路徑,之后把項目中的Test.class刪除(項目路徑沒有Test.class,看看是否能夠通過類加載器的利用,找到類)
import java.io.IOException;public class Test {static { //靜態代碼塊try {Runtime.getRuntime().exec("calc");} catch (IOException e) {e.printStackTrace();}}
}
URLCLassLoader 任意類加載:file/http/jar 協議
public class LoadClassTest {public static void main(String[] args) throws Exception {URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{new URL("file:///G:\\Java反序列化\\class_test\\")}); //指定Class的查找路徑URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{new URL("http://127.0.0.1:9999/")}); //指定Class的查找路徑URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{new URL("jar:http://127.0.0.1:9999/Test.jar!/")}); //指定Class的查找路徑Class<?> c = urlClassLoader.loadClass("Test"); //load Person類c.newInstance(); //實例化}
}
ClassLoader.defineClass 字節碼加載任意類 私有
public class LoadClassTest {public static void main(String[] args) throws Exception {ClassLoader classLoader = ClassLoader.getSystemClassLoader();Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);defineClass.setAccessible(true);byte[] code = Files.readAllBytes(Paths.get("G:\\Java反序列化\\class_test\\Test.class"));Class c = (Class) defineClass.invoke(classLoader, "Test",code, 0, code.length); //對象 類名 字節碼 字節碼起始 字節碼長度 defineClass返回的是Class<?> 這里也就是返回的Test.classc.newInstance(); //實例化 觸發靜態代碼塊}
}
Unsafe.defineClass 字節碼加載 public類但是不能直接調用,需要先反射調用public方法實例化類
Unsafe類中defineClass方法是public的,但是是個單例模式,不能直接調用defineClass()。
看到有個getUnsafe方法,是public的,但是直接調用Unsafe.getUnsafe()
是會報錯的因為有個安全檢查。
最后找到theUnsafe屬性
private static final Unsafe theUnsafe = new Unsafe();
所以反射調用theUnsafe屬性去實例化Unsafe
public native Class<?> defineClass(String name, byte[] b, int off, int len,ClassLoader loader,ProtectionDomain protectionDomain);
public class LoadClassTest {public static void main(String[] args) throws Exception {ClassLoader classLoader = ClassLoader.getSystemClassLoader();byte[] code = Files.readAllBytes(Paths.get("G:\\Java反序列化\\class_test\\Test.class"));Class unsafe = Unsafe.class;Field theUnsafeField = unsafe.getDeclaredField("theUnsafe");theUnsafeField.setAccessible(true);Unsafe unsafe1 = (Unsafe) theUnsafeField.get(null);Class<?> test = unsafe1.defineClass("Test", code, 0, code.length, classLoader, null);test.newInstance();}
}