嘤嘤嘤嘤,方法被反射调用了

1,116 阅读2分钟

项目中有一个需求,需要检测某些方法是否被合法调用。

说到合法调用,首先想到是是不是被反射给干了。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…