專案中有一個需求,需要檢測某些方法是否被合法呼叫。
說到合法呼叫,首先想到是是不是被反射給幹了。emmm,眾所周知,反射常常比用來幹一些壞事。然鵝,現在需要倒行逆施
下,檢測給定的方法是否被反射呼叫了。
之前印象中看到過一篇文章,大致是可以通過方法中獲取呼叫堆疊分析(如果有哪位大佬看到了,請務必分享我?)
關鍵程式碼如下:
StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
複製程式碼
先準備幾個類,寫個Test看看stackTraceElements
中到底是什麼
public class Wishes {
private String greeting;
//驗證getGreeting 是否被反射呼叫
public String getGreeting() {
StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
for (int i=0;i<stackTraceElements.length;i++){
System.out.println(stackTraceElements[i].getMethodName() + " | "+ stackTraceElements[i].getClassName());
}
this.greeting="Good Afternoon!";
return greeting;
}
public void setGreeting(String greeting) {
this.greeting = greeting;
}
}
複製程式碼
public class ReflectTest {
@Test
public void invokeCalled(){
try {
Class cls=Wishes.class;
Method method1=cls.getDeclaredMethod("getGreeting");
String result1=(String) method1.invoke(cls.newInstance());
System.out.println(result1);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
@Test
public void noInvokeCalled(){
Wishes wishes = new Wishes();
wishes.getGreeting();
}
複製程式碼
run這2個test,記錄日誌輸出
public void invokeCalled()
getStackTrace | java.lang.Thread
getGreeting | com.caocaokeji.im.reflect.Wishes
invoke0 | sun.reflect.NativeMethodAccessorImpl
invoke | sun.reflect.NativeMethodAccessorImpl
invoke | sun.reflect.DelegatingMethodAccessorImpl
invoke | java.lang.reflect.Method
invokeCalled | com.caocaokeji.im.reflect.ReflectTest
invoke0 | sun.reflect.NativeMethodAccessorImpl
invoke | sun.reflect.NativeMethodAccessorImpl
invoke | sun.reflect.DelegatingMethodAccessorImpl
invoke | java.lang.reflect.Method
...
複製程式碼
public void noInvokeCalled()
getStackTrace | java.lang.Thread
getGreeting | com.caocaokeji.im.reflect.Wishes
noInvokeCalled | com.caocaokeji.im.reflect.ReflectTest
invoke0 | sun.reflect.NativeMethodAccessorImpl
invoke | sun.reflect.NativeMethodAccessorImpl
invoke | sun.reflect.DelegatingMethodAccessorImpl
invoke | java.lang.reflect.Method
...
複製程式碼
經多次測試發現,反射呼叫的堆疊中會存在4個連續invoke相關的method名稱,當直接反射呼叫getGreeting()
時,getGreeting後緊跟著為反射呼叫的堆疊資訊,而正常呼叫則會在之間夾雜我們呼叫getGreeting
所處的method作用域內。emmm,照這個規律,我們可以寫一個檢測類了。
/**
* Created by hana on 2018/1/30.
* 檢測方法是否被反射干了
*/
public class ReflectionDetector {
private static final String METHOD_NAME = "invoke";
private static final String CLASS_NAME = "java.lang.reflect.Method";
private static final int INVALID_INDEX = -1;
/**
* 判斷某方法是否被反射呼叫
*
* @param stackTraceElements An element in a stack trace
* @param method 需要檢測的方法名
* @param force 是否強制丟擲異常
*/
public static void wrapReflectInvoked(StackTraceElement[] stackTraceElements, String method, boolean force) {
if (isReflectInvoked(stackTraceElements, method) && force) {
throw new AssertionError("嚶嚶嚶嚶~被反射了");
}
}
/**
* 判斷某方法是否被反射呼叫
*
* @param stackTraceElements An element in a stack trace
* @param method 需要檢測的方法名
* @return true 被放射呼叫了
* false 未被反射呼叫
*/
public static boolean isReflectInvoked(StackTraceElement[] stackTraceElements, String method) {
if (stackTraceElements == null || stackTraceElements.length < 1) {
return false;
}
int indexInvoke = getFirstClassIndexFromStack(stackTraceElements);
int indexMethod = getFirstMethodIndexFromStack(stackTraceElements, method);
return !(indexInvoke == INVALID_INDEX || indexMethod == INVALID_INDEX) && indexInvoke < indexMethod + 5;
}
/**
* 獲取陣列中第一個給定方法名的位置
* <p>
* 方法堆疊第一處方法呼叫即為當前方法檢測處呼叫
* </p>
*
* @param index 堆疊資訊
* @param name 給定欄位
* @return INVALID_INDEX 未找到
*/
private static int getFirstMethodIndexFromStack(StackTraceElement[] index, String name) {
for (int i = 0; i < index.length; i++) {
if (index[i].getMethodName().equalsIgnoreCase(name)) {
return i;
}
}
return INVALID_INDEX;
}
/**
* 獲取堆疊中第一處呼叫反射的位置
*
* @param index 堆疊資訊
* @return INVALID_INDEX 未找到
*/
private static int getFirstClassIndexFromStack(StackTraceElement[] index) {
for (int i = 0; i < index.length; i++) {
if (index[i].getClassName().equalsIgnoreCase(CLASS_NAME)) {
if (index[i].getMethodName().equalsIgnoreCase(METHOD_NAME)) {
// method | class
// invoke java.lang.reflect.Method
return i;
}
}
}
return INVALID_INDEX;
}
}
複製程式碼
emmm,再修改下getGreeting方法測試下
public String getGreeting() {
StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
System.out.println("[invokeCalled] "+ReflectionDetector.isReflectInvoked(stackTraceElements,"getGreeting"));
ReflectionDetector.wrapReflectInvoked(stackTraceElements,"getGreeting",true);
this.greeting="Good Afternoon!";
return greeting;
}
複製程式碼
run相應的test
Refrence: