项目中有一个需求,需要检测某些方法是否被合法调用。
说到合法调用,首先想到是是不是被反射给干了。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: