反射还能这么玩?

其实本来想加个前缀,结果标题是《Android 中反射还能这么用?》,后来想想,也不恰当,就把Android去了,本身反射是Java的东西,你硬生生的加个Android显然是不恰当的。

这几天稍微过了一下Weex的源码,可谓是亲眼目睹了它的源码。无意间发现一个类,叫WXHack,搜索一下代码,发现在Weex里用的地方就一处,好奇心驱使下去看了WXHack的源码,好家伙!看完之后总觉得这个类似曾相识,后来昨天在看OpenAtlas的代码的时候又看到了这个类,相关链接如下

后来我断定这个类应该是淘宝Atlas里的类,Weex直接抽出来了。为了验证自己的想法,下了个淘宝客户端反编译了下去找这个类,真的找到了。

想看源码的可以直接点上面的链接去看OpenAtals里的类,也可以看这个保持了淘宝的包结构,但是反编译并完全的项目,也就是包含了一些字节码的,但是这两个类应该是完整的。

那么携程的动态加载框架也算是有Atlas的成分了,自然也就有这两个类了,不出所料的找到了它们。

其实这个应该也不是淘专的专利,因为后来我在github上搜索了一番,发现了几个类似的类,这几个类在eclipse中竟然出现了。

那么,这两个类到底有什么作用呢,莫慌,容我慢慢道来。

首先,从名字上可以看出这是一个Hack类,它的作用就是辅助反射,但是它们却不是简简单单的辅助反射,而是反射后包装的形式:类,方法,字段 ,也就是HackedClass,HackedConstructor,HackedMethod,HackedField这几个反射的包装形式。并且还有AssertionFailureHandler用于处理反射发生异常时的处理,你可以选择扔出异常或者自己处理掉。

在看他的源码之前,我们先来看看传统的反射是怎么玩的。

写了两三个类用来测试。

Student实体类,内部有一个IBehavior类,代表学生的行为;此外还有静态变量,非静态变量,静态方法,非静态方法,泛型变量,作用就是等下测试不同场景的反射。

 scores = new HashMap();

    private IBehavior behavior = new BehaviorImpl();

    public Student() {
    }

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public IBehavior getBehavior() {
        return behavior;
    }

    public void setBehavior(IBehavior behavior) {
        this.behavior = behavior;
    }

    public void addScore(String name, int score) {
        scores.put(name, score);
    }

    public HashMap getScores() {
        return scores;
    }

    public void setScores(HashMap scores) {
        this.scores = scores;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public static String getSchool() {
        return school;
    }

    public static void setSchool(String school) {
        Student.school = school;
    }

    public void say(String word) {
        System.out.println(word);
    }

    @Override
    public String toString() {
        return "Student{" +
                "school=" + school +
                ", age=" + age +
                ", name=‘" + name + ‘\‘‘ +
                ", scores=" + scores +
                ", behavior=" + behavior +
                ‘}‘;
    }
}
" data-snippet-id="ext.ca8f931585b84a0209fec749c2b6697e" data-snippet-saved="false" data-codota-status="done">public class Student implements Serializable {
    private static String school = "清华大学";
    private String name;
    private int age;
    private HashMap<String, Integer> scores = new HashMap<String, Integer>();

    private IBehavior behavior = new BehaviorImpl();

    public Student() {
    }

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public IBehavior getBehavior() {
        return behavior;
    }

    public void setBehavior(IBehavior behavior) {
        this.behavior = behavior;
    }

    public void addScore(String name, int score) {
        scores.put(name, score);
    }

    public HashMap<String, Integer> getScores() {
        return scores;
    }

    public void setScores(HashMap<String, Integer> scores) {
        this.scores = scores;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public static String getSchool() {
        return school;
    }

    public static void setSchool(String school) {
        Student.school = school;
    }

    public void say(String word) {
        System.out.println(word);
    }

    @Override
    public String toString() {
        return "Student{" +
                "school=" + school +
                ", age=" + age +
                ", name=‘" + name + ‘\‘‘ +
                ", scores=" + scores +
                ", behavior=" + behavior +
                ‘}‘;
    }
}

IBehavior接口以及其实现类的代码如下

public interface IBehavior {
    void perform(String behaiviorName, String behaiviorContent);
}
public class BehaviorImpl implements IBehavior {
    @Override
    public void perform(String behaiviorName, String behaiviorContent) {
        System.out.println("behaiviorName:" + behaiviorName);
        System.out.println("behaiviorContent:" + behaiviorContent);
    }
}

你要反射,就先得拿到Class对象,两种方式,一直是Class在你的项目中,可以直接引用,第二种就是这个Class可能你不能直接引用,比如Android中的ContextImpl,一般来说,第二种的使用场景会更广泛。

 studentClassByClass = Student.class;

//通过class的字符串全类名
Class studentClassByName = Class.forName("cn.edu.zafu.Student");" data-snippet-id="ext.526205ef6aea7f48882bccbd81825bcb" data-snippet-saved="false" data-codota-status="done">//直接通过class
Class<?> studentClassByClass = Student.class;

//通过class的字符串全类名
Class<?> studentClassByName = Class.forName("cn.edu.zafu.Student");

然后你可以直接调用class的newInstance方法新建一个对象,但是对于没有默认构造函数的类来说,你需要拿到构造函数再去新建对象。这里需要注意的是你不知道修饰符是private,protected,public中的哪一个时,建议调用setAccessible设置成true,不然极有可能会发生异常。

 constructor = studentClassByName.getConstructor(String.class, int.class);
