Java反射実現原理分析

25887 ワード

一、反射の使い方


1、Class反射クラスの取得方法


(1)getClassメソッドで:
  Proxy proxy = new ProxyImpl();
  Class proxyClazz = proxy.getClass();

(2)Classを通過する.forNameメソッド
  Proxy proxy = new ProxyImpl();
  Class proxyClazz = Class.forName("com.dh.yjt.SpringBootDemo.test.Reflect.Proxy");

(3)通過する.class 
  Proxy proxy = new ProxyImpl();
  Class proxyClazz = Proxy.class;

2、タイプ情報の取得


反射の大きな利点は、実行中にオブジェクトのタイプ情報を取得できることです.たとえば、再実行中にオブジェクトメソッド情報を取得し、このメソッドを実行する必要がある場合は、次のようにします.
まず、実装クラスのメソッドが異なるレベルで表示されるインタフェースとその実装クラスを作成します.
  class ProxyImpl implements Proxy{
        public void run(){
            System.out.println("run");
        }

        public void publicFun(){
            System.out.println("publicFun");
        }

        private void privateFun(){
            System.out.println("privateFun");
        }

        void packageFun(){
            System.out.println("packageFun");
        }

        protected void protectedFun(){
            System.out.println("protectedFun");
        }

    }

    interface Proxy{
        public void run();
    }

次に、反射によってオブジェクトのメソッドを取得し、次のことを実行できます.
   @Test
    public void fun() throws Exception {
        Proxy proxy = new ProxyImpl();
        proxy.run();
        callHiddenMethod(proxy,"publicFun");
        callHiddenMethod(proxy,"privateFun");
        callHiddenMethod(proxy,"packageFun");
        callHiddenMethod(proxy,"protectedFun");
    }

    void callHiddenMethod(Object a, String methodName) throws Exception {
        Method g = a.getClass().getDeclaredMethod(methodName);// 
        g.setAccessible(true);// 
        g.invoke(a);//
    }

出力結果:
  run
  publicFun
  privateFun
  packageFun
  protectedFun

反射によってオブジェクトのすべての可視性を実行できる方法がわかります.

二、反射実現原理


1、Methodオブジェクトの取得


上記の例から分かるように、Classクラスを呼び出すgetDeclaredMethodは、指定されたメソッド名およびパラメータのメソッドオブジェクトMethod:を取得することができる
  @CallerSensitive
    public Method getDeclaredMethod(String name, Class>... parameterTypes)
        throws NoSuchMethodException, SecurityException {
        checkMemberAccess(Member.DECLARED, Reflection.getCallerClass(), true);
        Method method = searchMethods(privateGetDeclaredMethods(false), name, parameterTypes);
        if (method == null) {
            throw new NoSuchMethodException(getName() + "." + name + argumentTypesToString(parameterTypes));
        }
        return method;
    }

ここで、privateGetDeclaredMethodsメソッドは、キャッシュまたはJVMから宣言されたメソッドのリストを取得し、Classメソッドは、返されるメソッドのリストから名前とパラメータに一致するメソッドオブジェクトを見つけます. 
  private Method[] privateGetDeclaredMethods(boolean publicOnly) {
        checkInitted();
        Method[] res;
        ReflectionData rd = reflectionData();// reflectionData, 、 
        if (rd != null) {// , reflectionData 
            res = publicOnly ? rd.declaredPublicMethods : rd.declaredMethods;
            if (res != null) return res;
        }
        // No cached value available; request value from VM
     // , JVM 
        res = Reflection.filterMethods(this, getDeclaredMethods0(publicOnly));
        if (rd != null) {
            if (publicOnly) {
                rd.declaredPublicMethods = res;
            } else {
                rd.declaredMethods = res;
            }
        }
        return res;
    }

ここで、searchMethodsの方法は以下のように実現される.
private ReflectionData reflectionData() {
        SoftReference> reflectionData = this.reflectionData;// Class reflectionData
        int classRedefinedCount = this.classRedefinedCount;
        ReflectionData rd;
     // reflectionData 
        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
     // reflectionData
        return newReflectionData(reflectionData, classRedefinedCount);
    }
reflectionData()メソッドの実装から分かるように、reflectionData()オブジェクトはreflectionDataタイプであり、メモリが緊張しているときに回収される可能性があることを示しているが、SoftReferenceパラメータで回収のタイミングを制御することもでき、GCが発生すればそれを回収し、-XX:SoftRefLRUPolicyMSPerMBが回収された後に反射メソッドが実行されると、reflectionDataメソッドでこのようなオブジェクトを再作成するしかない.
  private ReflectionData newReflectionData(SoftReference> oldReflectionData, int classRedefinedCount) {
        if (!useCaches) return null;//

        while (true) {
            ReflectionData rd = new ReflectionData<>(classRedefinedCount);
            // try to CAS it...
       // CAS 
            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;
            }
        }
    }

ReflectionDataは次のように定義されています.
  private static class ReflectionData {
        volatile Field[] declaredFields;
        volatile Field[] publicFields;
        volatile Method[] declaredMethods;
        volatile Method[] publicMethods;
        volatile Constructor[] declaredConstructors;
        volatile Constructor[] 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;
        }
    }

