深入浅出-Java反射

文章目录

  • 前言
  • 一、反射是什么?
  • 二、反射如何使用
    • 1.常用API
      • 1)获取Class对象
      • 2)构造类对象
      • 3)获取类对象成员变量和方法
      • 4)调用方法
      • 5)动态代理
      • 6)获取泛型对象类型
    • 2.应用场景
  • 总结


前言

反射是java开发中比较常见和重要的一个知识,我们平时在开发中,可能会遇到,但相对其他知识没有那么频繁,曾几何时,我刚开始接触反射,查阅各种博客,大多数博客,无一例外,都是在介绍反射是获取运行中任意…,如何使用…,但是总觉得有点深奥,没有真正的让我们去理解和灵活运用,所以我想用这篇文章更加直白的去介绍这个知识,让大家轻松掌握这个知识。


一、反射是什么?

反射带个“反”字,相对而言就是“正”,“正”是什么呢,一般情况下,我们使用某个类,需要知道它是干什么的,实例化这个类,然后使用它,比如:

    Person person = new Person(); // 实例化person对象
    person.name = "张三"; // 赋值 name = 张三
    person.age = 24; // 赋值 age = 24

    person.work();// 调用work方法

像这样通过new 对象,创建实例,然后再使用的方式,是我们一般的操作方式,称之为“正”,而反射我们没法一开始就知道要初始化类的对象是什么,没法通过new的方式,创建实例,我们需要使用JDK提供的反射API进行反射调用:

   Class clz = Class.forName("com.example.lib.Person");
   Object obj = clz.newInstance();
   // 获取name成员变量
   Field nameField = clz.getField("name");
   // 设置name
   nameField.set(obj,"张三");
   // 获取age成员变量
   Field ageField = clz.getField("age");
   // 设置age
   ageField.setInt(obj,24);
   // 调用work方法
   Method method = clz.getMethod("work");
   method.invoke(obj);

上述两段代码,执行的效果其实是一样的,方式一在运行前就已经确定了运行的类(person),方式二运行时才知道运行的类,通过字符串值(com.example.lib.Person)获取运行的类。
so,反射是什么。

反射是在运行时才知道要操作的类是什么,并且可以在运行时获取到类的完整构造,并使用对应的方法和属性。(引自大白话说Java反射:入门、使用、原理)
反射发生在运行期,对应的还有编译期,下面给一张图体会一下他们的区别:
深入浅出-Java反射_第1张图片
由上图可以看出,编译期主要是将Java源码转换成字节码,运行期主要是处理器产生的代码并加载、执行。

二、反射如何使用

前面我们大概描述了一下,反射是什么东西,下面我们来介绍一下,如何使用反射,在这之前,我想先介绍一下,反射提供了那些功能给我们:

  • 判定任意对象所属类
  • 构造任意一个类的对象
  • 获取任意一个类的成员变量和方法
  • 调用任意一个对象的方法
  • 生成动态代理

1.常用API

1)获取Class对象

        // 第一种getClass方法获取
        Person person = new Person();
        Class<?> personClass = person.getClass();
        // 第二种类名.class获取
        personClass = Person.class;
        // 第三种Class.forName(类全路径名)获取
        try {
            personClass = Class.forName("com.example.lib.Person");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

2)构造类对象

 // 创建类对象
        Class clz = Person.class;
        try {
            // 第一种通过class newInstance()方法
            Person person1 = (Person) clz.newInstance();
        } catch (InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
        }
        // 第二种通过Constructor newInstance()方法
        try {
            Constructor constructor = clz.getConstructor(); // 不带参数
            Person person2 = (Person) constructor.newInstance();
            Constructor constructor2 = clz.getConstructor(String.class, int.class); // 带参数
            Person person3 = (Person) constructor2.newInstance("张三", 18);
        } catch (NoSuchMethodException | InstantiationException
                | IllegalAccessException | InvocationTargetException e) {
            e.printStackTrace();
        }

3)获取类对象成员变量和方法

        Class class1 = Person.class;
        Field[] allFields = class1.getDeclaredFields(); // 获取class对象的所有属性
        Field[] publicFields = class1.getFields(); // 获取class对象的public属性
        try {
            Field ageField = class1.getDeclaredField("age"); // 获取class指定属性
            Field idField = class1.getField("name"); // 获取class指定的public属性
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
        Method[] methods = class1.getDeclaredMethods(); // 获取class对象的所有声明方法
        Method[] allMethods = class1.getMethods(); // 获取class对象的所有方法 包括父类的方法

4)调用方法

            Class clz = Class.forName("com.example.lib.Person");
            Object obj = clz.newInstance();
            // 调用work方法
            Method method = clz.getMethod("work");
            method.invoke(obj);

5)动态代理

动态代理一般是为了给方法添加预处理或者添加后续操作或者修改、拦截某些方法,不影响原有业务逻辑,这里我们主要关注两个东西,一个是InvocationHandler(接口),一个是Proxy(代理类)。
下面介绍一个Android的例子(主要是hook 剪切板,我们每次剪切后的文字,都是我们事先设置的字符串),代码如下:

