• java反射及Method的Invoke方法(转载)


      用传统的OOP思想来说,任何一个你写好的且编译过的生成的Class文件,在被类加载器加载后,都会对应有一个java.lang.Class这个类的实例。所以说,每个类的自有的方法属性(类结构)自然被包含在了这个对应的实例上,因此就可以获取到。

    一、原理简介

     1 public class TestClassLoad {  
     2     public static void main(String[] args) throws Exception {  
     3         Class<?> clz = Class.forName("A");  
     4         Object o = clz.newInstance();  
     5         Method m = clz.getDeclaredMethod("hello", null);  
     6         m.invoke(o);   
     7     }
     8     static class A{
     9         public void hello() {
    10             System.out.println("hello world");
    11         }
    12     }  
    13 }  

      上面就是最常见的反射使用的例子,3、4行实现了类的装载、链接和初始化(newInstance方法实际上也是使用反射调用了方法),5、6行实现了从class对象中获取到method对象然后执行反射调用。下面简单分析一下后两行的原理。

      设想一下,如果想要实现method.invoke(action,null)调用action对象的myMethod方法,只需要实现这样一个Method类即可:

    1 Class Method{
    2      public Object invoke(Object obj,Object[] param){
    3         A instance=(A)obj;
    4         return instance.foo();
    5      }
    6 }

    反射的原理之一其实就是动态的生成类似于上述的字节码,加载到jvm中运行。

    二、获取Method对象

      调用Class类的getDeclaredMethod可以获取指定方法名和参数的方法对象Method。
      getDeclaredMethod()方法

    public Method getDeclaredMethod(String name, Class<?>... parameterTypes)
            throws NoSuchMethodException, SecurityException {
            checkMemberAccess(Member.DECLARED, ClassLoader.getCallerClassLoader(), true);
            Method method = searchMethods(privateGetDeclaredMethods(false), name, parameterTypes); //关注这里的两个方法
            if (method == null) {
                throw new NoSuchMethodException(getName() + "." + name + argumentTypesToString(parameterTypes));
            }
            return method;
        }

      其中privateGetDeclaredMethods方法从缓存或JVM中获取该Class中申明的方法列表,searchMethods方法将从返回的方法列表里找到一个匹配名称和参数的方法对象。

    private static Method searchMethods(Method[] methods,String name,
                                        Class<?>[] parameterTypes){
            Method res = null;
            String internedName = name.intern();
            for (int i = 0; i < methods.length; i++) {
                Method m = methods[i];
                if (m.getName() == internedName
                    && arrayContentsEq(parameterTypes, m.getParameterTypes())
                    && (res == null
                        || res.getReturnType().isAssignableFrom(m.getReturnType())))
                    res = m;
            }
    
            return (res == null ? res : getReflectionFactory().copyMethod(res));
        }

      如果找到一个匹配的Method,则重新copy一份返回,copyMethod(res)如下,

    public Method copyMethod(Method arg) {
          return arg.copy();
    }

    即Method.copy()方法

    Method copy() { 
            Method res = new Method(clazz, name, parameterTypes, returnType,
                                    exceptionTypes, modifiers, slot, signature,
                                    annotations, parameterAnnotations, annotationDefault);
            res.root = this;
            res.methodAccessor = methodAccessor;
            return res;
        }

      所次每次调用getDeclaredMethod方法返回的Method对象其实都是一个新的对象,且新对象的root属性都指向原来的Method对象,如果需要频繁调用,最好把Method对象缓存起来。
      接下来看privateGetDeclaredMethods()方法,用于从缓存或JVM中获取该Class中申明的方法列表,代码如下:

    private Method[] privateGetDeclaredMethods(boolean publicOnly) {
            checkInitted();
            Method[] res;
            ReflectionData<T> rd = reflectionData();
            if (rd != null) {
                res = publicOnly ? rd.declaredPublicMethods : rd.declaredMethods;
                if (res != null) return res;
            }
            // No cached value available; request value from VM
            res = Reflection.filterMethods(this, getDeclaredMethods0(publicOnly));
            if (rd != null) {
                if (publicOnly) {
                    rd.declaredPublicMethods = res;
                } else {
                    rd.declaredMethods = res;
                }
            }
            return res;
        }

      其中reflectionData()方法实现如下:

    // Lazily create and cache ReflectionData
        private ReflectionData<T> reflectionData() {
            SoftReference<ReflectionData<T>> reflectionData = this.reflectionData;
            int classRedefinedCount = this.classRedefinedCount;
            ReflectionData<T> rd;
            if (useCaches &&
                reflectionData != null &&
                (rd = reflectionData.get()) != null &&
                rd.redefinedCount == classRedefinedCount) {
                return rd;
            }
            // else no SoftReference or cleared SoftReference or stale ReflectionData
            // -> create and replace new instance
            return newReflectionData(reflectionData, classRedefinedCount);
        }

      这里有个比较重要的数据结构ReflectionData,用来缓存从JVM中读取类的如下属性数据:

    // reflection data that might get invalidated when JVM TI RedefineClasses() is called
        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;
    
            // Value of classRedefinedCount when we created this ReflectionData instance
            final int redefinedCount;
    
            ReflectionData(int redefinedCount) {
                this.redefinedCount = redefinedCount;
            }
        }

      从reflectionData()方法实现可以看出:reflectionData对象是SoftReference类型的,说明在内存紧张时可能会被回收,不过也可以通过-XX:SoftRefLRUPolicyMSPerMB参数控制回收的时机,只要发生GC就会将其回收,如果reflectionData被回收之后,又执行了反射方法,那只能通过newReflectionData方法重新创建一个这样的对象了,newReflectionData方法实现如下:

    private ReflectionData<T> newReflectionData(SoftReference<ReflectionData<T>> oldReflectionData,
                                                    int classRedefinedCount) {
            if (!useCaches) return null;
    
            while (true) {
                ReflectionData<T> rd = new ReflectionData<>(classRedefinedCount);
                // try to CAS it...
                if (Atomic.casReflectionData(this, oldReflectionData, new SoftReference<>(rd))) {
                    return rd;
                }
                // else retry
                oldReflectionData = this.reflectionData;
                classRedefinedCount = this.classRedefinedCount;
                if (oldReflectionData != null &&
                    (rd = oldReflectionData.get()) != null &&
                    rd.redefinedCount == classRedefinedCount) {
                    return rd;
                }
            }
        }
    static <T> boolean casReflectionData(Class<?> clazz,
                                                 SoftReference<ReflectionData<T>> oldData,
                                                 SoftReference<ReflectionData<T>> newData) {
                return unsafe.compareAndSwapObject(clazz, reflectionDataOffset, oldData, newData);
            }

      方法调用了casReflectionData(),通过unsafe.compareAndSwapObject方法重新设置reflectionData字段;
    在privateGetDeclaredMethods方法中,如果通过reflectionData()获得的ReflectionData对象不为空,则尝试从ReflectionData对象中获取declaredMethods属性,如果是第一次,或则被GC回收之后,重新初始化后的类属性为空,则需要重新到JVM中获取一次,并赋值给ReflectionData,下次调用就可以使用缓存数据了。

    三、invoke()方法

    invoke方法源码如下

     1 @CallerSensitive
     2 public Object invoke(Object obj, Object... args)
     3     throws IllegalAccessException, IllegalArgumentException,
     4        InvocationTargetException
     5 {
     6     if (!override) {
     7         if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
     8             Class<?> caller = Reflection.getCallerClass();
     9             checkAccess(caller, clazz, obj, modifiers);
    10         }
    11     }
    12     MethodAccessor ma = methodAccessor;             // read volatile
    13     if (ma == null) {
    14         ma = acquireMethodAccessor();
    15     }
    16     return ma.invoke(obj, args);
    17 }

    根据invoke方法的实现,将其分为以下几步:

    1、权限检查

      首先第6行检查AccessibleObject的override属性的值。AccessibleObject 类是 Field、Method 和 Constructor 对象的基类。它提供了将反射的对象标记为在使用时取消默认 Java 语言访问控制检查的能力。 

      override的值默认是false,表示需要权限调用规则,调用方法时需要检查权限;我们也可以用setAccessible方法设置为true,若override的值为true,表示忽略权限规则,调用方法时无需检查权限(也就是说可以调用任意的private方法,违反了封装)。

      如果override属性为默认值false,则进行进一步的权限检查:

      (1)第7行用Reflection.quickCheckMemberAccess(clazz, modifiers)方法检查方法是否为public,如果是的话跳出本步;如果不是public方法,那么用Reflection.getCallerClass()方法获取调用这个方法的Class对象,这是一个native方法:

    @CallerSensitive
        public static native Class<?> getCallerClass();

    获取了这个Class对象caller后用checkAccess方法做一次快速的权限校验,其实现为:

    volatile Object securityCheckCache;
        void checkAccess(Class<?> caller, Class<?> clazz, Object obj, int modifiers)
            throws IllegalAccessException
        {
            if (caller == clazz) {  // 快速校验
                return;             // 权限通过校验
            }
            Object cache = securityCheckCache;  // read volatile
            Class<?> targetClass = clazz;
            if (obj != null
                && Modifier.isProtected(modifiers)
                && ((targetClass = obj.getClass()) != clazz)) {
                // Must match a 2-list of { caller, targetClass }.
                if (cache instanceof Class[]) {
                    Class<?>[] cache2 = (Class<?>[]) cache;
                    if (cache2[1] == targetClass &&
                        cache2[0] == caller) {
                        return;     // ACCESS IS OK
                    }
                    // (Test cache[1] first since range check for [1]
                    // subsumes range check for [0].)
                }
            } else if (cache == caller) {
                // Non-protected case (or obj.class == this.clazz).
                return;             // ACCESS IS OK
            }
            // If no return, fall through to the slow path.
            slowCheckMemberAccess(caller, clazz, obj, modifiers, targetClass);
        }

    首先先执行一次快速校验,一旦调用方法的Class正确则权限检查通过。
    若未通过,则创建一个缓存,中间再进行一堆检查(比如检验是否为protected属性)。
    如果上面的所有权限检查都未通过,那么将执行更详细的检查,其实现为:

    void slowCheckMemberAccess(Class<?> caller, Class<?> clazz, Object obj, int modifiers,
                               Class<?> targetClass)
        throws IllegalAccessException
    {
        Reflection.ensureMemberAccess(caller, clazz, obj, modifiers);
        // Success: Update the cache.
        Object cache = ((targetClass == clazz)
                        ? caller
                        : new Class<?>[] { caller, targetClass });
        // Note:  The two cache elements are not volatile,
        // but they are effectively final.  The Java memory model
        // guarantees that the initializing stores for the cache
        // elements will occur before the volatile write.
        securityCheckCache = cache;         // write volatile
    }

      大体意思就是,用Reflection.ensureMemberAccess方法继续检查权限,若检查通过就更新缓存,这样下一次同一个类调用同一个方法时就不用执行权限检查了,这是一种简单的缓存机制。由于JMM的happens-before规则能够保证缓存初始化能够在写缓存之前发生,因此两个cache不需要声明为volatile。
      到这里,前期的权限检查工作就结束了。如果没有通过检查则会抛出异常,如果通过了检查则会到下一步。

    2、调用MethodAccessor的invoke方法

    我们继续接着invoke()源码:
         MethodAccessor ma = methodAccessor;             // read volatile
         if (ma == null) {
             ma = acquireMethodAccessor();
         }
         return ma.invoke(obj, args);

    可以看到实际上Method.invoke()并不是自己实现的反射调用逻辑,而是委托给sun.reflect.MethodAccessor来处理。

      首先要了解Method对象的基本构成,每个Java方法有且只有一个Method对象作为root,它相当于根对象,对用户不可见。这个root是不会暴露给用户的,当我们通过反射获取Method对象时,新创建Method对象把root包装起来再给用户,我们代码中获得的Method对象都相当于它的副本(或引用)。root对象持有一个MethodAccessor对象,所以所有获取到的Method对象都共享这一个MethodAccessor对象,因此必须保证它在内存中的可见性。root对象其声明及注释为:

    private volatile MethodAccessor methodAccessor;
    // For sharing of MethodAccessors. This branching structure is
    // currently only two levels deep (i.e., one root Method and
    // potentially many Method objects pointing to it.)
    //
    // If this branching structure would ever contain cycles, deadlocks can
    // occur in annotation code.
    private Method  root;

    那么MethodAccessor到底是个啥玩意呢?

    /** This interface provides the declaration for
        java.lang.reflect.Method.invoke(). Each Method object is
        configured with a (possibly dynamically-generated) class which
        implements this interface.
    */
        public interface MethodAccessor {
        /** Matches specification in {@link java.lang.reflect.Method} */
        public Object invoke(Object obj, Object[] args)
            throws IllegalArgumentException, InvocationTargetException;
    }

    可以看到MethodAccessor是一个接口,定义了invoke方法。分析其Usage可得它的具体实现类有:

    • sun.reflect.DelegatingMethodAccessorImpl
    • sun.reflect.MethodAccessorImpl
    • sun.reflect.NativeMethodAccessorImpl

    从方法invoke()源码可以看到,第一次调用一个实际Java方法对应的Method对象的invoke()方法之前,实现调用逻辑的MethodAccessor对象还没有创建;等第一次调用时通过acquireMethodAccessor()方法才新创建MethodAccessor并更新给root,然后调用MethodAccessor.invoke()完成反射调用:

    // NOTE that there is no synchronization used here. It is correct
    // (though not efficient) to generate more than one MethodAccessor
    // for a given Method. However, avoiding synchronization will
    // probably make the implementation more scalable.
    private MethodAccessor acquireMethodAccessor() {
        // First check to see if one has been created yet, and take it
        // if so
        MethodAccessor tmp = null;
        if (root != null) tmp = root.getMethodAccessor();
        if (tmp != null) {
            methodAccessor = tmp;
        } else {
            // Otherwise fabricate one and propagate it up to the root
            tmp = reflectionFactory.newMethodAccessor(this);
            setMethodAccessor(tmp);
        }
        return tmp;
    }

    可以看到methodAccessor实例由reflectionFactory对象操控生成,研究一下sun.reflect.ReflectionFactory类的源码:

    public class ReflectionFactory {
        private static boolean initted = false;
        private static Permission reflectionFactoryAccessPerm
            = new RuntimePermission("reflectionFactoryAccess");
        private static ReflectionFactory soleInstance = new ReflectionFactory();
        // Provides access to package-private mechanisms in java.lang.reflect
        private static volatile LangReflectAccess langReflectAccess;
        // 这里设计得非常巧妙
        private static boolean noInflation  = false;
        private static int     inflationThreshold = 15;
        //......
        //这是生成MethodAccessor的方法
        public MethodAccessor newMethodAccessor(Method method) {
            checkInitted();
            if (noInflation && !ReflectUtil.isVMAnonymousClass(method.getDeclaringClass())) {
                return new MethodAccessorGenerator().
                    generateMethod(method.getDeclaringClass(),
                                   method.getName(),
                                   method.getParameterTypes(),
                                   method.getReturnType(),
                                   method.getExceptionTypes(),
                                   method.getModifiers());
            } else {
                NativeMethodAccessorImpl acc =
                    new NativeMethodAccessorImpl(method);
                DelegatingMethodAccessorImpl res =
                    new DelegatingMethodAccessorImpl(acc);
                acc.setParent(res);
                return res;
            }
        }
        ....
    }
    在ReflectionFactory类中,noInflation默认为false,方法newMethodAccessor都会返回DelegatingMethodAccessorImpl对象
    class DelegatingMethodAccessorImpl extends MethodAccessorImpl {
        private MethodAccessorImpl delegate;
    
        DelegatingMethodAccessorImpl(MethodAccessorImpl var1) {
            this.setDelegate(var1);
        }
    
        public Object invoke(Object var1, Object[] var2) throws IllegalArgumentException, InvocationTargetException {
            return this.delegate.invoke(var1, var2); //调用被代理对象的invoke()方法
        }
    
        void setDelegate(MethodAccessorImpl var1) {
            this.delegate = var1;
        }
    }

      其实,DelegatingMethodAccessorImpl对象就是一个代理对象,负责调用被代理对象delegate的invoke方法,其中delegate参数目前是NativeMethodAccessorImpl对象,所以最终Method的invoke方法调用的是NativeMethodAccessorImpl对象invoke方法,实现如下: 

    class NativeMethodAccessorImpl extends MethodAccessorImpl {  
        private Method method;  
        private DelegatingMethodAccessorImpl parent;//这是一个间接层,方便在native与Java版的MethodAccessor之间实现切换
        private int numInvocations;  
    
        NativeMethodAccessorImpl(Method method) {  
            this.method = method;  
        }      
    
        public Object invoke(Object obj, Object[] args)  
            throws IllegalArgumentException, InvocationTargetException  
        {  
            if (++numInvocations > ReflectionFactory.inflationThreshold()) {  //如果大于15
                MethodAccessorImpl acc = (MethodAccessorImpl)  
                    new MethodAccessorGenerator().  
                        generateMethod(method.getDeclaringClass(),  
                                       method.getName(),  
                                       method.getParameterTypes(),  
                                       method.getReturnType(),  
                                       method.getExceptionTypes(),  
                                       method.getModifiers());  
                parent.setDelegate(acc);  
            }          
            return invoke0(method, obj, args);  
        }  
    
        void setParent(DelegatingMethodAccessorImpl parent) {  
            this.parent = parent;  
        }  
    
        private static native Object invoke0(Method m, Object obj, Object[] args);  //native方法,在HotSpot VM里是由JVM_InvokeMethod()函数所支持的
    }  

    第13行可以看到,每次NativeMethodAccessorImpl.invoke()方法被调用时,程序调用计数器都会增加1,看看是否超过阈值;一旦超过,则调用MethodAccessorGenerator.generateMethod()来生成Java版的MethodAccessor的实现类,并且改变DelegatingMethodAccessorImpl所引用的MethodAccessor为Java版。后续经由DelegatingMethodAccessorImpl.invoke()调用到的就是Java版的实现了。

    可以看到DelegatingMethodAccessorImpl就是一个间接层,方便在native与Java版的MethodAccessor之间实现切换。 

    之所以这样设计MethodAccessor两个版本,一个是Java实现的,另一个是native code实现的,是因为Java实现的版本在初始化时需要较多时间,但长久来说性能较好;native版本正好相反,启动时相对较快,但运行时间长了之后速度就比不过Java版了。这是HotSpot的优化方式带来的性能特性,同时也是许多虚拟机的共同点:跨越native边界会对优化有阻碍作用,它就像个黑箱一样让虚拟机难以分析也将其内联,于是运行时间长了之后反而是托管版本的代码更快些。 

      为了权衡两个版本的性能,Sun的JDK使用了“inflation”的技巧:让Java方法在被反射调用时,开头15次使用native版,等反射调用次数超过阈值时则生成一个专用的MethodAccessor实现类,生成其中的invoke()方法的字节码,以后对该Java方法的反射调用就会使用Java版。

    3、java版

      接下来继续上面的源码。generateMethod方法在生成MethodAccessorImpl对象时,会在内存中生成对应的字节码,并调用ClassDefiner.defineClass创建对应的class对象,部分代码如下:

    //....省略代码比较长,运用了asm动态生成字节码技术字节码过程
    return (MagicAccessorImpl)AccessController.doPrivileged(new PrivilegedAction() {
                    public MagicAccessorImpl run() {
                        try {
                            return (MagicAccessorImpl)ClassDefiner.defineClass(var13, var17, 0, var17.length, var1.getClassLoader()).newInstance();
                        } catch (IllegalAccessException | InstantiationException var2) {
                            throw new InternalError(var2);
                        }
                    }
                });

      在ClassDefiner.defineClass方法实现中,每被调用一次都会生成一个DelegatingClassLoader类加载器对象

    static Class<?> defineClass(String var0, byte[] var1, int var2, int var3, final ClassLoader var4) {
            ClassLoader var5 = (ClassLoader)AccessController.doPrivileged(new PrivilegedAction() {
                public ClassLoader run() {
                    return new DelegatingClassLoader(var4);
                }
            });
            return unsafe.defineClass(var0, var1, var2, var3, var5, (ProtectionDomain)null);
        }

      这里每次都生成新的类加载器,是为了性能考虑,在某些情况下可以卸载这些生成的类,因为类的卸载是只有在类加载器可以被回收的情况下才会被回收的,如果用了原来的类加载器,那可能导致这些新创建的类一直无法被卸载,从其设计来看本身就不希望这些类一直存在内存里的,在需要的时候有就行了。
      对本文开头的例子的A.hello(),生成的Java版MethodAccessor大致如下:

    public class GeneratedMethodAccessor1 extends MethodAccessorImpl {      
        public GeneratedMethodAccessor1() {  
            super();  
        }  
    
        public Object invoke(Object obj, Object[] args)     
            throws IllegalArgumentException, InvocationTargetException {  
            // prepare the target and parameters  
            if (obj == null) throw new NullPointerException();  
            try {  
                A target = (A) 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 {  
                target.hello(arg0);  
            } catch (Throwable t) {  
                throw new InvocationTargetException(t);  
            }  
        }  
    }  

      就反射调用而言,这个invoke()方法非常干净(然而就“正常调用”而言这额外开销还是明显的)。注意到参数数组被拆开了,把每个参数都恢复到原本没有被Object[]包装前的样子,然后对目标方法做正常的invokevirtual调用。由于在生成代码时已经循环遍历过参数类型的数组,生成出来的代码里就不再包含循环了。

    4、性能比较

    性能比较
      从变化趋势上看,第1次和第16次调用是最耗时的(初始化NativeMethodAccessorImpl和字节码拼装MethodAccessorImpl)。毕竟初始化是不可避免的,而native方式的初始化会更快,因此前几次的调用会采用native方法。
      随着调用次数的增加,每次反射都使用JNI跨越native边界会对优化有阻碍作用,相对来说使用拼装出的字节码可以直接以Java调用的形式实现反射,发挥了JIT优化的作用,避免了JNI为了维护OopMap(HotSpot用来实现准确式GC的数据结构)进行封装/解封装的性能损耗。因此在已经创建了MethodAccessor的情况下,使用Java版本的实现会比native版本更快。所以当调用次数到达一定次数(15次)后,会切换成Java实现的版本,来优化未来可能的更频繁的反射调用。

    5、invoke过程图解

    转载自:http://rednaxelafx.iteye.com/blog/548536
    http://www.fanyilun.me/2015/10/29/Java%E5%8F%8D%E5%B0%84%E5%8E%9F%E7%90%86/

    https://www.sczyh30.com/posts/Java/java-reflection-2/

  • 相关阅读:
    Tosca IE 浏览器的Internet Options 配置, 解决login很慢的问题
    Tosca 注意事项(持续更新)
    Tosca database help link
    Tosca TestCases: Update all,Checkin all,Checkout,Checkout Tree
    Tosca Connection Validation error:40
    Tosca new project Repository as MS SQL Server
    【笔记4】用pandas实现条目数据格式的推荐算法 (基于用户的协同)
    【笔记3】用pandas实现矩阵数据格式的推荐算法 (基于用户的协同)
    【笔记2】推荐算法中的数据格式
    【笔记1】如何预测用户对给定物品的打分?
  • 原文地址:https://www.cnblogs.com/qingchen521/p/8575761.html
Copyright © 2020-2023  润新知