反射机制

作者: 健身营养爱好者 | 来源:发表于2018-12-10 15:22 被阅读14次

    前言

    HI,欢迎来到裴智飞的《每周一博》。今天是十二月第二周,我给大家介绍一下反射的知识,为什么要介绍这些呢,因为插件化技术需要它作为基础。

    一. 反射机制

    JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法,对于任意一个对象,都能够调用它的任意一个方法和属性,这种在运行时动态的获取信息以及动态调用对象的方法的功能称为Java的反射机制。

    加载一个类的时候,jvm会去寻找Class文件并载入到内存中,在运行期间一个Class对象在内存里只有一个,反射就是在运行时从内存中解析Class对象,把属性和方法映射成一个个的Java对象,原理如图。

    反射就好比察看一个人的内脏器官,然后判断出这个人的健康状况,反射是Java作为一种动态语言的关键性质,利用它可以实现动态编译。

    二. 获取Class对象

    类运行时的类型信息就是用Class对象表示的,它包含了与类有关的信息。每一个类都有一个Class对象,每当编译一个新类就产生一个Class对象,基本类型 (boolean, byte, char, short, int, long, float, and double)有Class对象,数组有Class对象,就连关键字void也有Class对象(void.class)。

    Class对象对应着java.lang.Class类,它没有公共的构造方法,Class对象是在类加载的时候由Java虚拟机以及通过调用类加载器中的 defineClass 方法自动构造的,因此不能显式地声明一个Class对象。一个类被加载到内存并供我们使用需要经历如下三个阶段:

    • 加载:这是由类加载器(ClassLoader)执行的。通过一个类的全限定名来获取其定义的二进制字节流(Class字节码),将这个字节流所代表的静态存储结构转化为方法去的运行时数据接口,根据字节码在java堆中生成一个代表这个类的java.lang.Class对象。

    • 链接:在链接阶段将验证Class文件中的字节流包含的信息是否符合当前虚拟机的要求,为静态域分配存储空间并设置类变量的初始值,并且如果必需的话,将常量池中的符号引用转化为直接引用。

    • 初始化:到了此阶段,才真正开始执行类中定义的java程序代码。用于执行该类的静态初始器和静态初始块,如果该类有父类的话,则优先对其父类进行初始化。

    所有的类都是在对其第一次使用时,动态加载到JVM中的。当程序创建第一个对类的静态成员的引用时,就会加载这个类,使用new创建类对象的时候也会被当作对类的静态成员的引用。因此Java程序程序在它开始运行之前并非被完全加载,其各个类都是在必需时才加载的。

    在类加载阶段,类加载器首先检查这个类的Class对象是否已经被加载,如果尚未加载,默认的类加载器就会根据类的全限定名查找.class文件。在这个类的字节码被加载时,它们会接受验证,以确保其没有被破坏,并且不包含不良Java代码。一旦某个类的Class对象被载入内存,我们就可以它来创建这个类的所有对象。

    要想反射一个类,必须先要获取到该类的Class对象,可以通过三种方法获取Class对象。

    1. 通过Object类中的 getClass() 方法,这是有了对象时候调用的,但是有了对象其实没必要反射了,除非调用私有属性;
    Car car = new Car();
    Class<?> s = car.getClass();
    
    1. 每个类都有一个静态的属性class,可以直接通过该属性获得Class对象,这种情况是需要导入包的,依赖性太强;
    Class<?> s = Car.class;
    
    1. 通过Class.forName()方法完成,必须要指定类的全名含包名,适用于不知道类的情况,当然它会抛ClassNotFoundException异常,一般会把字符串写入配置文件中来实现模块解耦。
    Class<?> c = Class.forName("com.refelct.Car");
    

    还有个好处是通过Class.forName()会初始化静态块,而前两者不会。我们知道当一个类的静态块被调用的时候会进行首次加载,但如果一个字段被static final修饰,那么在调用这个字段的时候是不会对类进行初始化的。因为被static和final修饰的字段,在编译期就把结果放入了常量池中了,但是如果只是将一个域设置为static 或final的,还是会对类进行初始化的。

    Class的方法有很多,和反射的方法很类似,这里列举一些常用的。

    • forName:产生Class引用,forName立即就进行了初始化;
    • Object-getClass:获取Class对象的一个引用,返回表示该对象的实际类型的Class引用;
    • getName:取全限定的类名(包括包名);
    • getSimpleName:获取类名(不包括包名);
    • isInterface:判断Class对象是否是表示一个接口;
    • getInterfaces:返回Class对象数组,表示Class对象所引用的类所实现的所有接口;
    • getSupercalss:返回Class对象,表示Class对象所引用的类所继承的直接基类,应用该方法可在运行时发现一个对象完整的继承结构;
    • newInstance:返回一个Oject对象,是实现“虚拟构造器”的一种途径,使用该方法创建的类,必须带有无参的构造器;
    • getFields:获得某个类的所有的public字段,包括继承自父类的所有公共字段。 类似的还有getMethods和getConstructors;
    • getDeclaredFields:获得某个类的自己声明的字段,即包括public、private和proteced,默认不包括父类声明的任何字段。类似的还有getDeclaredMethods和getDeclaredConstructors;

    三. 反射的使用

    1. 反射的API主要涉及这么几个类:Constructor描述构造函数,Field描述变量,Method描述方法。我们一般用getDeclaredXXX来获取对应的类型,getDeclaredXXX和getXXX的区别是前者可以获取到私有类型,下面一个例子打印出了String类的信息。
    package com.reflect;
     
    import java.lang.reflect.Constructor;
    import java.lang.reflect.Field;
    import java.lang.reflect.Method;
    import java.lang.reflect.Modifier;
     
    public class Demo {
     
        public static void main(String[] args) throws ClassNotFoundException {
        
            System.out.println("printFieldInfo start===========");
            printFieldInfo("hello");
            System.out.println("printFieldInfo end=============");
            
            System.out.println("printMethodInfo start===========");
            printMethodInfo("hello");
            System.out.println("printMethodInfo end===========");
                
            System.out.println("printConstructorInfo start===========");
            printConstructorInfo("hello");
            System.out.println("printConstructorInfo end===========");
        }
        
        public static void printFieldInfo(Object o){
            Class<?> clazz = o.getClass();
            Field[] declaredFields = clazz.getDeclaredFields();
            for (int i = 0; i < declaredFields.length; i++) {
                Field field = declaredFields[i];
                System.out.print((i+1)+" ");
                System.out.print(Modifier.toString(field.getModifiers())+" ");
                System.out.print(field.getType().getSimpleName()+" ");
                System.out.print(field.getName());
                System.out.println(";");
            }
        }
        
        public static void printMethodInfo(Object o){
            Class<?> clazz = o.getClass();
            Method[] declaredMethods = clazz.getDeclaredMethods();
            for (int i = 0; i < declaredMethods.length; i++) {
                Method method = declaredMethods[i];
                System.out.print((i+1)+" ");
                System.out.print(Modifier.toString(method.getModifiers())+" ");
                System.out.print(method.getReturnType().getSimpleName());
                System.out.print("(");
                Class<?>[] parameterTypes = method.getParameterTypes();
                for (int j = 0; j < parameterTypes.length; j++) {
                    Class<?> parameterType =parameterTypes[j];
                    if(j==parameterTypes.length-1){
                        System.out.print(parameterType.getSimpleName()+" arg"+j);
                    }else{
                        System.out.print(parameterType.getSimpleName()+" arg"+j+",");
                    }
                }
                System.out.println(");");
            }
        }
        
        
        public static void printConstructorInfo(Object o){
            Class<?> clazz = o.getClass();
            Constructor<?>[] declaredConstructors = clazz.getDeclaredConstructors();
            for (int i = 0; i < declaredConstructors.length; i++) {
                Constructor<?> constructor = declaredConstructors[i];
                System.out.print((i+1)+" ");
                System.out.print(Modifier.toString(constructor.getModifiers())+" ");
                System.out.print(clazz.getSimpleName());
                System.out.print("(");
                Class<?>[] parameterTypes = constructor.getParameterTypes();
                for (int j = 0; j < parameterTypes.length; j++) {
                    Class<?> parameterType =parameterTypes[j];
                    if(j==parameterTypes.length-1){
                        System.out.print(parameterType.getSimpleName()+" arg"+j);
                    }else{
                        System.out.print(parameterType.getSimpleName()+" arg"+j+",");
                    }
                }
                System.out.println(");");
            }
        }
     
    }
    

    打印结果如下;

    printFieldInfo start===========
    1 private final char[] value;
    2 private int hash;
    3 private static final long serialVersionUID;
    4 private static final ObjectStreamField[] serialPersistentFields;
    5 public static final Comparator CASE_INSENSITIVE_ORDER;
    printFieldInfo end=============
    printMethodInfo start===========
    1 public boolean(Object arg0);
    2 public String();
    ……
    76 public String(Locale arg0);
    77 public String();
    printMethodInfo end===========
    printConstructorInfo start===========
    1 public String(byte[] arg0,int arg1,int arg2);
    2 public String(byte[] arg0,Charset arg1);
    ……
    15 public String(byte[] arg0,int arg1);
    16 public String(byte[] arg0,int arg1,int arg2,int arg3);
    printConstructorInfo end===========
    
    1. 实际操练,编写测试类,定义了有参和无参的构造函数,普通变量,静态变量,普通方法,静态方法。
    public class BeReflected {
        // 私有普通变量
        private String field1 = "I am field1";
        // 私有静态变量
        private static String staticField = "I am staticField";
        // 无参构造方法
        public BeReflected() {
        }
        // 有参构造方法
        public BeReflected(String s) {
            field1 = s;
        }
        // 普通无参方法
        private void method1() {
            System.out.println("I am method1");
        }
        // 普通带1个参数方法
        private void method2(String param) {
            System.out.println("I am method1--param = " + param);
        }
        // 普通带多个参数方法
        private void method3(String param, String param2, int param3) {
            System.out.println("param = " + param + " param2 = " + param2 + " param3 = " + param3);
        }
        // 静态无参方法
        public static void staticMethod() {
            System.out.println("I am staticMethod");
        }
        // 静态带参数方法
        public static void staticMethod(String s) {
            System.out.println("I am staticMethod:s:" + s);
        }
    }
    
    1. 反射创建对象:通过Class的newInstance对象就可以创建一个对象,所以new只是创建对象的一种方式。当然通过newInstance要求改类需要有一个空的无参构造方法。
    Class<?> c = Class.forName("com.refelct.BeReflected");
    Object obj = c.newInstance();
    
    1. 反射调用构造函数:如果想调用有参数的构造函数,就要用到Constructor这个类了,通过Class的getDeclaredConstructor方法可以获得Constructor对象,传入的参数是方法参数类型,比如int要传入int.class,字符串传String.class,字符串数组传String[].class;
    Class<?> c = Class.forName("com.refelct.BeReflected");
    Constructor ss = c.getDeclaredConstructor(String.class);
    Object tt = ss.newInstance("测试构造函数");
    
    1. 反射获取静态变量:调用属性就要用到Field这个类了,通过getDeclaredField方法获取到属性后需要设置setAccessible(true)。由于静态变量是属于类的,所以不需要类的实例,直接调用Field类的get(null)即可获得;
    Class<?> c = Class.forName("com.refelct.BeReflected");
    Field field = c.getDeclaredField("staticField");
          if (field != null) {
                field.setAccessible(true);
                Object o = field.get(null);
                System.out.println("o:" + o);
          } 
    
    1. 反射获取私有普通变量:由于普通变量是属于对象的,所以需要先获得类的实例,然后再调用Field类的get(obj);
    Class<?> c = Class.forName("com.refelct.BeReflected");
    Object obj = c.newInstance();
    Field field = c.getDeclaredField("field1");
          if (field != null) {
                field.setAccessible(true);
                Object o = field.get(obj);
                System.out.println("o:" + o);
          } 
    

    所以静态和非静态的区别在于是否需要传入对象,当然我试了静态方法和属性传入obj也可以获取到。

    1. 反射设置私有变量:通过Field类的set(obj,value)可以修改私有变量。
    Class<?> c = Class.forName("com.refelct.BeReflected");
    Object obj = c.newInstance();
     Field field = c.getDeclaredField("field1");
                if (field != null) {
                    field.setAccessible(true);
                    field.set(obj, "测试变量");
                    System.out.println("o:" + field.get(obj));
                } 
    
    1. 反射调用无参方法:方法主要是用到Method类,先通过getDeclaredMethod()获取方法,然后setAccessible(true),接着执行invoke函数,同理静态方法不需要对象。
    Class<?> c = Class.forName("com.refelct.BeReflected");
    // 调用静态无参方法
    Method method = c.getDeclaredMethod("method1");
                if (method != null) {
                    method.setAccessible(true);
                    method.invoke(null);
                }
    
    // 调用普通无参方法
    Object obj = c.newInstance();
    Method method = c.getDeclaredMethod("method1");
                if (method != null) {
                    method.setAccessible(true);
                    method.invoke(obj);
                }
    
    1. 反射调用有参方法:有参是需要传入参数类型Class<?>... parameterTypes和参数值Object... args的。
    Class<?> c = Class.forName("com.refelct.BeReflected");
    // 调用普通有1个参数的方法
    Object obj = c.newInstance();
    Method method = c.getDeclaredMethod("method2",String.class);
                if (method != null) {
                    method.setAccessible(true);
                    method.invoke(obj,"我是测试值");
                }
    
    // 调用普通有多个参数的方法
    Object obj = c.newInstance();
    Method method = c.getDeclaredMethod("method3",
          new Class<?>[]{String.class, String.class, int.class});
                if (method != null) {
                    method.setAccessible(true);
                    method.invoke(obj, new Object[]{"1", "2", 3});
                }
    

    四. 反射的其他知识

    1. final变量可以反射吗?
      如果是直接声明的,是无法反射的,因为编译期间final类型的数据自动被优化了,即所有用到该变量的地方都被替换成了常量。所以 get方法在编译后自动优化成了return "gps",而不是 return GPS_PROVIDER。
    private static final String GPS_PROVIDER = "gps";
    

    但如果不是直接定义的就可以反射

    private static final String GPS_PROVIDER ;
    public LocationManager(){
               GPS_PROVIDER = "gps";
    }
    
    1. 只能反射自己jvm所包含的class,不能反射别的进程里的类,比如想反射微信里的某个字段,那是不可能的。

    五. 反射的问题

    1. 反射的效率问题:反射比直接调用实例要慢,getMethod和getDeclaredField方法会比invoke和set方法耗时,详细介绍可以参考这篇文章。这里我们做个测试,反射调用静态方法正常调用静态方法各100000遍。
        private static void testTime() throws Exception {
            {
                long t1 = System.currentTimeMillis();
                Class<?> c = Class.forName("com.refelct.BeReflected");
                Object obj = c.newInstance();
                Method m = c.getDeclaredMethod("method1");
                m.setAccessible(true);
                for (int i = 0; i < 100000; i++) {
                    m.invoke(obj);
                }
                long t2 = System.currentTimeMillis() - t1;
                System.out.println("反射消耗:" + t2);
            }
            {
                long t1 = System.currentTimeMillis();
                BeReflected beReflected = new BeReflected();
                for (int i = 0; i < 100000; i++) {
                    beReflected.method1();
                }
                long t2 = System.currentTimeMillis() - t1;
                System.out.println("正常消耗:" + t2);
            }
        }
    
    
    结果打印:
    正常消耗:8ms
    反射消耗:65ms
    

    可以看到但是调用invoke就很耗时了,还没有把getMethod和Class.forName加入循环,那么有什么可以提高反射效率的方法吗?这里我想到这么几点;

    • 使用接口代替Object:在用反射创建对象时转成类的实例或者接口,然后执行方法调用,避免调用invoke;
                IRe obj = (IRe) c.newInstance();
                // BeReflected obj = (BeReflected) c.newInstance();
                for (int i = 0; i < 100000; i++) {
                    obj.method1();
                }
    结果打印:
    正常消耗:7ms
    反射消耗:5ms
    
    • 使用缓存:对于中间产物使用缓存存储下来,比如class对象,方法名,变量,可以用一个map来做缓存;

    • 使用getDeclaredMethod(param)是要由于先调用getMethod然后去遍历方法名的,getDeclaredField也是;

    1. 反射的安全问题:因为可以随意修改类的所有状态和行为,包括private方法和实例,所以如果不熟悉被反射类的实现原理,随意修改可能导致潜在的逻辑问题;

    2. 兼容性问题:反射会涉及到直接访问类的方法名和实例名,不同版本的API如果有变动,反射时找不到对应的属性和方法时会报异常,最常见的就是针对安卓版本的适配;

    3. 其他问题:有用到反射的类不能被混淆,静态编译时没有用到的类和资源不能被删除,否则反射找不到;

    4. 反射的特点:
      反射自由度高,不受类的访问权限限制;
      反射存在性能问题,但使用不频繁时对程序性能影响并不大;
      反射是改不了方法,拦截方法需要采用动态代理;

    六. 反射的用途

    为什么需要反射呢,这用途说来就很多了。

    1. 构建框架:一般构建框架的时候会用到反射,比如创建Activity的时候就用到了newInstance方法,Java当中的很多框架都采用反射。
    2. 构建设计模式:比如一个采用反射来创建对象的工厂模式。
    public class Factory {
        public static <T extends Product> T getProduct(String className){
            Class<?> cls = Class.forName(className);
            Product product = (Product) cls.newInstance();
            return (T) product;
        }
    }
    
    1. 按需加载类,节省编译和初始化APK的时间。动态加载第三方jar包,解决安卓开发中方法数不能超过65536个的问题;
    2. 通过反射运行配置文件,实现解耦,其实也是设计框架的思想;
    3. 跳过泛型检查:如果我们往List里添加元素,必须符合类型检查,如果不符合就编译不过,泛型是在编译期检查的,通过反射就可以在运行时跳过这个限制。
    ArrayList<String> strList = new ArrayList<>();
    strList.add("aaa");
    strList.add("bbb");
    // 正常情况下添加一个100是无法通过编译的
    // strList.add(100);
    
    //获取ArrayList的Class对象,反向的调用add()方法,添加数据
    Class listClass = strList.getClass(); 
    Method m = listClass.getMethod("add", Object.class);
    m.invoke(strList, 100);
    
    // 输出的时候注意这里用了Object,而不是String
    for(Object obj : strList){
        System.out.println(obj);
    }
    
    1. 反射执行main方法
        Class clazz = Class.forName("fanshe.main.Student");
        Method methodMain = clazz.getMethod("main", String[].class);
        // 方式一
        methodMain.invoke(null, (Object)new String[]{"a","b","c"});
        // 方式二
        // methodMain.invoke(null, new Object[]{new String[]{"a","b","c"}});
    
    1. 写一个简单的反射工具类ReflectUtils,但实际做框架时还是要转成接口的,还要考虑泛型,不能一味的使用Object。
    public class ReflectUtils {
    
        // 反射一个对象的无参方法,静态方法obj可以传null
        // ReflectUtils.invoke(c, "staticMethod", obj)
        public static Object invoke(Class<?> c, String methodName, Object obj) {
            try {
                Method method = c.getDeclaredMethod(methodName);
                if (method != null) {
                    method.setAccessible(true);
                    return method.invoke(obj);
                } else {
                    p("该方法不存在");
                }
    
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
    
        // 反射一个对象的有一个参数方法,静态方法obj可以传null
        // ReflectUtils.invoke(c, "staticMethod", obj, String.class, "test");
        public static void invoke(Class<?> c, String methodName, Object obj, Class<?> target, Object params) {
            try {
                Method method = c.getDeclaredMethod(methodName, target);
                if (method != null) {
                    method.setAccessible(true);
                    method.invoke(obj, params);
                } else {
                    p("该方法不存在");
                }
    
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        // 反射一个对象的有多个参数方法,静态方法obj可以传null
        // ReflectUtils.invoke(c, "method4", obj, new Class<?>[]{String.class, String.class},
        // new String[]{"1", "2"});
        // ReflectUtils.invoke(c, "method3", obj, new Class<?>[]{String.class, String.class, int.class},
        // new Object[]{"1", "2", 3});
        public static void invoke(Class<?> c, String methodName, Object obj, Class<?>[] target, Object[] params) {
            try {
                Method method = c.getDeclaredMethod(methodName, target);
                if (method != null) {
                    method.setAccessible(true);
                    method.invoke(obj, params);
                } else {
                    p("该方法不存在");
                }
    
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        // 获取一个对象的值,静态变量obj可以传null
        // ReflectUtils.getValue(c, "pubfield1", obj);
        public static Object getValue(Class<?> c, String fieldName, Object obj) {
            try {
                Field field = c.getDeclaredField(fieldName);
                if (field != null) {
                    field.setAccessible(true);
                    return field.get(obj);
                } else {
                    p("该变量不存在");
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
    
    
        // 修改一个对象的值,静态变量obj可以传null
        // ReflectUtils.setValue(c, "staticField", obj, "测试变量")
        public static Object setValue(Class<?> c, String fieldName, Object obj, Object value) {
            try {
                Field field = c.getDeclaredField(fieldName);
                if (field != null) {
                    field.setAccessible(true);
                    field.set(obj, value);
                    return field.get(obj);
                } else {
                    p("该变量不存在");
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
    
        // 传入被代理对象的classloader,实现的接口,还有DynamicProxyHandler的对象即可
        public static Object newProxyInstance(Object object, InvocationHandler invocationHandler) {
            return Proxy.newProxyInstance(object.getClass().getClassLoader(),
                    object.getClass().getInterfaces(), invocationHandler);
        }
    
    
        public static void p(String s) {
            System.out.println("" + s);
            // Log.e("gzq",""+s);
        }
    }
    

    七. 总结

    本文介绍了反射的一些基本知识,也是为下篇动态代理做一个铺垫,感谢大家的阅读,我们下周再见。

    相关文章

      网友评论

        本文标题:反射机制

        本文链接:https://www.haomeiwen.com/subject/wkfrhqtx.html