什么是Java反射?
在前文中,我們有一行代碼?Computer macBookPro = JSON.parseObject(preReceive,Computer.class);
這行代碼是什么意思呢?看起來好像就是我們聲明了一個名為?macBookPro
?的?Computer
?類,它由 fastjson 的 parseObject 方法將?preReceive
?反序列化而來,但?Computer.class
?是什么呢?
在 Java 中,Computer.class
是一個引用,它表示了?Computer
?的字節碼對象(Class對象),這個對象被廣泛應用于反射、序列化等操作中。那么為什么 parseObject 需要這個引用呢?首先 fastjson 是不了解類中的情況的,因此它需要一個方法來動態的獲得類中的屬性,那么 Java 的反射機制提供了這個功能。
Java reflect demo
我們先看一個 Java 反射的 Demo。
package org.example;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;public class JavaReflectDemo {public static void main(String[] args){// 獲取Car類的Class對象,用于后續的反射操作Class<?> temp = Car.class;// 獲得Car類的所有屬性與方法和構造方法Field[] fields = temp.getDeclaredFields();Method[] methods = temp.getDeclaredMethods();Constructor<?>[] constructors = temp.getDeclaredConstructors();// 通過循環遍歷獲得類屬性for (Field field : fields){System.out.println("Field: " + field.getName());}// 通過循環遍歷獲得方法名for (Method method : methods ) {System.out.println("Methods: " + method.getName());}// 通過雙循環獲得類的構造方法及其方法所需要的參數的數據類型for (Constructor<?> constructor : constructors) {System.out.println("Constructor:" + constructor.getName());Class<?>[] constructorParameterType = constructor.getParameterTypes();for (Class<?> parameterType : constructorParameterType) {System.out.println("Parameter type is:" + parameterType.getName());}}// 通過反射調用類方法}public static class Car{private int carLength;public String carName;private int carPrice = 50000;public Car(int carLength, String carName,int carPrice){this.carLength = carLength;this.carName = carName;this.carPrice = carPrice;}private void CarAnnounce() {System.out.println("China Car! Best Car!");System.out.println("The Car Price is " + this.carPrice);System.out.println("The Car Length is " + this.carLength);}private void CarType(){System.out.println("This function is still under development!");}}
}
反射調用類變量
上述代碼中,我們有一個公共靜態類?Car
?,其中包含了私有和公共方法和屬性,在主函數中通過反射獲取了類的屬性和方法以及構造方法,我們逐行分析代碼。
-
Class<?> temp = Car.class;
?這行代碼用于獲取?Car
?的 Class 對象,Class 對象是整個反射操作的起點。那么?Class<?>
?是什么意思呢?其實在這里這個問號指的是?temp
?可以接收任意類型的類,我們也可以通過?Class<Car>
?來接收 Class 對象。 -
getDeclaredFields()
?是 Java 的反射操作,通過 Class 對象獲得類中所有的屬性,包括私有屬性,它返回一個?Field[]
?對象,實際上是一個包含類中所有屬性的數組,但它被特定為?Field[]
?對象。 -
getDeclaredMethods()
?同理,獲得類中所有的方法(但不包含構造方法),返回一個?Methods[]
?數組。 -
getDeclaredConstructors()
?用于獲得類中所有的構造方法,Constructor<?>[]
?的含義是,Constructor
?是個泛型類,它的定義是?Constructor<T>
?,這意味著它適用于任何類型的構造方法,通過使用通配符?<?>
?表示這個數組接收任何類的構造方法,也就是表示了constructors
?這個數組可以用于存儲任意類的任意構造方法。 -
獲得了數組后,通過 Java 的 for-each 循環遍歷數組并打印到屏幕。
運行結果如下。
反射調用類方法
簡要將Demo中的代碼修改如下。
// 通過循環遍歷獲得方法名for (Method method : methods) {// 直接調用類的靜態方法if (method.getName().equals("CarType")) {method.invoke(null);}// 通過類的實例調用類方法if (method.getName().equals("CarAnnounce")){Car tempCar = new Car(1000,"Richard's car");method.invoke(tempCar);// 通過反射獲得類字段,并修改字段值重新調用方法Field field = temp.getDeclaredField("carPrice");field.setAccessible(true);field.set(tempCar, 99999);method.invoke(tempCar);}System.out.println("Methods: " + method.getName());
}
我們可以通過反射直接調用類的方法,method.invoke(類的實例, 參數, 多個參數用逗號隔開)
,若是調用靜態方法可以傳遞?null
?代替類的實例,但如果調用的方法需要參數,我們需要嚴格得按照方法傳入對應的參數。
我們還可以通過反射修改?private
?屬性,例如 Demo 中的?carPrice
。運行結果如下。
我們將?carLength
?使用?final
?修飾符進行修飾,此時直接修改?carLength
?會報錯。如下圖。
但通過反射我們可以修改?final
?修飾符修飾后的屬性。代碼如下。
Field field2 = temp.getDeclaredField("carLength");
field2.setAccessible(true);Field modifiers = field2.getClass().getDeclaredField("modifiers");
modifiers.setAccessible(true);
modifiers.setInt(field2, field2.getModifiers() & ~Modifier.FINAL);field2.set(tempCar, 7777);
method.invoke(tempCar);
我們來重點關注其中的操作,我們首先獲取?carLength
?的?Field
?對象,并設置其為可讀寫的權限。
其次獲取該對象的?modifiers
?對象,它表示?carLength
?被哪些修飾符所修飾。
重點是modifiers.setInt(field2, field2.getModifiers() & ~Modifier.FINAL)
?我們逐步進行解析:
-
modifiers.setInt
?對?modifiers
?對象進行修改,也就是修改?carLength
?的修飾符。 -
首先傳入實例,重點在其參數,這里實際是一個位操作,
getmodifiers()
?方法會返回當前對象的修飾符組合,它是由 Java Modifier 類中定義的值所組合起來的。見下圖。
-
那么?
~Modifier.FINAL
?中的?~
?是對該值取反,0x10
?轉換為二進制為?0001 0000
?取反為?1110 1111
,&
?對其進行與操作,那么實際上就是在去除?FINAL
?修飾符。 -
最后將其結果修改?
modifiers
?對象,也就是去除了?FINAL
?修飾符。
整段代碼的執行結果如下。
反射執行命令
在 Java 中,有一個類叫做?java.lang.Runtime
?,這個類有一個?exec
?方法可以用于執行本地命令。一般情況下我們使用如下的方法執行命令。我們通過Runtime.getRuntime()方法獲得實例,并創建新的Process對象,用于執行命令。
Runtime類是Java中的一個特殊類,它負責提供Java應用程序與運行時環境(Java虛擬機)的交互接口。它被設計為單例模式,確保整個應用程序中只有一個Runtime實例。這種設計決定了Runtime類無法被直接實例化。
package org.example;public class ExecuteCommandDemo {public static void main(String[] args){try {// 創建Runtime對象Runtime temp = Runtime.getRuntime();Process process = temp.exec("calc.exe");// 等待命令執行完畢process.waitFor();} catch (Exception e) {e.printStackTrace();}}
}
而我們同樣可以通過反射來執行命令。
首先獲取Runtime類對象以便后續的反射操作,再從Runtime類中獲取getRuntime方法,通過執行getRuntime方法獲取實例,再從類中找到?exec
?方法,但由于?exec
?具有很多重載版本,我們指定使用接收字符串作為參數的方法。最后通過調用?exec
?方法,執行命令。
package org.example;
import java.lang.reflect.Method;public class ExecuteCommandDemo {public static void main(String[] args){try {Class <?> reflectExec = Class.forName("java.lang.Runtime");Method getruntimeMethod = reflectExec.getMethod("getRuntime");Object runtimeInstance = getruntimeMethod.invoke(null);Method execMethod = reflectExec.getMethod("exec", String.class);Process process = (Process) execMethod.invoke(runtimeInstance, "calc.exe");// 等待命令執行完畢process.waitFor();} catch (Exception e) {e.printStackTrace();}}
}
Fastjson的危險反序列化
@type
?是fastjson中的一個特殊注解,它告訴 fastjson 應該將 JSON 字符串轉換成哪個 Java 類。這很容易出現安全問題。
我們來看下面這段代碼,我們定義了一串json字符串,想要通過@type
注解來將json字符串轉化為java.lang.Runtime
對象,但是 fastjson在 1.2.24 后默認禁用?autoType
?的白名單設置,在默認情況下我們不能任意的將json字符串轉化為指定的java類。
但通過?ParserConfig.getGlobalInstance().addAccept("java.lang")
?我們可以在白名單中添加?java.lang
?類。
后續的代碼就是通過反序列化將其轉換為對象(這里的Object.class是為了接收轉換后的任意對象),再強制轉換為Runtime對象,轉換完成后就和正常調用java.lang.Runtime
執行命令相同了。
package org.example;import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;public class FastjsonDangerousDeserialization {public static void main(String[] args) throws Exception{String json = "{\"@type\":\"java.lang.Runtime\"}";ParserConfig.getGlobalInstance().addAccept("java.lang");Runtime runtime = (Runtime) JSON.parseObject(json, Object.class);runtime.exec("calc.exe");}
}
文章轉載自:ZywOo
原文鏈接:https://www.cnblogs.com/RichardLuo/p/18287704/Fastjson_2
體驗地址:引邁 - JNPF快速開發平臺_低代碼開發平臺_零代碼開發平臺_流程設計器_表單引擎_工作流引擎_軟件架構