constructor.setAccessible(true);
Student student = (Student) constructor.newInstance("区长", 22);
System.out.println(student);" data-snippet-id="ext.4fa1e103a743a0644b71ec9c05ae22d3" data-snippet-saved="false" data-codota-status="done">//构造函数new对象
Constructor<?> constructor = studentClassByName.getConstructor(String.class, int.class);
constructor.setAccessible(true);
Student student = (Student) constructor.newInstance("区长", 22);
System.out.println(student);

对于静态变量的访问,可以拿到Field,直接设置即可。set方法的第一个参数传null就是了。

//静态变量
Field schoolField = studentClassByName.getDeclaredField("school");
schoolField.setAccessible(true);
schoolField.set(null, "浙江大学");
System.out.println(Student.getSchool());

但是对于非静态变量,set方法的第一个参数你就需要传递一个实例进去,比如上面通过构造函数new出来的对象。

//非静态变量
Field ageFiled = studentClassByName.getDeclaredField("age");
ageFiled.setAccessible(true);
Integer age = (Integer) ageFiled.get(student);
System.out.println(age);

ageFiled.set(student, 100);
System.out.println(student.getAge());

同理静态方法同静态变量

//静态方法调用
Method setSchoolMethod = studentClassByName.getDeclaredMethod("setSchool", String.class);
setSchoolMethod.invoke(null, "清华大学");
System.out.println(Student.getSchool());

非静态方法同非静态变量

//非静态方法调用
Method sayMethod = studentClassByName.getDeclaredMethod("say", String.class);
sayMethod.setAccessible(true);
sayMethod.invoke(student, "hello world");

等等,还有一个泛型变量呢

 scores = new HashMap();" data-snippet-id="ext.bedc64eb9121fe1ebb84b3c5cc452777" data-snippet-saved="false" data-codota-status="done"> private HashMap<String, Integer> scores = new HashMap<String, Integer>();

懵逼了,不会写。。。。。其实还是一样,强转就ok了,只不过强转前需要对类型进行校验。

然后就是异常处理,反射大多数人都是通过try catch直接捕捉异常。。。这也没什么好说的。

劫持,什么叫劫持呢,其实就是替换字段,有人说你直接反射拿到变量,直接反射替换就是了,这是一种方式,但是这并不是我想要的,比如我要劫持Student里的behavior变量,但是我不想修改它,我想保留它原来的逻辑,但是我又想加入新的东西,这个就有点类型面向切面编程了,比如日志的注入。怎么做?懵逼了。。。。显然是动态代理啊。使用代理类去完成劫持操作,既可以保留原有操作,又可以增加新的逻辑。

那么Hack类是怎么做的呢。首先生成HackedClass类,怎么生成的呢,调用Hack的into方法,入参有两种形式,一种就是直接传Class,另一种就是传递Class的全类名字符串。

 HackedClass into(final Class clazz) {
    return new HackedClass(clazz);
}

@SuppressWarnings({"rawtypes", "unchecked"})
public static  HackedClass into(final String class_name) throws HackDeclaration.HackAssertionException {
    try {
        return new HackedClass(Class.forName(class_name));
    } catch (final ClassNotFoundException e) {
        fail(new HackDeclaration.HackAssertionException(e));
        return new HackedClass(null);    // TODO: Better solution to avoid null?
    }
}" data-snippet-id="ext.5e91ff262e0e464ad898711f8fe92360" data-snippet-saved="false" data-codota-status="done">public static <T> HackedClass<T> into(final Class<T> clazz) {
    return new HackedClass<T>(clazz);
}

@SuppressWarnings({"rawtypes", "unchecked"})
public static <T> HackedClass<T> into(final String class_name) throws HackDeclaration.HackAssertionException {
    try {
        return new HackedClass(Class.forName(class_name));
    } catch (final ClassNotFoundException e) {
        fail(new HackDeclaration.HackAssertionException(e));
        return new HackedClass(null);    // TODO: Better solution to avoid null?
    }
}

into方法返回的是HackedClass对象,这个对象是对反射的包装类之一。其源码如下

 {

    protected Class mClass;

    public HackedClass(final Class clazz) {
        mClass = clazz;
    }

    public HackedField staticField(final String name) throws HackDeclaration.HackAssertionException {
        return new HackedField(mClass, name, Modifier.STATIC);
    }

    public HackedField field(final String name) throws HackDeclaration.HackAssertionException {
        return new HackedField(mClass, name, 0);
    }

    public HackedMethod staticMethod(final String name, final Class... arg_types) throws HackDeclaration.HackAssertionException {
        return new HackedMethod(mClass, name, arg_types, Modifier.STATIC);
    }

    public HackedMethod method(final String name, final Class... arg_types) throws HackDeclaration.HackAssertionException {
        return new HackedMethod(mClass, name, arg_types, 0);
    }

    public HackedConstructor constructor(final Class... arg_types) throws HackDeclaration.HackAssertionException {
        return new HackedConstructor(mClass, arg_types);
    }

    public Class getmClass() {
        return mClass;
    }
}   " data-snippet-id="ext.64cee6afb187f4390fafb782427d0abb" data-snippet-saved="false" data-codota-status="done">public static class HackedClass<C> {

    protected Class<C> mClass;

    public HackedClass(final Class<C> clazz) {
        mClass = clazz;
    }

    public HackedField<C, Object> staticField(final String name) throws HackDeclaration.HackAssertionException {
        return new HackedField<C, Object>(mClass, name, Modifier.STATIC);
    }

    public HackedField<C, Object> field(final String name) throws HackDeclaration.HackAssertionException {
        return new HackedField<C, Object>(mClass, name, 0);
    }

    public HackedMethod staticMethod(final String name, final Class<?>... arg_types) throws HackDeclaration.HackAssertionException {
        return new HackedMethod(mClass, name, arg_types, Modifier.STATIC);
    }

    public HackedMethod method(final String name, final Class<?>... arg_types) throws HackDeclaration.HackAssertionException {
        return new HackedMethod(mClass, name, arg_types, 0);
    }

    public HackedConstructor constructor(final Class<?>... arg_types) throws HackDeclaration.HackAssertionException {
        return new HackedConstructor(mClass, arg_types);
    }

    public Class<C> getmClass() {
        return mClass;
    }
}   

