1.簡介
Java反射就是在運行狀態中,對于任意一個類,都能夠知道這個類的所有屬性和方法;對于任意一個物件,都能夠呼叫它的任意方法和屬性;并且能改變它的屬性,由此反射被稱為框架的靈魂,
最終操作的是位元組碼檔案(可以讀和修改位元組碼檔案),java反射機制的相關類在java.lang.reflect.*包下
2.反射作業原理
當我們撰寫完一個Java專案之后,每個java檔案都會被編譯成一個.class檔案,這些Class物件承載了這個類的所有資訊,包括父類、介面、建構式、方法、屬性等,這些class檔案在程式運行時會被ClassLoader加載到虛擬機中,當一個類被加載以后,Java虛擬機就會在記憶體中自動產生一個Class物件,我們通過new的形式創建物件實際上就是通過這些Class來創建,只是這個程序對于我們是不透明的而已,反射的作業原 理就是借助Class.java、Constructor.java、Method.java、Field.java這四個類在程式運行時動態訪問和修改任何類的行為和狀態,
3.反射機制相關類
| 類 | 含義 |
|---|---|
| java.lang.Class | 代表整個類 (整個位元組碼) |
| java.lang.reflect.Method | 代表類中的方法 (方法位元組碼) |
| java.lang.reflect.Constructor | 代表類中的構造方法 (構造方法位元組碼) |
| java.lang.reflect.Field | 代表類中的成員變數(靜態變數+實體變數) |
注:必須先獲得Class才能獲取Method、Constructor、Field
4.實體
下面是一個基本類
package com.gk0d.reflect;
public class Person {
//私有屬性
private String name = "Tom";
//公有屬性
public int age = 18;
//構造方法
public Person() {
}
//私有方法
private void say(){
System.out.println("private say()...");
}
//公有方法
public void work(){
System.out.println("public work()...");
}
}
4.1通過反射實體化物件
- 使用Class物件的newInstance()方法來創建Class物件對應類的實體,
// 通過反射機制,獲取Class,通過Class來實體化物件
Class c = Class.forName("com.gk0d.reflect.Person");
// newInstance() 這個方法會呼叫User這個類的無引數構造方法,完成物件的創建,
// 重點是:newInstance()呼叫的是無參構造,所以必須保證無參構造是存在的!
Object obj = c.newInstance();
- 先通過Class物件獲取指定的Constructor物件,再呼叫Constructor物件的newInstance()方法來創建實體,這種方法可以用指定的構造器構造類的實體,
//獲取String所對應的Class物件
Class<?> c = String.class;
//獲取String類帶一個String引數的構造器
Constructor constructor = c.getConstructor(String.class);
//根據構造器創建實體
Object obj = constructor.newInstance("23333");
System.out.println(obj);
4.2獲取方法
獲取某個Class物件的方法集合,主要有以下幾個方法:
getDeclaredMethods方法回傳類或介面宣告的所有方法,包括公共、保護、默認(包)訪問和私有方法,但不包括繼承的方法
public Method[] getDeclaredMethods() throws SecurityException
getMethods方法回傳某個類的所有公用(public)方法,包括其繼承類的公用方法
public Method[] getMethods() throws SecurityException
- getMethod 方法回傳一個特定的方法,其中第一個引數為方法名稱,后面的引數為方法的引數對應Class的物件,
public Method getMethod(String name, Class<?>... parameterTypes)
4.3獲取構造器資訊
獲取類構造器的用法與上述獲取方法的用法類似,主要是通過Class類的getConstructor方法得到Constructor類的一個實體,而Constructor類有一個newInstance方法可以創建一個物件實體:
public T newInstance(Object ... initargs)
此方法可以根據傳入的引數來呼叫對應的Constructor創建物件實體
4.4獲取類的成員變數資訊
主要是這幾個方法,:
- getFiled:訪問公有的成員變數
- getDeclaredField:所有已宣告的成員變數,但不能得到其父類的成員變數
- getFileds 和 getDeclaredFields 方法用法同上(參照 Method),
4.5呼叫方法
當我們從類中獲取了一個方法后,我們就可以用 invoke() 方法來呼叫這個方法,invoke 方法的原型為:
public Object invoke(Object obj, Object... args)
throws IllegalAccessException, IllegalArgumentException,
InvocationTargetException
下面是一個實體
public class test1 {
public static void main(String[] args) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
Class<?> klass = methodClass.class;
//創建methodClass的實體
Object obj = klass.newInstance();
//獲取methodClass類的add方法
Method method = klass.getMethod("add",int.class,int.class);
//呼叫method對應的方法 => add(1,4)
Object result = method.invoke(obj,1,4);
System.out.println(result);
}
}
class methodClass {
public final int fuck = 3;
public int add(int a,int b) {
return a+b;
}
public int sub(int a,int b) {
return a+b;
}
5.利用
反射的利用主要是呼叫一些危險函式,例如Runtime.exec方法可以本地執行命令,大部分關于jsp命令執行的payload可能都是呼叫此方法進行命令執行的,
5.1普通執行命令
import org.apache.commons.io.IOUtils;
import java.io.IOException;
import java.io.InputStream;
public class Main {
public static void main(String[] args) throws IOException {
InputStream ipconfig = Runtime.getRuntime().exec("ipconfig").getInputStream();
String s = IOUtils.toString(ipconfig,"gbk"); //使用IOUtils.toString靜態方法將位元組輸入流轉換為字符
System.out.println(s);
}
}
缺點就是代碼是靜態的,并不能做到類似webshell的效果
5.2利用反射執行命令
import org.apache.commons.io.IOUtils;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
public class Test2 {
public static void main(String[] args) throws Exception {
String command = "ipconfig";
Class cls = Class.forName("java.lang.Runtime"); //Runtime加載進記憶體
Constructor declaredConstructor = cls.getDeclaredConstructor(); //獲取構造方法,
declaredConstructor.setAccessible(true); //暴力反射,因為JDK的安全檢查耗時較多.所以這種方式方式關閉安全檢查,達到提升反射速度的目的
Object o = declaredConstructor.newInstance(); //創建Runtime類
Method exec = cls.getMethod("exec", String.class); //獲取exec方法,設定需要引數string型別引數
Process process = (Process) exec.invoke(o,command); //執行exec方法,并傳入ipconfig引數
InputStream inputStream = process.getInputStream(); //獲取輸出的資料
String ipconfig = IOUtils.toString(inputStream,"gbk"); //位元組輸出流轉換為字符
System.out.println(ipconfig);
}
}
method.invoke的第一個引數必須是類實體物件,如果呼叫的是static方法那么第一個引數值可以傳null,因為在java中呼叫靜態方法是不需要有類實體的,因為可以直接類名.方法名(引數)的方式呼叫,
method.invoke的第二個引數不是必須的,如果當前呼叫的方法沒有引數,那么第二個引數可以不傳,如果有引數那么就必須嚴格的依次傳入對應的引數型別
本文來自博客園,作者:gk0d,轉載請注明原文鏈接:https://www.cnblogs.com/gk0d/p/16814704.html
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/518824.html
標籤:Java
下一篇:JAVA基本型別和包裝型別
