嚶嚶嚶嚶,方法被反射呼叫了

Harlber發表於2018-01-30

專案中有一個需求,需要檢測某些方法是否被合法呼叫。

說到合法呼叫,首先想到是是不是被反射給幹了。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:

stackoverflow.com/questions/4…

stackoverflow.com/questions/2…

相關文章