如果你要获得静态方法,就调用staticMethod,静态变量就调用staticField,非静态方法就调用method,非静态变量就调用field,要获得构造函数就调用constructor,当然你也可以调用getmClass获得Class。最终如果你调用方法相关的函数会得到HackedMethod,调用变量相关的会得到HackedField,调用构造函数相关的会获得HackedConstructor,这三个类的源码如下。自己看源码。。。。不解释了。。。太长了,主要是对反射的封装,并达到面向对象的效果。

 {

    private final Field mField;

    HackedField(final Class clazz, final String name, int modifiers) throws HackDeclaration.HackAssertionException {
        Field field = null;
        try {
            if (clazz == null) {
                return;
            }
            field = clazz.getDeclaredField(name);
            if (modifiers > 0 && (field.getModifiers() & modifiers) != modifiers) {
                fail(new HackDeclaration.HackAssertionException(field + " does not match modifiers: " + modifiers));
            }
            field.setAccessible(true);
        } catch (final NoSuchFieldException e) {
            HackDeclaration.HackAssertionException hae = new HackDeclaration.HackAssertionException(e);
            hae.setHackedClass(clazz);
            hae.setHackedFieldName(name);
            fail(hae);
        } finally {
            mField = field;
        }
    }

    @SuppressWarnings("unchecked")
    public  HackedField ofGenericType(final Class type) throws HackDeclaration.HackAssertionException {
        if (mField != null && !type.isAssignableFrom(mField.getType())) {
            fail(new HackDeclaration.HackAssertionException(new ClassCastException(mField + " is not of type " + type)));
        }
        return (HackedField) this;
    }

    @SuppressWarnings("unchecked")
    public HackedField ofType(final String type_name) throws HackDeclaration.HackAssertionException {
        try {
            return (HackedField) ofType(Class.forName(type_name));
        } catch (final ClassNotFoundException e) {
            fail(new HackDeclaration.HackAssertionException(e));
            return this;
        }
    }

    @SuppressWarnings("unchecked")
    public  HackedField ofType(final Class type) throws HackDeclaration.HackAssertionException {
        if (mField != null && !type.isAssignableFrom(mField.getType())) {
            fail(new HackDeclaration.HackAssertionException(new ClassCastException(mField + " is not of type " + type)));
        }
        return (HackedField) this;
    }

    public void hijack(final C instance, final Interception.InterceptionHandler handler) {
        final T delegatee = get(instance);
        if (delegatee == null) {
            throw new IllegalStateException("Cannot hijack null");
        }
        final Class[] interfaces = delegatee.getClass().getInterfaces();
        set(instance, Interception.proxy(delegatee, handler, interfaces));
    }

    public T get(final C instance) {
        try {
            @SuppressWarnings("unchecked") final T value = (T) mField.get(instance);
            return value;
        } catch (final IllegalAccessException e) {
            e.printStackTrace();
            return null;
        }
    }

    public void set(final C instance, final Object value) {
        try {
            mField.set(instance, value);
        } catch (final IllegalAccessException e) {
            e.printStackTrace();

        }
    }

    public Field getField() {
        return mField;
    }
}

public static class HackedMethod {

    protected final Method mMethod;

    HackedMethod(final Class clazz, final String name, final Class[] arg_types, int modifiers) throws HackDeclaration.HackAssertionException {
        Method method = null;
        try {
            if (clazz == null) {
                return;
            }
            method = clazz.getDeclaredMethod(name, arg_types);
            if (modifiers > 0 && (method.getModifiers() & modifiers) != modifiers) {
                fail(new HackDeclaration.HackAssertionException(method + " does not match modifiers: " + modifiers));
            }
            method.setAccessible(true);
        } catch (final NoSuchMethodException e) {
            HackDeclaration.HackAssertionException hae = new HackDeclaration.HackAssertionException(e);
            hae.setHackedClass(clazz);
            hae.setHackedMethodName(name);
            fail(hae);
        } finally {
            mMethod = method;
        }
    }

    public Object invoke(final Object receiver, final Object... args) throws IllegalArgumentException, InvocationTargetException {
        Object obj = null;
        try {
            obj = mMethod.invoke(receiver, args);
            return obj;
        } catch (final IllegalAccessException e) { /* Should never happen */
            e.printStackTrace();
        }
        return obj;
    }

    public Method getMethod() {
        return mMethod;
    }
}

