• 反射在Java 中算是黑魔法的存在,你不想了解一下?


    作者:段浅浅儿
    转载地址:https://juejin.cn/post/7130666039385276452

    前言

    反射在Java 中算是黑魔法的存在了。 用一句话来形容「反其道而行之」

    很多限制在反射面前,就是形同虚设。 例如我们设置了一个类的成员变量是 private, 目的就是为了不让外部可以随意修改访问。但是呢,使用反射就可以,你说牛不牛。

    正因为反射技术的灵活性,所以在各大框架中被频繁的使用,所以在学习的过程中,了解反射的意义对后续框架的学习有很大的帮助。

    具体是这么做到的?还是其他更巧妙的用法?想知道的话,就接着看下去吧~

    What:反射是什么?

    在java中,运行时,只要给定类的名字,能够知道这个类的所有信息,可以构造出指定对象,可以调用它的任意一个属性和方法。

    这种动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射机制。

    基于这个机制,反射的作用是

    • 动态的加载类,动态的获取类的信息(属性,方法,构造器)
    • 动态的构造对象
    • 动态调用类和对象的任意方法,构造器
    • 获取泛型信息
    • 处理注解
    • 动态代理

    How:如何使用反射?

    Java 反射机制主要提供了以下功能:

    • 在运行时判断任意一个对象所属的类。
    • 在运行时构造任意一个类的对象。
    • 在运行时判断任意一个类所具有的成员变量和方法。
    • 在运行时调用任意一个对象的方法。

    咱们下面,使用反射获取Person 这个类,来为大家一一演示下。

    public class Person {
    
        public String name;
        private int age;
        private List favorities;
    
        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }
    
        public void addFavorite(String favorite) {
            if (favorities == null) {
                favorities = new ArrayList<>();
            }
            favorities.add(favorite);
        }
    
        private void changeAge(int age) {
            this.age = age;
        }
    
        @Override
        public String toString() {
            return "Person{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    ", favorities=" + favorities +
                    '}';
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    获取反射中的Class对象

    获取 Class 类对象有三种方法

    • 使用 Class.forName 静态方法。(需要给出该类的全路径名:包名+类名)
    • 使用 .class 方法。
    • 使用类对象的 getClass() 方法。
                //第一种,使用 Class.forName 静态方法。
                Class  clazz1 = Class.forName("reflect.Person");
                //第二种,使用 .class 方法。
                Class  clazz2 = Person.class;
                //第三种,使用类对象的 getClass() 方法。
                Person person =  new Person("浅浅",18);
                Class  clazz3 = person.getClass();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    其中

    • 基本类型只可以通过 .class 方法获得Class对象。

    通过反射创建类对象

    很多文章说主要有两种方式:

    • 通过 Class 对象的 newInstance() 方法、
    • 通过 Constructor 对象的 newInstance() 方法。

    两者的区别是通过 Class 对象则只能使用默认的无参数构造方法,通过 Constructor 对象创建类对象可以选择特定构造方法。

    其实, class.newInstance() 内部实现也是通过 Constructor。而且现在已经标记为 @Deprecated(since="9"),建议使用 Constructor 。

                //获取class对象
                Class clazz = Class.forName("reflect.Person");
    
                //如果有默认构造方法,就使用这个
                Person p1 = (Person) clazz.getConstructor().newInstance();
    
                //指定构造方法
                Person p2 = (Person) clazz.getDeclaredConstructor(String.class, int.class).newInstance("dr", 1);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    通过反射获取类的成员变量

    • 成员变量
                //获取class对象
                Class clazz = Class.forName("reflect.Person");
                //构造对象
                Person p = (Person) clazz.getDeclaredConstructor(String.class, int.class).newInstance("dr", 18);
    
                //获取指定变量 name
                Field nameField = clazz.getField("name");
                //修改变量的值
                nameField.set(p,"张三");
                //获取指定变量 age 
                Field ageField = clazz.getDeclaredField("age");
                //age 是private变量,需要先设置可用
                ageField.setAccessible(true);
                //修改变量的值
                ageField.setInt(p,17);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    需要注意的是:

    • getField() : 只能获取 public 修饰的变量,如果是私有变量,会报错NoSuchField
    • getDeclaredField() : 可用于获取全部变量,在不知道变量的权限修饰符的时候,建议使用 getDeclaredField(), 比较保险。

    修改变量的值的方法分为两类

    • 引用类型 只有一个方法 set(Object obj, Object value)
    • 基本类型 为每个基本类型,都提供了一个方法,例如 setInt(Object obj, int i)setLong(Object obj, long l) 大家使用时,根据不同的类型,选择不同的方法。

    通过反射获取类的方法

                //获取class对象
                Class clazz = Class.forName("reflect.Person");
                //构造对象
                Person p = (Person) clazz.getDeclaredConstructor(String.class, int.class).newInstance("dr", 18);
                //获取指定方法 "addFavorite"
                Method method = clazz.getMethod("addFavorite", String.class);
                //调用对象方法
                method.invoke(p, "篮球");
    
                //获取指定方法 "changeAge"
                Method privateMethod = clazz.getDeclaredMethod("changeAge", int.class);
                //changeAge 是private方法,需要先设置可用
                privateMethod.setAccessible(true);
                privateMethod.invoke(p, 17);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    同样提供了 getMethod()getDeclaredMethod(),和 Field 作用一样,不赘述了,

    调用方法的方式只有一种 invoke(Object obj, Object... args)

    Why:反射为什么可以拿到所有的类信息

    要想知道这个问题,需要从类的加载机制说起。

    当我们写完一个类,它首先是一个 java 文件,首先会被编译成.class 文件,然后在运行时,要使用到这个类的时候,jvm 会开始加载这个 .class 文件到内存中。 **.class **是个二进制文件,里面包含了类的全部信息。当加载到内存的时候,分为两个部分:

    • 方法区:存储类运行时数据结构。
    • 堆:创建相应的 Class 对象

    所谓类运行时数据结构,其实就是Java类在 JVM 内存中的一个快照,包括常量池、类字段、类方法等。

    Class 对象就是相当于是一个入口,放在堆中,方便去程序员访问方法区的类运行时结构信息。

    也就是说,只要类被加载到 JVM 内存,就可以获取它全部的信息了。

    那类加载的时机是什么呢?

    其实,虚拟机规范中并没有强制约束何时进行加载,但是规定了5种情况必须对类进行「初始化」,其中就有一条关于反射的:

    使用 java.lang.reflect 包的方法对类进行反射调用的时候,如果类没有进行初始化,则需要先触发其初始化。

    那类初始化一定会先执行加载吗

    类的生命周期如下:

    其中还规定了 加载、验证、准备、初始化,卸载这5个阶段的先后顺序是确定的。

    所以初始化一定会触发加载的。

    反射的机制就是基于以上基础。

    小问题:同一个类的 Class 对象有几个呢?

    还记得上文「获取反射中的Class对象」创建的 clazz1,clazz2,clazz3 吗?你觉得这是3个不同的对象吗?

    System.out.println(clazz1 == clazz2); 会输出什么?

    System.out.println(clazz2 == clazz3); 会输出什么?

    答案是都是同个对象,所以输出的都是 true。 原因是什么呢?我们马上来揭晓~

    Java 中类的加载都是由类加载器实现的。在我们开发人员眼中,类加载器可以分为以下几种:

    • 启动类加载器:Bootstrap ClassLoader 它用来加载Java核心类库。使用C/C++语言实现的,嵌套在JVM内部,java程序无法直接操作这个类。
    • 扩展类加载器:Extension ClassLoader java.ext.dirs目录中加载类库,或者从JDK安装目录:jre/lib/ext目录下加载类库
    • 应用程序类加载器:Application Classloader 程序中默认的类加载器,咱们程序写的类,默认都是由它加载完成的。
    • 自定义加载器:User ClassLoader 咱们自己如果有特别需求,实现的加载器

    他们的之间的关系是:

    jvm 对class文件采用的是按需加载的方式,当需要使用该类时,jvm才会将它的class文件加载到内存中产生class对象。

    将这个过程详细说说的话,就是:

    • 第一步:如果一个类加载器接收到了类加载的请求,它自己不会先去加载,会把这个请求委托给父类加载器去执行。

    • 第二步:如果父类还存在父类加载器,则继续向上委托,一直委托到启动类加载器:Bootstrap ClassLoader

    • 第三步:如果父类加载器可以完成加载任务,就返回成功结果,如果父类加载失败,就由子类自己去尝试加载,如果子类加载失败就会抛出ClassNotFoundException异常

    这就是我们常说的「双亲委派模型」类加载机制。这样做的好处是,加载器之间具备带有优先级的层次关系,每个加载器负责的 classpath 是不同的,而且每加载一个类,全部的加载器范围判定都是从上到下的,所以可以保证一个Class文件只能被同一类加载器加载一次,所以在堆中的Class都是同个对象。

    Class 是如何存储反射数据的?

    在Class里有个关键的属性叫做reflectionData,这里主要存的是每次从jvm里获取到的一些类属性,比如方法,字段等。避免每次都要从 JVM 里去获取数据。

    这个属性主要是SoftReference的,内存不足的的情况下有可能会被回收。

    ReflectionData 中的属性是按需懒加载的。当调用了某个反射方法获取属性时,才会将当前属性数据填充到ReflectionData 的成员变量中。

    public final class Class<T>{
       //反射数据数据结构
        private static class ReflectionData<T> {
            volatile Field[] declaredFields;
            volatile Field[] publicFields;
            volatile Method[] declaredMethods;
            volatile Method[] publicMethods;
            volatile Constructor<T>[] declaredConstructors;
            volatile Constructor<T>[] publicConstructors;
            // Intermediate results for getFields and getMethods
            volatile Field[] declaredPublicFields;
            volatile Method[] declaredPublicMethods;
            volatile Class<?>[] interfaces;
    
            // Cached names
            String simpleName;
            String canonicalName;
            static final String NULL_SENTINEL = new String();
    
            // Value of classRedefinedCount when we created this ReflectionData instance
            final int redefinedCount;
    
            ReflectionData(int redefinedCount) {
                this.redefinedCount = redefinedCount;
            }
        }
    
       //反射数据软应用对象
        private transient volatile SoftReference<ReflectionData<T>> reflectionData;
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    而且需要说明的是,我们每次获取反射的Constructor/Method/Field时,都是重新生成的对象。无论是获取全部,还是某个指定的对象,都会执行下 copy() 动作。

    以Method为例:

        public Method getMethod(String name, Class<?>... parameterTypes)
            throws NoSuchMethodException, SecurityException {
            Objects.requireNonNull(name);
            SecurityManager sm = System.getSecurityManager();
            if (sm != null) {
                checkMemberAccess(sm, Member.PUBLIC, Reflection.getCallerClass(), true);
            }
            Method method = getMethod0(name, parameterTypes);
            if (method == null) {
                throw new NoSuchMethodException(methodToString(name, parameterTypes));
            }
            //执行Copy动作
            return getReflectionFactory().copyMethod(method);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    copyMethod() 最终会调用 Method 类的 copy() 方法

        Method copy() {
            if (this.root != null)
                throw new IllegalArgumentException("Can not copy a non-root Method");
    
            Method res = new Method(clazz, name, parameterTypes, returnType,
                                    exceptionTypes, modifiers, slot, signature,
                                    annotations, parameterAnnotations, annotationDefault);
            res.root = this;
            res.methodAccessor = methodAccessor;
            return res;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    由此可见,我们每次通过调用getDeclaredMethod方法返回的Method对象其实都是一个新的对象,所以不宜多调哦,如果调用频繁最好缓存起来。 同样的道理,也适用于 Constructor和 Method.

    Why: setAccessible() 是如何修改访问权限的?

    实际上并没有真正修改Method/Filed/Constructor的访问权限,而是通过一个 boolean 值数据 override 跳过权限检查的。

    权限的检查,都封装在一个 AccessibleObject的类中,Method/Filed/Constructor 都继承了它。其中提供了 setAccessible()方法来进行权限检查开关设置。

    public class AccessibleObject implements AnnotatedElement {
    
        //默认为false  
        boolean override;
    
        /**
         * setAccessible()最终会跳到这个方法
         * 修改override的值
         */
        boolean setAccessible0(boolean flag) {
            this.override = flag;
            return flag;
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    以Method.invoke 为例,其中就判断了 override属性。

     public Object invoke(Object obj, Object... args)
            throws IllegalAccessException, IllegalArgumentException,
               InvocationTargetException
        {
            //override == true,就会不执行权限检查
            if (!override) {
                Class<?> caller = Reflection.getCallerClass();
                checkAccess(caller, clazz,
                            Modifier.isStatic(modifiers) ? null : obj.getClass(),
                            modifiers);
            }
            MethodAccessor ma = methodAccessor;             // read volatile
            if (ma == null) {
                ma = acquireMethodAccessor();
            }
            return ma.invoke(obj, args);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    Why: 为什么说反射的性能很差?

    我们平常很多框架都使用了反射,而反射中最多使用的就是 Method ,所以我们就从这里分析。

    进入 Method 的 invoke 方法我们可以看到,一开始是进行了一些权限的检查,最后是调用了 MethodAccessor 类的 invoke 方法进行进一步处理:

        public Object invoke(Object obj, Object... args)
            throws IllegalAccessException, IllegalArgumentException,
               InvocationTargetException
        {
            //检查权限
            if (!override) {
                Class<?> caller = Reflection.getCallerClass();
                checkAccess(caller, clazz,
                            Modifier.isStatic(modifiers) ? null : obj.getClass(),
                            modifiers);
            }
            MethodAccessor ma = methodAccessor;  
            if (ma == null) {
                //获取MethodAccessor
                ma = acquireMethodAccessor();
            }
            //实际上调用了 MethodAccessor
            return ma.invoke(obj, args);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    MethodAccessor 实际上是一个接口,的到底是哪个类对象,所以我们需要进入 acquireMethodAccessor() 方法中看看。

        private MethodAccessor acquireMethodAccessor() {
            //是否存在对应的 MethodAccessor 对象,
            MethodAccessor tmp = null;
            if (root != null) tmp = root.getMethodAccessor();
            if (tmp != null) {
                 //存在就复用之前的对象
                methodAccessor = tmp;
            } else {
                // 否则通过ReflectionFactory 创建一个新的。
                tmp = reflectionFactory.newMethodAccessor(this);
                setMethodAccessor(tmp);
            }
    
            return tmp;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    关键的实现是 reflectionFactory.newMethodAccessor()方法。

     public MethodAccessor newMethodAccessor(Method method) {
            //检查是否初始化
            checkInitted();
    
            if (Reflection.isCallerSensitive(method)) {
                Method altMethod = findMethodForReflection(method);
                if (altMethod != null) {
                    method = altMethod;
                }
            }
    
            // use the root Method that will not cache caller class
            Method root = langReflectAccess().getRoot(method);
            if (root != null) {
                method = root;
            }
    
            //如果是不使用Inflation机制
            if (noInflation && !ReflectUtil.isVMAnonymousClass(method.getDeclaringClass())) {
                //生成新的 MethodAccessor
                return new MethodAccessorGenerator().
                    generateMethod(method.getDeclaringClass(),
                                   method.getName(),
                                   method.getParameterTypes(),
                                   method.getReturnType(),
                                   method.getExceptionTypes(),
                                   method.getModifiers());
            } else {
            //否则使用NativeMethodAccessorImpl
                NativeMethodAccessorImpl acc =
                    new NativeMethodAccessorImpl(method);
                DelegatingMethodAccessorImpl res =
                    new DelegatingMethodAccessorImpl(acc);
                acc.setParent(res);
                return res;
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    条件语句上出现了 noInflation,这是个啥呢,从初始化方法 checkInitted() 中可以看到都是从JVM的参数设置中读取的,默认 noInflation = false,也就是说 Inflation 机制开关设置,默认是开启的。

        private static void checkInitted() {
            //省略......
    
            Properties props = GetPropertyAction.privilegedGetProperties();
            // Inflation 机制开关设置,默认开启,noInflation 默认是false
            String val = props.getProperty("sun.reflect.noInflation");
            if (val != null && val.equals("true")) {
                noInflation = true;
            }
            // Inflation 机制的阈值参数设置,默认是15
            val = props.getProperty("sun.reflect.inflationThreshold");
            if (val != null) {
                try {
                    inflationThreshold = Integer.parseInt(val);
                } catch (NumberFormatException e) {
                    throw new RuntimeException("Unable to parse property sun.reflect.inflationThreshold", e);
                }
            }
             //省略......
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    还多了一个inflationThreshold 参数,这是一个Int 类型的数据,默认是15,具体使用在 NativeMethodAccessorImpl 的 invoke方法中。

    public Object invoke(Object var1, Object[] var2) throws IllegalArgumentException, InvocationTargetException {
      // 如果native版本执行超过 -Dsun.reflect.inflationThreshold的的值,默认值是15,则设置DelegatingMethodAccessorImpl的delegate属性为java版本的MethodAccessor实现,即切换为java版本
            if (++this.numInvocations > ReflectionFactory.inflationThreshold() && !ReflectUtil.isVMAnonymousClass(this.method.getDeclaringClass())) {
                MethodAccessorImpl var3 = (MethodAccessorImpl)(new MethodAccessorGenerator()).generateMethod(this.method.getDeclaringClass(), this.method.getName(), this.method.getParameterTypes(), this.method.getReturnType(), this.method.getExceptionTypes(), this.method.getModifiers());
              // 切换java版本
                this.parent.setDelegate(var3);
            }
            // native方法
            return invoke0(this.method, var1, var2);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    注意了,这个地方就是 Inflation 机制的精髓所在。

    反射方法调用的版本实现有两个版本:Java 和 Native 。

    • Native: 具体实现是NativeMethodAccessorImpl,实现最终调用native方法 invoke0() 。启动时相对较快,但是运行效率较慢。
    • Java 具体实现是 MethodAccessorGeneratorX,通过ASM技术动态生成一个MethodAccessorImpl 的子类并实现 invoke方法。初始化慢,但是运行效率较快

    所以综合两个实现,基本性能考虑,引申出 inflation 机制,即:初次加载字节码实现反射,使用花费时间更短的 native 实现。如果频繁使用,再动态生成一个类作为 java 实现,后续都切换为 java 实现。

    另外这其中,还是使用了设计模式之代理模式:

    • MethodAccessor的实现都委托给 DelegatingMethodAccessorImpl
    • DelegatingMethodAccessorImpl 内部通过delegate属性来包装真正实现 invoke 方法的 MethodAccessorImpl
    • Inflation 机制切换实现方法,就是通过修改delegate属性来实现的。

    有没有感受到它的妙处~

    Java 版本的实现详解

    未开启 Inflation 机制和超过native 方法调用次数阈值都是通过MethodAccessorGenerator.generateMethod() 来生成 Java 版的 MethodAccessor 的实现类。

    方法很长,简单看一下:

        private MagicAccessorImpl generate(final Class<?> declaringClass,
                                           String name,
                                           Class<?>[] parameterTypes,
                                           Class<?>   returnType,
                                           Class<?>[] checkedExceptions,
                                           int modifiers,
                                           boolean isConstructor,
                                           boolean forSerialization,
                                           Class<?> serializationTargetClass)
        {
            ByteVector vec = ByteVectorFactory.create();
            //字节码工具实现
            asm = new ClassFileAssembler(vec);
            this.declaringClass = declaringClass;
            this.parameterTypes = parameterTypes;
            this.returnType = returnType;
            this.modifiers = modifiers;
            this.isConstructor = isConstructor;
            this.forSerialization = forSerialization;
    
            asm.emitMagicAndVersion();
            asm.emitShort(add(numCPEntries, S1));
    
            final String generatedName = generateName(isConstructor, forSerialization);
            asm.emitConstantPoolUTF8(generatedName);
            asm.emitConstantPoolClass(asm.cpi());
            thisClass = asm.cpi();
            if (isConstructor) {
                if (forSerialization) {
                    asm.emitConstantPoolUTF8
                        ("jdk/internal/reflect/SerializationConstructorAccessorImpl");
                } else {
                    asm.emitConstantPoolUTF8("jdk/internal/reflect/ConstructorAccessorImpl");
                }
            } else {
                asm.emitConstantPoolUTF8("jdk/internal/reflect/MethodAccessorImpl");
            }
            asm.emitConstantPoolClass(asm.cpi());
            superClass = asm.cpi();
            //省略......
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    核心逻辑主要是通过 ClassFileAssembler 类来生成字节码,会生成一个 class 文件,名称是 GeneratedMethodAccessorX,这个 X 是调用次数,会递增的。初始化慢的原因就在这里。

    生成的 class 文件大概是这个样子的,想当于直接调用。

    例如是实现 Person#addFavorite() 方法反射的时候,就会生成如下的 GeneratedMethodAccessorX

    package sun.reflect;
    
    public class GeneratedMethodAccessor1 extends MethodAccessorImpl {
        public GeneratedMethodAccessor1() {
            super();
        }
    
       //invoke方法实现
        public Object invoke(Object obj, Object[] args)
            throws IllegalArgumentException, InvocationTargetException {
            // prepare the target and parameters
            if (obj == null) throw new NullPointerException();
            try {
                //对象实例类型强转成 Person
                Person target = (Person) obj;
                if (args.length != 1) throw new IllegalArgumentException();
                String arg0 = (String) args[0];
            } catch (ClassCastException e) {
                throw new IllegalArgumentException(e.toString());
            } catch (NullPointerException e) {
                throw new IllegalArgumentException(e.toString());
            }
            // make the invocation
            try {
                //直接调用对象的方法 "addFavorite"
                target.addFavorite(arg0);
            } catch (Throwable t) {
                throw new InvocationTargetException(t);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    总结起来,Java版的实现就是用空间换时间。

    需要特别说明的是,加载Java版本 GeneratedMethodAccessorX的类加载器是专门提供的自定义加载器 DelegatingClassLoader,而且每个方法都会生成一个类加载器。

      static Class<?> defineClass(String name, byte[] bytes, int off, int len,
                                    final ClassLoader parentClassLoader)
        {
            ClassLoader newLoader = AccessController.doPrivileged(
                new PrivilegedAction<ClassLoader>() {
                    public ClassLoader run() {
                            return new DelegatingClassLoader(parentClassLoader);
                        }
                    });
            return JLA.defineClass(newLoader, name, bytes, null, "__ClassDefiner__");
        }
    
    //自定义加载器,内部没有额外实现,只是名字的区别
    class DelegatingClassLoader extends ClassLoader {
        DelegatingClassLoader(ClassLoader parent) {
            super(parent);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    方法注释中,解释的也很清楚:

    • 安全风险:避免了在同一个加载器下,加载不同的字节码带来的未知风险。
    • 性能考虑:在某些情况下可以提前卸载这些生成的类,从而减少运行时间。(因为类的卸载是只有在类加载器可以被回收的情况下才会被回收的)

    最后

    呕心沥血万字长文,让我对反射认识的更清晰了,同时也希望能够带给大家帮助~


    为了帮助到大家更好的掌握好 Android 基础知识,这里准备了相关复习文档,如下

    如果需要的话可以去我GitHub 参考学习:
    https://github.com/733gh/Android-T3
    
    • 1
    • 2
  • 相关阅读:
    1700. 无法吃午餐的学生数量
    (3)跨时钟域问题
    来,做一道字节跳动面试的简单算法题
    爱奇艺:基于龙蜥与 Koordinator 在离线混部的实践解析
    为了保证openGauss的正确安装,请首先对主机环境进行配置
    Shell基础语法——流程控制、函数
    猿创征文|独特且教训的技术成长之路
    Ultra-Fast-Lane-Detection 制作自己的数据集并进行训练
    【电商项目第三问】
    ubuntu20.0安装 java并配置环境
  • 原文地址:https://blog.csdn.net/m0_71263309/article/details/126349867