まとめ:newReflectionDataメソッドでは、privateGetDeclaredMethodsで得られたreflectionData()オブジェクトが空でなければ、ReflectionDataオブジェクトからReflectionDataプロパティを取得しようと試み、初めて、またはGCで回収された後、再初期化されたクラスプロパティが空であれば、JVMに再取得し、declaredMethodsに値を付与する必要があり、次回の呼び出しでキャッシュデータを使用することができる.
そしてReflectionDataメソッドは、返されるメソッドのリストから、名前とパラメータに一致するメソッドオブジェクトを見つけます.
  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));
    }

一致するsearchMethodsが見つかった場合、Methodメソッドに戻ります.
  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;
        // Might as well eagerly propagate this if already present
        res.methodAccessor = methodAccessor;
        return res;
    }

ピット1防止:コードから分かるように、Method.copy()メソッドを呼び出すたびに返されるgetDeclaredMethodオブジェクトは、実際には新しいオブジェクトであり、新しいオブジェクトのMethod属性は、コピー前のrootオブジェクトを指し、頻繁に呼び出す必要がある場合は、Methodオブジェクトをキャッシュすることが望ましい.

2、methodを実行する


指定されたメソッドオブジェクトMethodを取得すると、そのMethodメソッドを呼び出すことができ、invokeは以下のように実現される.
  public Object invoke(Object obj, Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
        if (!override) {
            if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
                Class> caller = Reflection.getCallerClass();
                checkAccess(caller, clazz, obj, modifiers);
            }
        }
        MethodAccessor ma = methodAccessor; // read volatile
        if (ma == null) {
            ma = acquireMethodAccessor();
        }
        return ma.invoke(obj, args);
    }

ここでのinvokeオブジェクトはMethodAccessorメソッドの実装の鍵であり、最初はinvokeが空であり、methodAccessorを呼び出して新しいacquireMethodAccessorオブジェクトを生成する必要があり、MethodAccessor自体がインタフェースであり、以下のように実現される.
  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メソッドでは、acquireMethodAccessorクラスのReflectionFactoryによって、主にnewMethodAccessorインタフェースを実現するオブジェクトが作成され、以下のように実現される.
  public MethodAccessor newMethodAccessor(Method var1) {
        checkInitted();
        if (noInflation && !ReflectUtil.isVMAnonymousClass(var1.getDeclaringClass())) {
            return (new MethodAccessorGenerator()).generateMethod(var1.getDeclaringClass(), var1.getName(), var1.getParameterTypes(), var1.getReturnType(), var1.getExceptionTypes(), var1.getModifiers());
        } else {
            NativeMethodAccessorImpl var2 = new NativeMethodAccessorImpl(var1);
            DelegatingMethodAccessorImpl var3 = new DelegatingMethodAccessorImpl(var2);
            var2.setParent(var3);
            return var3;
        }
    } 
MethodAccessorクラスには、2つの重要なフィールドがあります.ReflectionFactory(デフォルトnoInflation)とfalse(デフォルト15)です.inflationThresholdメソッドでは、checkInitted-Dsun.reflect.inflationThreshold=xxxでこの2つのフィールドを再設定でき、一度だけ設定できます.-Dsun.reflect.noInflation=truenoInflationである場合、方法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);
    }

    void setDelegate(MethodAccessorImpl var1) {
        this.delegate = var1;
    }
}

実は、DelegatingMethodAccessorImplオブジェクトは、被エージェントの呼び出しを担当するエージェントオブジェクトです.DelegatingMethodAccessorImpldelegateメソッドinvokeパラメータは現在delegateオブジェクトなので、最終的にはNativeMethodAccessorImplMethodメソッドで呼び出されたのはinvokeオブジェクトNativeMethodAccessorImplの方法は、以下のように実現される.
  public Object invoke(Object var1, Object[] var2) throws IllegalArgumentException, InvocationTargetException {
        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()); this.parent.setDelegate(var3); } return invoke0(this.method, var1, var2); }

ここではinvokeクラスのReflectionFactoryが用いられ、inflationThresholdが15回のdelegateメソッドを呼び出した後、呼び出しを継続するとinvokeクラスのMethodAccessorGeneratorメソッドによりgenerateMethodオブジェクトが生成され、MethodAccessorImplオブジェクトに設定され、次にdelegateが実行されると、新しく作成されたMethod.invokeオブジェクトのMethodAccessorメソッドが呼び出される.
 
踏み込み防止2:Methodのinvokeメソッドを複数回実行すると、15回を超えるごとに新しいinvoke()オブジェクトが作成されるため、メモリが増加します.
 

参照先:


1、Java反射メカニズムの応用実践http://www.importnew.com/24042.html
2、Java方法の反射の実現原理を深く分析するhttps://www.jianshu.com/p/3ea4a6b57f87
3、偽の愚かな説-一緒にGCの血事件から反射原理について話しますhttps://mp.weixin.qq.com/s/5H6UHcP6kvR2X5hTj_SBjA?
 
転載先:https://www.cnblogs.com/aiqiqi/p/10691116.html