public static class HackedConstructor {

    protected Constructor mConstructor;

    HackedConstructor(final Class clazz, final Class[] arg_types) throws HackDeclaration.HackAssertionException {
        try {
            if (clazz == null) {
                return;
            }
            mConstructor = clazz.getDeclaredConstructor(arg_types);
        } catch (NoSuchMethodException e) {
            HackDeclaration.HackAssertionException hae = new HackDeclaration.HackAssertionException(e);
            hae.setHackedClass(clazz);
            fail(hae);
        }
    }

    public Object getInstance(final Object... arg_types) throws IllegalArgumentException {
        Object obj = null;
        mConstructor.setAccessible(true);
        try {
            obj = mConstructor.newInstance(arg_types);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return obj;
    }
}" data-snippet-id="ext.724d65d09729669340a38a5c5049e8cf" data-snippet-saved="false" data-codota-status="done">
public static class HackedField<C, T> {

    private final Field mField;

    HackedField(final Class<C> clazz, final String name, int modifiers) throws HackDeclaration.HackAssertionException {
        Field field = null;
        try {
            if (clazz == null) {
                return;
            }
            field = clazz.getDeclaredField(name);
            if (modifiers > 0 && (field.getModifiers() & modifiers) != modifiers) {
                fail(new HackDeclaration.HackAssertionException(field + " does not match modifiers: " + modifiers));
            }
            field.setAccessible(true);
        } catch (final NoSuchFieldException e) {
            HackDeclaration.HackAssertionException hae = new HackDeclaration.HackAssertionException(e);
            hae.setHackedClass(clazz);
            hae.setHackedFieldName(name);
            fail(hae);
        } finally {
            mField = field;
        }
    }

    @SuppressWarnings("unchecked")
    public <T2> HackedField<C, T2> ofGenericType(final Class<?> type) throws HackDeclaration.HackAssertionException {
        if (mField != null && !type.isAssignableFrom(mField.getType())) {
            fail(new HackDeclaration.HackAssertionException(new ClassCastException(mField + " is not of type " + type)));
        }
        return (HackedField<C, T2>) this;
    }

    @SuppressWarnings("unchecked")
    public HackedField<C, T> ofType(final String type_name) throws HackDeclaration.HackAssertionException {
        try {
            return (HackedField<C, T>) ofType(Class.forName(type_name));
        } catch (final ClassNotFoundException e) {
            fail(new HackDeclaration.HackAssertionException(e));
            return this;
        }
    }

    @SuppressWarnings("unchecked")
    public <T2> HackedField<C, T2> ofType(final Class<T2> type) throws HackDeclaration.HackAssertionException {
        if (mField != null && !type.isAssignableFrom(mField.getType())) {
            fail(new HackDeclaration.HackAssertionException(new ClassCastException(mField + " is not of type " + type)));
        }
        return (HackedField<C, T2>) this;
    }

    public void hijack(final C instance, final Interception.InterceptionHandler<?> handler) {
        final T delegatee = get(instance);
        if (delegatee == null) {
            throw new IllegalStateException("Cannot hijack null");
        }
        final Class<?>[] interfaces = delegatee.getClass().getInterfaces();
        set(instance, Interception.proxy(delegatee, handler, interfaces));
    }

    public T get(final C instance) {
        try {
            @SuppressWarnings("unchecked") final T value = (T) mField.get(instance);
            return value;
        } catch (final IllegalAccessException e) {
            e.printStackTrace();
            return null;
        }
    }

    public void set(final C instance, final Object value) {
        try {
            mField.set(instance, value);
        } catch (final IllegalAccessException e) {
            e.printStackTrace();

        }
    }

    public Field getField() {
        return mField;
    }
}

public static class HackedMethod {

    protected final Method mMethod;

    HackedMethod(final Class<?> clazz, final String name, final Class<?>[] arg_types, int modifiers) throws HackDeclaration.HackAssertionException {
        Method method = null;
        try {
            if (clazz == null) {
                return;
            }
            method = clazz.getDeclaredMethod(name, arg_types);
            if (modifiers > 0 && (method.getModifiers() & modifiers) != modifiers) {
                fail(new HackDeclaration.HackAssertionException(method + " does not match modifiers: " + modifiers));
            }
            method.setAccessible(true);
        } catch (final NoSuchMethodException e) {
            HackDeclaration.HackAssertionException hae = new HackDeclaration.HackAssertionException(e);
            hae.setHackedClass(clazz);
            hae.setHackedMethodName(name);
            fail(hae);
        } finally {
            mMethod = method;
        }
    }

    public Object invoke(final Object receiver, final Object... args) throws IllegalArgumentException, InvocationTargetException {
        Object obj = null;
        try {
            obj = mMethod.invoke(receiver, args);
            return obj;
        } catch (final IllegalAccessException e) { /* Should never happen */
            e.printStackTrace();
        }
        return obj;
    }

    public Method getMethod() {
        return mMethod;
    }
}

public static class HackedConstructor {

    protected Constructor<?> mConstructor;

    HackedConstructor(final Class<?> clazz, final Class<?>[] arg_types) throws HackDeclaration.HackAssertionException {
        try {
            if (clazz == null) {
                return;
            }
            mConstructor = clazz.getDeclaredConstructor(arg_types);
        } catch (NoSuchMethodException e) {
            HackDeclaration.HackAssertionException hae = new HackDeclaration.HackAssertionException(e);
            hae.setHackedClass(clazz);
            fail(hae);
        }
    }