1.创建一个代理类,实现InvocationHandler接口,传入系统类,在invoke方法里面,通过方法名拦截对应方法。

// 代理类
public class MyClipProxy implements InvocationHandler {
    private final IBinder mBase;

    public MyClipProxy(IBinder mBase) {
        this.mBase = mBase; // 传入系统代理类
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 拦截系统原有的方法
        if("queryLocalInterface".equals(method.getName())){

            Class<?> mStubClass = Class.forName("android.content.IClipboard$Stub");
            Class<?> mStubClipboard = Class.forName("android.content.IClipboard");
            return Proxy.newProxyInstance(mStubClass.getClassLoader(),new Class[]{mStubClipboard},
                    new MyClip(mBase,mStubClass));

        }

        // 不是这个方法按原来系统执行
        return method.invoke(mBase,args);
    }
}

2.对于系统获取剪切板文字方法进行修改,达到无论如何剪切获取的都是“hook系统源码,哈哈哈。。。”

public class MyClip implements InvocationHandler {
    private Object mBase;

    public MyClip(IBinder base, Class stub) {
        try {
            Method asInterface = null;
            asInterface = stub.getDeclaredMethod("asInterface", IBinder.class);
            mBase = asInterface.invoke(null, base);

        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }


    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {


        // 这里我们拦截粘贴的方法,
        if ("getPrimaryClip".equals(method.getName())) {
            return ClipData.newPlainText(null, "hook系统源码,哈哈哈。。。");
        }
        // 再拦截是否有复制的方法,放系统认为一直都有
        if ("hasPrimaryClip".equals(method.getName())) {
            return true;
        }
        // 其他启动还是返回原有的
        return method.invoke(mBase, args);

    }
}

3.通过反射获取剪切板服务代理对象,修改对应的成员变量,让系统每次拿到的数据都是我们通过反射修改过的数据

/**
 * hook系统剪切板服务
 */
public class ClipHelper {


    public static void binder() {

        try {
            // 反射方式获取ServiceManager对象
            Class<?> serviceManagerClass = Class.forName("android.os.ServiceManager");
            // 拿到getService方法
            Method getServiceMethod = serviceManagerClass.getDeclaredMethod("getService", String.class);
            // 通过方法拿到系统服务代理对象
            IBinder binder = (IBinder) getServiceMethod.invoke(null, "clipboard");
            // 创建自己的代理对象
            IBinder myBinder = (IBinder) Proxy.newProxyInstance(serviceManagerClass.getClassLoader(),
                    new Class[]{IBinder.class}, new MyClipProxy(binder));
            // 拿到ServiceManager的数组
            Field field = serviceManagerClass.getDeclaredField("sCache");
            field.setAccessible(true);
            Map<String, IBinder> map = (Map<String, IBinder>) field.get(null);
            // 将服务存入map
            map.put("clipboard", myBinder);

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }


    }


}

4.需要的执行的地方,调用ClipHelper.binder()

                try {
                    ClipHelper.binder();
                } catch (Exception e) {
                    e.printStackTrace();
                    Log.e("ddup", "clip hook error = " + e.getMessage());
                }

6)获取泛型对象类型

我们为了避免类型强转,会使用到泛型,通过反射我们可以还原泛型的原始类型,这就为我们比如传入指定类型,自动解析对象,提供了支持。

public class People<T> {
}

public  class Person extends People<String> {
}
Person person = new Person() ;
            Class<?> class1 = person.getClass();
            Type type = class1.getGenericSuperclass();// 获取class对象的直接超类的Type
            if(type instanceof ParameterizedType){
                Type[] actualTypeArguments = ((ParameterizedType) type).getActualTypeArguments();
                if (actualTypeArguments != null && actualTypeArguments.length > 0) {
                    Class<?> clzType =  (Class<?>) actualTypeArguments[0];
                    System.out.println("泛型类型是: "+clzType);
                }
            }else {
                System.out.println("获取泛型类型出错!");
            }

最后输出:

泛型类型是: class java.lang.String

上面的代码是获取类的泛型,还有获取方法参数中的实际类型,获取方法返回值泛型实际类型,这里不在说了,感兴趣可以看一下,这篇文章通过反射获得泛型实际类型。

2.应用场景

上面讲述了反射的常用API,其实里面已经涉及到了反射的应用场景,主要在以下几个方面:

  • 逆向代码,反编译、hook相关
  • EventBus、Retrofit和注解相关结合起来的框架
  • json解析如Gson,动态生成类代码

总结

反射是作用在运行期,可以动态加载类,实现动态代理,比较灵活,但是比较消耗性能,适合执行某些特定的操作,真正需要大量生成代码还是使用APT之类的技术较好。
关于反射,我了解也只有这么多了,其中的原理,还没有来得及研究。
纸上得来终觉浅,还是得我们一行行实际。

Thanks:

大白话说Java反射:入门、使用、原理
Java学习之反射机制及应用场景

创作不易,觉得不错的话,请点赞、转发、评论鼓励,谢谢。

你可能感兴趣的:(Java基础,反射,动态代理,java)