反射还能这么玩?

来源:互联网 发布:电脑乐器演奏软件 编辑:程序博客网 时间:2024/04/29 05:18

其实本来想加个前缀,结果标题是《Android 中反射还能这么用?》,后来想想,也不恰当,就把Android去了,本身反射是Java的东西,你硬生生的加个Android显然是不恰当的。
这几天稍微过了一下Weex的源码,可谓是亲眼目睹了它的源码。无意间发现一个类,叫WXHack,搜索一下代码,发现在Weex里用的地方就一处,好奇心驱使下去看了WXHack的源码,好家伙!看完之后总觉得这个类似曾相识,后来昨天在看OpenAtlas的代码的时候又看到了这个类,相关链接如下

  • Hack.java
  • Interception.java

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

这里写图片描述

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

  • AtlasForAndroid

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

  • Hack.java
  • Interception.java

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

  • HackedField.java
  • HackedMethod0.java
  • HackedMethod1.java

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

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

在看他的源码之前,我们先来看看传统的反射是怎么玩的。
写了两三个类用来测试。

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

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,一般来说,第二种的使用场景会更广泛。

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

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

//构造函数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");

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

 private HashMap<String, Integer> scores = new HashMap<String, Integer>();

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

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

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

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

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对象,这个对象是对反射的包装类之一。其源码如下

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,这三个类的源码如下。自己看源码。。。。不解释了。。。太长了,主要是对反射的封装,并达到面向对象的效果。

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方法

//通过Class来获得一个HackedClassHack.HackedClass<Student> hackPersonByClass = Hack.into(Student.class);//输出class测试System.out.println(hackPersonByClass.getmClass());//通过Class的全类名获得一个HackedClassHack.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方法,将类型传入。

//通过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字符串的全类名。

//反射获得静态变量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

//泛型参数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。

//异常处理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);

最终的效果如下

这里写图片描述

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

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方法,在调用前和调用后做一些处理。比如我做一些日志输出。如下代码

//字段劫持处理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需要做一些操作,就可以这么来了。

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);}

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

4 1
原创粉丝点击