    public Object getInstance(final Object... arg_types) throws IllegalArgumentException {
        Object obj = null;
        mConstructor.setAccessible(true);
        try {
            obj = mConstructor.newInstance(arg_types);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return obj;
    }
}

注意HackedField中有个方法叫hijack,这个方法的作用就是劫持。

最终,如果反射失败的话会进入Hack的fail方法处理,我们可以设置AssertionFailureHandler处理器,返回false会扔出异常,否则不会扔出异常。可以在里面做一些事,比如埋点。这样就可以集中到一个地方处理了。


private static void fail(HackDeclaration.HackAssertionException e) throws HackDeclaration.HackAssertionException {
    if (sFailureHandler == null || !sFailureHandler.onAssertionFailure(e)) {
        throw e;
    }
}

public static void setAssertionFailureHandler(AssertionFailureHandler handler) {
    sFailureHandler = handler;
}

public interface AssertionFailureHandler {
    boolean onAssertionFailure(HackDeclaration.HackAssertionException failure);
}

瞎说了这么多,来实践一把。

获得HackedClass对象,调用Hack.into方法

 hackPersonByClass = Hack.into(Student.class);
//输出class测试
System.out.println(hackPersonByClass.getmClass());
//通过Class的全类名获得一个HackedClass
Hack.HackedClass hackPersonByName = Hack.into("cn.edu.zafu.Student");
//输出class测试
System.out.println(hackPersonByName.getmClass());" data-snippet-id="ext.a40a5f5553d8573e25bdf902acd5e58f" data-snippet-saved="false" data-codota-status="done">//通过Class来获得一个HackedClass
Hack.HackedClass<Student> hackPersonByClass = Hack.into(Student.class);
//输出class测试
System.out.println(hackPersonByClass.getmClass());
//通过Class的全类名获得一个HackedClass
Hack.HackedClass<Student> hackPersonByName = Hack.into("cn.edu.zafu.Student");
//输出class测试
System.out.println(hackPersonByName.getmClass());

创建实例,调用HackedClass的constructor方法,将参数的类型传入,然后调用getInstance方法就可以获得一个实例了。

//获得构造函数
Hack.HackedConstructor personConstructor = hackPersonByName.constructor(String.class, int.class);
//创建并获得实例
Student student = (Student) personConstructor.getInstance("区长", 121);
//输出结果测试
System.out.println(student);
//直接调用反射获得的对象的方法
student.say("世界你好,世界再见");

非静态变量需要通过field方法获得一个HackedField对象,这时候你获得的field是一个Object类型的,因此如果你想要类型安全,需要调用一下ofType方法,将类型传入。

 ofType 获得一个HackedField,非静态
Hack.HackedField hackName = hackPersonByName.field("name").ofType(String.class);
//反射调用
String name = hackName.get(student);
//输出结果测试
System.out.println(name);

//通过field -> ofType 获得一个HackedField,非静态
Hack.HackedField hackAge = hackPersonByName.field("age").ofType(int.class);
//反射设置属性
hackAge.set(student, 16);
//反射获得age,进行验证
Integer age = hackPersonByName.field("age").ofType(int.class).get(student);
//输出结果测试
System.out.println(age);" data-snippet-id="ext.fa9eb43902e7e86f69da14bbbf3a323e" data-snippet-saved="false" data-codota-status="done">//通过field -> ofType 获得一个HackedField,非静态
Hack.HackedField<Student, String> hackName = hackPersonByName.field("name").ofType(String.class);
//反射调用
String name = hackName.get(student);
//输出结果测试
System.out.println(name);

//通过field -> ofType 获得一个HackedField,非静态
Hack.HackedField<Student, Integer> hackAge = hackPersonByName.field("age").ofType(int.class);
//反射设置属性
hackAge.set(student, 16);
//反射获得age,进行验证
Integer age = hackPersonByName.field("age").ofType(int.class).get(student);
//输出结果测试
System.out.println(age);

静态变量直接调用staticField方法即可,除了上面的ofType将类型传入,这个类型可以是Class对象,也可以是Class字符串的全类名。

 hackSchool = hackPersonByName.staticField("school").ofType("java.lang.String");

//获得静态变量值
String sSchool = (String) hackSchool.get(null);
//输出结果测试
System.out.println(sSchool);

//设置值
hackSchool.getField().set(null, "北京大学");
//获得值验证是否设置成功,通过getField()方式
sSchool = (String) hackSchool.getField().get(null);
//输出结果测试
System.out.println(sSchool);" data-snippet-id="ext.840950dfd42bf0f51202441c6563cc12" data-snippet-saved="false" data-codota-status="done">
//反射获得静态变量
Hack.HackedField<Student, Object> hackSchool = hackPersonByName.staticField("school").ofType("java.lang.String");

//获得静态变量值
String sSchool = (String) hackSchool.get(null);
//输出结果测试
System.out.println(sSchool);

//设置值
hackSchool.getField().set(null, "北京大学");
//获得值验证是否设置成功,通过getField()方式
sSchool = (String) hackSchool.getField().get(null);
//输出结果测试
System.out.println(sSchool);

泛型参数可以调用ofGenericType方法转为泛型,比如下面的Map

> hackScores = hackPersonByName.field("scores").ofGenericType(Map.class);
Map stringIntegerMap = hackScores.get(student);
stringIntegerMap.put("语文", 80);
stringIntegerMap.put("数学", 90);
//泛型参数设置
hackScores.set(student, stringIntegerMap);
//输出结果测试
System.out.println(student.getScores());" data-snippet-id="ext.1ba30094a925872cd8b861cd077a907e" data-snippet-saved="false" data-codota-status="done">//泛型参数
Hack.HackedField<Student, Map<String, Integer>> hackScores = hackPersonByName.field("scores").ofGenericType(Map.class);
Map<String, Integer> stringIntegerMap = hackScores.get(student);
stringIntegerMap.put("语文", 80);
stringIntegerMap.put("数学", 90);
//泛型参数设置
hackScores.set(student, stringIntegerMap);
//输出结果测试
System.out.println(student.getScores());

方法的调用需要调用method相关的方法,非静态方法直接调用method方法获得HackedMethod对象,需要将方法的参数类型传入。之后直接invoke调用的时候将调用对象实例和参数传入。

//反射非静态方法调用
hackPersonByName.method("say", String.class).invoke(student, "fuck the source code");

静态方法和非静态方法相比就是invoke的时候调用对象可以直接传null。

//反射静态方法调用
hackPersonByName.staticMethod("setSchool", String.class).invoke(null, "南京大学");
//输出结果测试
System.out.println(Student.getSchool());
//反射静态方法调用
String school = (String) hackPersonByName.staticMethod("getSchool").getMethod().invoke(null);
System.out.println(school);

以上调用的最终输出如下图所示,代码可能跟图有出入(貌似修改过前后顺序)

Hack类里的异常处理最终都会走到fail方法中,fail方法中会判断AssertionFailureHandler是否为空,空的情况下会直接扔出异常,否则会看AssertionFailureHandler的处理结果,如果结果返回true,则不扔出异常,返回fasle的情况下也会扔出异常,我们通过setAssertionFailureHandler方法设置一个异常处理器就可以了。下面调用两个不存在的字段,让它进入到这个处理器中,如果字段名是notHandler则扔出异常,否则输出信息并返回true。

 hackedClass = failure.getHackedClass();
        String hackedFieldName = failure.getHackedFieldName();
        String hackedMethodName = failure.getHackedMethodName();
        System.out.println("=====onAssertionFailure start=====");
        System.out.println("hackedClass:" + hackedClass);
        System.out.println("hackedFieldName:" + hackedFieldName);
        System.out.println("hackedMethodName:" + hackedMethodName);
        System.out.println("=====onAssertionFailure end=====");
        //返回true不会抛出异常,否则抛出异常

        return true;
    }
});
//获得一个不存在的对象,验证onAssertionFailure回调
Hack.HackedField unknownField = hackPersonByName.field("unknownField").ofType(String.class);
Hack.HackedField notHandler = hackPersonByName.field("notHandler").ofType(String.class);
" data-snippet-id="ext.9ecd6bd84d1b21724eb64f71ae87101f" data-snippet-saved="false" data-codota-status="done">//异常处理
Hack.setAssertionFailureHandler(new Hack.AssertionFailureHandler() {
    @Override
    public boolean onAssertionFailure(Hack.HackDeclaration.HackAssertionException failure) {
        //如果是notHandler字段,则不处理,即扔出异常,否则打印输出,不扔出异常
        if ("notHandler".equals(failure.getHackedFieldName())) {
            return false;
        }

        Class<?> hackedClass = failure.getHackedClass();
        String hackedFieldName = failure.getHackedFieldName();
        String hackedMethodName = failure.getHackedMethodName();
        System.out.println("=====onAssertionFailure start=====");
        System.out.println("hackedClass:" + hackedClass);
        System.out.println("hackedFieldName:" + hackedFieldName);
        System.out.println("hackedMethodName:" + hackedMethodName);
        System.out.println("=====onAssertionFailure end=====");
        //返回true不会抛出异常,否则抛出异常

        return true;
    }
});
//获得一个不存在的对象,验证onAssertionFailure回调
Hack.HackedField<Student, String> unknownField = hackPersonByName.field("unknownField").ofType(String.class);
Hack.HackedField<Student, String> notHandler = hackPersonByName.field("notHandler").ofType(String.class);

最终的效果如下

劫持字段,字段的劫持就是通过动态代理来实现的,内部的逻辑如下

 implements
        InvocationHandler {
    private T mDelegatee;

    @Override
    public Object invoke(Object obj, Method method, Object[] args)
            throws Throwable {
        Object obj2 = null;
        try {
            obj2 = method.invoke(delegatee(), args);
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e2) {
            e2.printStackTrace();
        } catch (InvocationTargetException e3) {
            throw e3.getTargetException();
        }
        return obj2;
    }

    protected T delegatee() {
        return this.mDelegatee;
    }

    void setDelegatee(T t) {
        this.mDelegatee = t;
    }
}" data-snippet-id="ext.908e22327e1ea819f85ed07937a157d7" data-snippet-saved="false" data-codota-status="done">public static abstract class InterceptionHandler<T> implements
        InvocationHandler {
    private T mDelegatee;

    @Override
    public Object invoke(Object obj, Method method, Object[] args)
            throws Throwable {
        Object obj2 = null;
        try {
            obj2 = method.invoke(delegatee(), args);
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e2) {
            e2.printStackTrace();
        } catch (InvocationTargetException e3) {
            throw e3.getTargetException();
        }
        return obj2;
    }

    protected T delegatee() {
        return this.mDelegatee;
    }

    void setDelegatee(T t) {
        this.mDelegatee = t;
    }
}

这个字段的方法调用都是调用委托对象,这个对象就是我们原始的字段,这个delegatee方法我们可以重写返回新对象,这样就和反射直接替换没有什么差别了。当然也可以重写invoke方法,在调用前和调用后做一些处理。比如我做一些日志输出。如下代码

() {
    @Override
    public Object invoke(Object obj, Method method, Object[] args) throws Throwable {
        System.out.println("hijack:[invoke start]");

        Object o = super.invoke(obj, method, args);

        System.out.println("hijack:[invoke end]");
        return o;
    }
};
//劫持behavior字段
hackPersonByName.field("behavior").hijack(student, handler);
//测试劫持效果
student.getBehavior().perform("sleep", "sleep 10h");" data-snippet-id="ext.3710ef91fd9836945d5edf969058baa6" data-snippet-saved="false" data-codota-status="done">//字段劫持处理
Interception.InterceptionHandler handler = new Interception.InterceptionHandler<IBehavior>() {
    @Override
    public Object invoke(Object obj, Method method, Object[] args) throws Throwable {
        System.out.println("hijack:[invoke start]");

        Object o = super.invoke(obj, method, args);

        System.out.println("hijack:[invoke end]");
        return o;
    }
};
//劫持behavior字段
hackPersonByName.field("behavior").hijack(student, handler);
//测试劫持效果
student.getBehavior().perform("sleep", "sleep 10h");

最终效果如下

好了说了这么多,还是不明白这个东西有什么软用呢?Android动态加载资源的时候我们需要用到的ContextImpl对象,需要对AssetManager和Resources需要做一些操作,就可以这么来了。

 AssetManager;
    public static HackedMethod AssetManager_addAssetPath;
    public static HackedClass ContextImpl;
    public static HackedField ContextImpl_mResources;
    public static boolean sIsReflectChecked;
    public static boolean sIsReflectAvailable;

    public static boolean defineAndVerify() {
        if (sIsReflectChecked) {
            return sIsReflectAvailable;
        }
        Hacks hacks = new Hacks();
        try {
            Hack.setAssertionFailureHandler(hacks);
            allClasses();
            allConstructors();
            allFields();
            allMethods();
            sIsReflectAvailable = true;
            return sIsReflectAvailable;
        } catch (Throwable e) {
            e.printStackTrace();
        } finally {
            Hack.setAssertionFailureHandler(null);
            sIsReflectChecked = true;
        }
        return false;
    }

    private static void allClasses() throws HackAssertionException {
        AssetManager = Hack.into(AssetManager.class);
        ContextImpl = Hack.into("android.app.ContextImpl");
    }

    private static void allConstructors() throws HackAssertionException {

    }

    private static void allFields() throws HackAssertionException {

        ContextImpl_mResources = ContextImpl.field("mResources").ofType(
                Resources.class);
    }

    private static void allMethods() throws HackAssertionException {
        AssetManager_addAssetPath = AssetManager.method("addAssetPath", String.class);
    }

    @Override
    public boolean onAssertionFailure(HackAssertionException hackAssertionException) {
        //throw it
        return false;
    }
}" data-snippet-id="ext.761aabe2bd8c30242e974baa0a5b4f3f" data-snippet-saved="false" data-codota-status="done">

import java.lang.reflect.InvocationTargetException;

public class Hacks extends HackDeclaration implements
        AssertionFailureHandler {

    public static HackedClass<AssetManager> AssetManager;
    public static HackedMethod AssetManager_addAssetPath;
    public static HackedClass<Object> ContextImpl;
    public static HackedField<Object, Resources> ContextImpl_mResources;
    public static boolean sIsReflectChecked;
    public static boolean sIsReflectAvailable;

    public static boolean defineAndVerify() {
        if (sIsReflectChecked) {
            return sIsReflectAvailable;
        }
        Hacks hacks = new Hacks();
        try {
            Hack.setAssertionFailureHandler(hacks);
            allClasses();
            allConstructors();
            allFields();
            allMethods();
            sIsReflectAvailable = true;
            return sIsReflectAvailable;
        } catch (Throwable e) {
            e.printStackTrace();
        } finally {
            Hack.setAssertionFailureHandler(null);
            sIsReflectChecked = true;
        }
        return false;
    }

    private static void allClasses() throws HackAssertionException {
        AssetManager = Hack.into(AssetManager.class);
        ContextImpl = Hack.into("android.app.ContextImpl");
    }

    private static void allConstructors() throws HackAssertionException {

    }

    private static void allFields() throws HackAssertionException {

        ContextImpl_mResources = ContextImpl.field("mResources").ofType(
                Resources.class);
    }

    private static void allMethods() throws HackAssertionException {
        AssetManager_addAssetPath = AssetManager.method("addAssetPath", String.class);
    }

    @Override
    public boolean onAssertionFailure(HackAssertionException hackAssertionException) {
        //throw it
        return false;
    }
}

调用 Hacks.defineAndVerify反复进行定义和验证,成功后就可以直接调用了。

boolean flag = Hacks.defineAndVerify();

if (flag) {
    AssetManager assetManager = AssetManager.class.newInstance();
    Hacks.AssetManager_addAssetPath.invoke(assetManager,"assetPath");
    Hacks.ContextImpl_mResources.set(context, resources);
}

上面的这个例子是伪代码,实际情况自己去把握~~~~写的比较随意,看看就好了~

时间: 2024-10-27 18:18:05

反射还能这么玩?的相关文章

Android事件总线还能怎么玩?

作者简介:何红辉,Android工程师,现任职于友盟. 顾名思义,AndroidEventBus是一个Android平台的事件总线框架,它简化了Activity.Fragment.Service等组件之间的交互,很大程度上降低了它们之间的耦合,使我们的代码更加简洁,耦合性更低,提升了我们的代码质量.但它能做的却不仅限于这些.经过定制,它能完成很多有意思的功能,那么究竟该怎么做呢?就让我们一起往下看吧. 不堪回首的痛 首先,让我们先来看看这么一个场景:你是否在开发的过程中遇到过从Activity-

若病毒将智能玩具武器化,你还放心孩子玩吗?

智能化浪潮的逼近,在让大众享受到众多便利的同时,越来越多的负面问题也凸现出来.比如在玩具行业,传统的"呆板型"玩具似乎被逐渐淘汰,越来越多的玩具被加上联网功能,将智能化当成最大卖点.这些带有WiFi硬件.蓝牙.麦克风,甚至摄像头的智能玩具,以良好的人机互动性.多元玩法等受到孩子们的欢迎. 但正是因为智能玩具数量愈发庞大,其中蕴藏的相关数据成为了无尽宝藏,正被某些有不良居心的黑客给盯上.被黑客攻击后,智能玩具不仅会肆无忌惮地收集更多孩子.家长的相关数据,对隐私等造成极大困扰,还会变成一款

Word揭秘:公式还能这么玩!

如今办公室里用Word来处理资料文档一种再普遍不过的现象了,学校的老师出试卷也离不开它.用Word编辑公式也是一个非常的技巧,玩转Word的同时,你玩转公式了吗?想要在Word中编辑公式,可不是说说就能搞定的事情! MathType免费获取地址:http://www.mathtype.cn/ 各版本注册码获取地址:http://www.mathtype.cn/news/6-9xuliehao-zhucema-jihuoma.html 想要在Word中玩转公式?那你必然需要一个MathType公式

趣玩|OMG!原来眼妆还能这么玩!-淘惠多九块九包邮网(http://www.taohuiduo.com)

当美妆新手们还在握着眼线笔眼影刷战战兢兢地往眼睛上画,当眼妆达人们还在尝试各种渐变色以及做电视剧女猪脚仿妆时,以色列的Tal Peleg早已领先了若干身位,将眼皮当做画布,在眉毛与眼窝处画出一张又一张美丽的图案,令人惊叹. 精湛的工笔,神奇的技术,Tel Peleg就是如此令人惊奇.她不仅眼妆技艺神乎其神,而且本人长得也很漂酿. 如此惊艳而绚丽的孔雀眼妆,配上紫色的假发,或许是她搭配得太好了,这样的妆容看上去一点儿也不违和. 浏览Tel Peleg的facebook,可以发现,她的很多眼妆作品是

Win10还能这样玩!在开始菜单中置顶喜欢的应用

很多童鞋都希望打造简洁的桌面,不喜欢上面有太多的程序图标.问题来了,没有桌面快捷方式,怎么快速运行平时常用的软件? 小编今天就教大家一个Win10的小技巧,可以让你手动修改Win10开始菜单列表程序快捷方式的排序,让这里成为你快速启动常用软件的窗口. 首先建议大家进入设置→个性化→开始,关闭里面的最近常用.最近添加应用的勾选,省的它们浪费空间. 接下来需要进入开始菜单所在的文件夹. 按"Win键+R"进入运行窗口,输入并执行"shell:Common Start menu&q

商务统邀请框openZoosUrl还可以这么玩

网站商务通的openZoosUrl函数大家应该非常熟悉,它是商务通为我们提供的用来打开商务通对话窗口并可以传递一些信息的函数.最近仔细的研究了一下这个函数,发现了一些好玩的东西写出来与大家一起分享. 我们一般情况下使用openZoosUrl的形式有一下两种: <a href="javascript:openZoosUrl();" target="_self">点击咨询</a> <a href="javascript:openZ

原来还能这么玩

今天看了下系统,提了两个问题把自己逗乐了: 原来还能这么玩儿,乐了一上午--

可穿戴设备还能怎么玩?

根据市场研究公司Juniper近日发表的报告预测,2018年全球可穿戴设备出货量将达到1.3亿台,10倍于2013年,许多 业内人士无疑将可穿戴设备视为继智能手机之后的下一个互联网浪潮制高点.就在刚刚结束的CES年度大展上,从硬件创业公司Razer到索尼等大型消费电子 制造商,再到英特尔.爱普生等芯片商和耗材商,几乎所有与硬件沾边的公司都在谋划这个潜在市场.这种情况下,你不得不承认,"可穿戴设备"的热潮已经被点 燃了.   听上去很酷,这或许是当前可穿戴设备普及的最大障碍之一.普通消费

.Net中集合排序还可以这么玩

背景: public class StockQuantity { public StockQuantity(string status, DateTime dateTime, int quantity) { Status = status; DateTime = dateTime; Quantity = quantity; } public string Status { get; set; } public DateTime DateTime { get; set; } public int