Spring AOP系列(五)—反射

前言

前面咱們進行了代理模式、靜態代理、動態代理的學習。而動態代理就是利用Java的反射技術(Java Reflection),在運行時建立一個實現某些給定接口的新類(也稱「動態代理類」)及其實例(對象)。因此接下來咱們有必要學習一下Java中的反射。html

1、基礎知識

1.1 反射是什麼?

在講反射以前,先提一個問題:假如如今有一個類User,我想建立一個User對象而且獲取到其name屬性,我該怎麼作呢?
User.javajava

package com.reflect;

/**
 * @author: create by lengzefu
 * @description: com.reflect
 * @date:2020-09-29
 */
public class User {
    private String name = "小明";
    
    Integer age = 18;
    
    public User(){
        
    }
    
    public User(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
}

方式很簡單:算法

import com.reflect.User;

public class Main {

    public static void main(String[] args) {
        User user = new User();
        System.out.println(user.getName());
    }
}

這種方式是咱們平常在寫代碼時常常用到的一種方式。這是由於咱們在使用某個對象時,老是預先知道本身須要使用到哪一個類,所以可使用直接 new 的方式獲取類的對象,進而調用類的方法。
那假設咱們預先並不知道本身要使用的類是什麼呢?這種場景其實很常見,好比動態代理,咱們事先並不知道代理類是什麼,代理類是在運行時才生成的。這種狀況咱們就要用到今天的主角:反射bootstrap

1.1.1 反射的定義

JAVA反射機制是指在運行狀態中,對於任意一個類,都可以知道這個類的全部屬性和方法;對於任意一個對象,都可以調用它的任意一個方法和屬性;這種動態獲取的信息以及動態調用對象的方法的功能稱爲java語言的反射機制。
注意:這裏特別強調的是運行狀態api

1.2 反射能作什麼?

定義已經給了咱們答案。反射可使得程序在運行時能夠獲取到任意類的任意屬性和方法並調用。緩存

1.3 反射爲何能作到?

這裏咱們須要講一個「類對象」的概念。java中「面向對象」的理念貫徹的比較完全,它強調「萬事萬物皆對象」。那麼「類」是否是也能夠認爲是一個對象呢?java中有一種特殊的類:Class,它的對象是「類」,好比「String」類,「Thread」類都是它的對象。安全

java.lang.Class是訪問類型元數據的接口,也是實現反射的關鍵數據。經過Class提供的接口,能夠訪問一個類型的方法、字段等信息。數據結構

以上已經解答了「反射爲何能作到可使得程序在運行時能夠獲取到任意類的任意屬性和方法並調用」的問題,它是依賴.class字節碼文件作到的。那麼首先咱們須要解決的問題是如何獲取字節碼文件對象(Class對象)。app

1.3.1 獲取Class對象

對於一個類,如上文的User,我想獲取User的相關信息(因爲Users屬於Class類的對象,因此通常稱該行爲爲「獲取類對象」),該怎麼作呢?
有如下三種方式框架

import com.reflect.User;

public class Main {

    public static void main(String[] args) throws ClassNotFoundException {
        // 1.已實例化的對象可調用 getClass() 方法來獲取,一般應用在:傳過來一個 Object類型的對象,不知道具體是什麼類,用這種方法
        User user = new User();
        Class clz1 = user.getClass();

        // 2.類名.class 的方式獲得,該方法最爲安全可靠,程序性能更高,這說明任何一個類都有一個隱含的靜態成員變量 class
        Class clz2 = User.class;

        // 經過類的全路徑名獲取Class對象,用的最多,若是根據類路徑找不到這個類那麼就會拋出 ClassNotFoundException異常。
        Class clz3 = Class.forName("com.reflect.User");

        // 一個類在 JVM 中只會有一個 Class 實例,即咱們對上面獲取的 clz1,clz2,clz3進行 equals 比較,發現都是true。
        System.out.println(clz1.equals(clz2));
        System.out.println(clz2.equals(clz3));
    }
}

1.3.2 Class API

獲取公共構造器 getConstructors()
獲取全部構造器 getDeclaredConstructors()
獲取該類對象 newInstance()
獲取類名包含包路徑 getName()
獲取類名不包含包路徑 getSimpleName()
獲取類公共類型的全部屬性 getFields()
獲取類的全部屬性 getDeclaredFields()
獲取類公共類型的指定屬性 getField(String name)
獲取類所有類型的指定屬性 getDeclaredField(String name)
獲取類公共類型的方法 getMethods()
獲取類的全部方法 getDeclaredMethods()
得到類的特定公共類型方法: getMethod(String name, Class[] parameterTypes)
獲取內部類 getDeclaredClasses()
獲取外部類 getDeclaringClass()
獲取修飾符 getModifiers()
獲取所在包 getPackage()
獲取所實現的接口 getInterfaces()

具體如何使用再也不贅述

2、反射原理解析

2.1 反射與類加載的關係

java類的執行須要經歷如下過程,

編譯:java文件編譯後生成.class字節碼文件
加載:類加載器負責根據一個類的全限定名來讀取此類的二進制字節流到 JVM 內部,並存儲在運行時內存區的方法區,而後將其轉換爲一個與目標類型對應的 java.lang.Class 對象實例
連接

  • 驗證:格式(class文件規範) 語義(final類是否有子類) 操做
  • 準備:靜態變量賦初值和內存空間,final修飾的內存空間直接賦原值,此處不是用戶指定的初值。
  • 解析:符號引用轉化爲直接引用,分配地址

初始化:有父類先初始化父類,而後初始化本身;將static修飾代碼執行一遍,若是是靜態變量,則用用戶指定值覆蓋原有初值;若是是代碼塊,則執行一遍操做。

Java的反射就是利用上面第二步加載到jvm中的.class文件來進行操做的。.第二步加載到jvm中的.class文件來進行操做的。.class文件中包含java類的全部信息,當你不知道某個類具體信息時,可使用反射獲取class,而後進行各類操做。

首先咱們來看看如何使用反射來實現方法的調用的:

public class Main {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        // 1.獲取類對象
        Class clz3 = Class.forName("com.reflect.User");
        // 2.獲取類的構造函數
        Constructor constructor = clz3.getConstructor(String.class, Integer.class);
        // 3.建立一個對象
        User user = (User)constructor.newInstance("璐璐", 17);
        // 4.獲取方法getName
        Method method = clz3.getMethod("getName");
        // 5.調用該方法
        String name = (String) method.invoke(user);

        System.out.println(name);
    }
}

接下來主要解析4,5兩個過程:獲取Method對象和Methode.invoke

2.2 獲取 Method 對象

2.2.1 獲取 Method 的API介紹

Class API中關於獲取Method對象的方法有以下幾個:
getMethod/getMethodsgetDeclaredMethod/getDeclaredMethod
後綴有無「s」的區別
有「s」表示獲取的全部的,無「s」表示獲取的是特定的(由方法參數指定)。
getMethodgetDeclaredMethod的區別
Method對應的是Member.PUBLIC,DeclaredMethod對應的是Member.DECLARED
二者定義以下:

public
interface Member {

    /**
     * Identifies the set of all public members of a class or interface,
     * including inherited members.
     * 標識類或接口的全部公共成員的集合,包括父類的公共成員。
     */
    public static final int PUBLIC = 0;

    /**
     * Identifies the set of declared members of a class or interface.
     * Inherited members are not included.
     * 標識類或接口全部聲明的成員的集合(public、protected,private),可是不包括父類成員
     */
    public static final int DECLARED = 1;
}

其實無論是getMethod仍是getDeclaredMethod,底層都調用了同一個方法:privateGetDeclaredMethods,所以咱們只分析其中一個方法便可。

2.2.2 getMethod 方法源碼分析

seq1
// 4.獲取方法getName
Method method = clz3.getMethod("getName");

客戶端調用Class.getMethod()方法。

seq2
// 參數「name」爲方法名,參數「parameterTypes」爲方法的參數,因爲參數可能有多個且類型不一樣,因此這裏使用到了泛型及可變參數的設定
    public Method getMethod(String name, Class<?>... parameterTypes)
        throws NoSuchMethodException, SecurityException {
        // 權限安全檢查,無權限則拋出 SecurityException
        checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), true);
        Method method = getMethod0(name, parameterTypes, true);
        // 獲取到的method爲空,拋出 NoSuchMethodException 異常
        if (method == null) {
            throw new NoSuchMethodException(getName() + "." + name + argumentTypesToString(parameterTypes));
        }
        return method;
    }

該方法的核心方法是 getMethod0

seq3
private Method getMethod0(String name, Class<?>[] parameterTypes, boolean includeStaticMethods) {
		// 保存接口中的方法,最多隻有1個,但MethodArray初始化大小至少爲2
        MethodArray interfaceCandidates = new MethodArray(2);
        // 遞歸獲取方法,之因此遞歸,正是由於getMethod是須要獲取父類中的方法,與前面關於getMethod的介紹對應
        Method res =  privateGetMethodRecursive(name, parameterTypes, includeStaticMethods, interfaceCandidates);
        // 獲取到本類或父類中的方法,直接返回結果
        if (res != null)
            return res;

        // Not found on class or superclass directly
        // 在本類或父類中沒有找到對應的方法,則嘗試去接口中的方法尋找
        // removeLessSpecifics:移除更不具體的方法,保留具備更具體實現的方法
        interfaceCandidates.removeLessSpecifics();
        return interfaceCandidates.getFirst(); // may be null
    }

接下來分析privateGetMethodRecursive

seq4
private Method privateGetMethodRecursive(String name,
            Class<?>[] parameterTypes,
            boolean includeStaticMethods,
            MethodArray allInterfaceCandidates) {
        // Must _not_ return root methods
        Method res;
        // Search declared public methods 搜索原本中聲明的公共方法
        if ((res = searchMethods(privateGetDeclaredMethods(true),
                                 name,
                                 parameterTypes)) != null) {
            if (includeStaticMethods || !Modifier.isStatic(res.getModifiers()))
            // res 不爲空,返回
                return res;
        }
        // Search superclass's methods res爲空,繼續向父類搜索
        if (!isInterface()) { // 接口必然無父類
            Class<? super T> c = getSuperclass();
            if (c != null) {
	            // 遞歸調用getMethod0,獲取父類的方法實現
                if ((res = c.getMethod0(name, parameterTypes, true)) != null) {
                    return res;
                }
            }
        }
        // Search superinterfaces' methods res仍然爲空,繼續向接口搜索
        Class<?>[] interfaces = getInterfaces();
        for (Class<?> c : interfaces)
		    // 遞歸調用getMethod0,獲取接口的方法實現
            if ((res = c.getMethod0(name, parameterTypes, false)) != null)
                allInterfaceCandidates.add(res);
        // Not found
        return null;
    }

該方法原英文註釋翻譯以後的大概意思爲:

注意:該例程(做用相似於函數,含義比函數更廣)使用的搜索算法的目的是等效於privateGetPublicMethods方法的施加順序。它僅獲取每一個類的已聲明公共方法,可是,以減小在要查詢的類中聲明瞭所請求方法,常見狀況下必須建立的Method對象的數量。 因爲使用默認方法,除非在超類上找到方法,不然須要考慮在任何超級接口中聲明的方法。 收集全部InterfaceCandidates的超級接口中聲明的全部候選對象,若是未在超類上找到匹配項,則選擇最具體的候選者。

原文的英語註釋中各類從句真的不少,本身翻譯完感受仍是有點問題。簡單來講,我以爲比較重要的一點應該是仍是能理解到:
獲取該類已聲明的方法,若是使用的是默認方法,則從父類中尋找該方法。不然去接口中尋找實現最具體的候選方法

接下來分析searchMethods

seq5
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[] methods是怎麼獲得的,咱們回到searchMethods的方法調用處:

searchMethods(privateGetDeclaredMethods(true),
                                 name,
                                 parameterTypes)) != null)

methods經過方法privateGetDeclaredMethods(true)獲得

seq6
private Method[] privateGetDeclaredMethods(boolean publicOnly) {
        checkInitted();
        Method[] res;
        // 1.ReflectionData 存儲反射數據的緩存結構
        ReflectionData<T> rd = reflectionData();
        if (rd != null) {
	        // 2.先從緩存中獲取methods
            res = publicOnly ? rd.declaredPublicMethods : rd.declaredMethods;
            if (res != null) return res;
        }
        // No cached value available; request value from VM
        // 3.沒有緩存,經過 JVM 獲取
        res = Reflection.filterMethods(this, getDeclaredMethods0(publicOnly));
        if (rd != null) {
            if (publicOnly) {
                rd.declaredPublicMethods = res;
            } else {
                rd.declaredMethods = res;
            }
        }
        return res;
    }

先看看ReflectionData<T>

// 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<T>是類Class的靜態內部類,<T>表示泛型,爲具體的類對象。該緩存數據結構中存儲了類的全部信息。redefinedCount是類的重定義次數,能夠理解爲緩存的版本號。
注意最上面的一行註釋:reflection data that might get invalidated when JVM TI RedefineClasses() is called。意思是 當 JVM TI(工具接口)RedefineClasses()被調用時,緩存數據可能會失效。

經過以上分析,咱們知道,每個類對象理論上都會有(被垃圾回收或歷來沒被加載過就沒沒有)一個ReflectionData<T>的緩存,那麼如何獲取它呢?

這就要用到 reflectionData

// Lazily create and cache ReflectionData
    private ReflectionData<T> reflectionData() {
	    // 獲取當前 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);
    }

看看newReflectionData的實現

private ReflectionData<T> newReflectionData(SoftReference<ReflectionData<T>> oldReflectionData,
                                                int classRedefinedCount) {
        // 不使用緩存則直接返回null
        if (!useCaches) return null;

		// 使用while+CAS方式更新數據,建立一個新的ReflectionData,若是更新成功直接返回
        while (true) {
            ReflectionData<T> rd = new ReflectionData<>(classRedefinedCount);
            // try to CAS it...
            if (Atomic.casReflectionData(this, oldReflectionData, new SoftReference<>(rd))) {
                return rd;
            }
            // else retry
            // 獲取到舊的reflectionData和classRedefinedCount的值,若是舊的值不爲null, 而且緩存未失效,說明其餘線程更新成功了,直接返回 
            oldReflectionData = this.reflectionData;
            classRedefinedCount = this.classRedefinedCount;
            if (oldReflectionData != null &&
                (rd = oldReflectionData.get()) != null &&
                rd.redefinedCount == classRedefinedCount) {
                return rd;
            }
        }
    }

如今咱們回到privateGetDeclaredMethods方法的實現,對於第3步:

// 3.沒有緩存,經過 JVM 獲取
        res = Reflection.filterMethods(this, getDeclaredMethods0(publicOnly));

調用的是native方法,此處再也不贅述。
在獲取到對應方法之後,並不會直接返回,以下:

return (res == null ? res : getReflectionFactory().copyMethod(res));
seq7

經過單步調試可發現getReflectionFactory().copyMethod(res)最終調用的是Method#copy

Method copy() {
	    // 1.該對象的root爲null時,代表是 基本方法對象
        if (this.root != null)
	        // 2.只能拷貝基本方法對象即root爲null的對象
            throw new IllegalArgumentException("Can not copy a non-root Method");
		// 3.新建一個與基本方法對象具備相同性質的方法對象
        Method res = new Method(clazz, name, parameterTypes, returnType,
                                exceptionTypes, modifiers, slot, signature,
                                annotations, parameterAnnotations, annotationDefault);
        // 4.res做爲this的拷貝,其root屬性必須指向this
        res.root = this;
        // Might as well eagerly propagate this if already present
        // 5.全部的Method的拷貝都會使用同一份methodAccessor
        res.methodAccessor = methodAccessor;
        return res;
    }

注意如下幾點:

  • root 屬性:能夠理解爲每個 java方法都有惟一的一個Method對象,這個對象就是root,它至關於根對象,對用戶不可見。這個root是不會暴露給用戶的,當咱們經過反射獲取Method對象時,新建立Method對象把root包裝起來再給用戶,咱們代碼中得到的Method對象都至關於它的副本(或引用)。
  • methodAccessor:root 對象持有一個 MethodAccessor 對象,因此全部獲取到的 Method對象都共享這一個 MethodAccessor 對象,所以必須保證它在內存中的可見性。
  • res.root = this:res 做爲 this 的拷貝,其 root 屬性必須指向 this。
小結

getMethod方法時序圖
Alt

2.3 Method.invoke

public Object invoke(Object obj, Object... args)
        throws IllegalAccessException, IllegalArgumentException,
           InvocationTargetException
    {
        if (!override) {
	        // 1.檢查權限
            if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
                Class<?> caller = Reflection.getCallerClass();
                checkAccess(caller, clazz, obj, modifiers);
            }
        }
        // 2.獲取 MethodAccessor
        MethodAccessor ma = methodAccessor;             // read volatile
        if (ma == null) {
	        // 2.1爲空時建立MethodAccessor
            ma = acquireMethodAccessor();
        }
        // 3.調用 MethodAccessor.invoke
        return ma.invoke(obj, args);
    }

2.3.1 檢查權限

這裏對 override 變量進行判斷,若是 override == true,就跳過檢查 咱們一般在 Method#invoke 以前,會調用 Method#setAccessible(true),就是設置 override 值爲 true。

2.3.2 獲取 MethodAccessor

在上面獲取 Method 的時候咱們講到過,Method#copy 會給 Method 的 methodAccessor 賦值。因此這裏的 methodAccessor 就是拷貝時使用的 MethodAccessor。若是 ma 爲空,就去建立 MethodAccessor。

/*
     注意這裏沒有使用synchronization。 爲給定方法生成一個以上的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;
    }

這裏會先查找 root 的 MethodAccessor,這裏的 root 在上面 Method#copy 中設置過。若是仍是沒有找到,就去建立 MethodAccessor。

class ReflectionFactory {
    public MethodAccessor newMethodAccessor(Method method) {
        // 其中會對 noInflation 進行賦值
        checkInitted();
        // ...
        if (noInflation && !ReflectUtil.isVMAnonymousClass(method.getDeclaringClass())) {
            // 生成的是 MethodAccessorImpl
            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;
        }
    }
}

這裏能夠看到,一共有三種 MethodAccessor。MethodAccessorImpl,NativeMethodAccessorImpl,DelegatingMethodAccessorImpl。採用哪一種 MethodAccessor 根據 noInflation 進行判斷,noInflation 默認值爲 false,只有指定了 sun.reflect.noInflation 屬性爲 true,纔會 採用 MethodAccessorImpl。因此默認會調用 NativeMethodAccessorImpl。

MethodAccessorImpl 是經過動態生成字節碼來進行方法調用的,是 Java 版本的 MethodAccessor,字節碼生成比較複雜,這裏不放代碼了。你們感興趣能夠看這裏的 generate 方法。

DelegatingMethodAccessorImpl 就是單純的代理,真正的實現仍是 NativeMethodAccessorImpl。

class DelegatingMethodAccessorImpl extends MethodAccessorImpl {
    private MethodAccessorImpl delegate;
 
    DelegatingMethodAccessorImpl(MethodAccessorImpl delegate) {
        setDelegate(delegate);
    }
 
    public Object invoke(Object obj, Object[] args)
        throws IllegalArgumentException, InvocationTargetException
    {
        return delegate.invoke(obj, args);
    }
 
    void setDelegate(MethodAccessorImpl delegate) {
        this.delegate = delegate;
    }
}

NativeMethodAccessorImpl 是 Native 版本的 MethodAccessor 實現。

class NativeMethodAccessorImpl extends MethodAccessorImpl {
    public Object invoke(Object obj, Object[] args)
        throws IllegalArgumentException, InvocationTargetException
    {
        // We can't inflate methods belonging to vm-anonymous classes because
        // that kind of class can't be referred to by name, hence can't be
        // found from the generated bytecode.
        if (++numInvocations > ReflectionFactory.inflationThreshold()
                && !ReflectUtil.isVMAnonymousClass(method.getDeclaringClass())) {
            // Java 版本的 MethodAccessor
            MethodAccessorImpl acc = (MethodAccessorImpl)
                new MethodAccessorGenerator().
                    generateMethod(method.getDeclaringClass(),
                                   method.getName(),
                                   method.getParameterTypes(),
                                   method.getReturnType(),
                                   method.getExceptionTypes(),
                                   method.getModifiers());
            parent.setDelegate(acc);
        }
        // Native 版本調用
        return invoke0(method, obj, args);
    }
 
    private static native Object invoke0(Method m, Object obj, Object[] args);
}

在 NativeMethodAccessorImpl 的實現中,咱們能夠看到,有一個 numInvocations 閥值控制,numInvocations 表示調用次數。若是 numInvocations 大於 15(默認閥值是 15),那麼就使用 Java 版本的 MethodAccessorImpl。
爲何採用這個策略呢,能夠 JDK 中的註釋:

// "Inflation" mechanism. Loading bytecodes to implement
// Method.invoke() and Constructor.newInstance() currently costs
// 3-4x more than an invocation via native code for the first
// invocation (though subsequent invocations have been benchmarked
// to be over 20x faster). Unfortunately this cost increases
// startup time for certain applications that use reflection
// intensively (but only once per class) to bootstrap themselves.
// To avoid this penalty we reuse the existing JVM entry points
// for the first few invocations of Methods and Constructors and
// then switch to the bytecode-based implementations.
//
// Package-private to be accessible to NativeMethodAccessorImpl
// and NativeConstructorAccessorImpl
private static boolean noInflation        = false;

Java 版本的 MethodAccessorImpl 調用效率比 Native 版本要快 20 倍以上,可是 Java 版本加載時要比 Native 多消耗 3-4 倍資源,因此默認會調用 Native 版本,若是調用次數超過 15 次之後,就會選擇運行效率更高的 Java 版本。那爲何 Native 版本運行效率會沒有 Java 版本高呢?從 R 大博客來看,是由於 這是HotSpot的優化方式帶來的性能特性,同時也是許多虛擬機的共同點:跨越native邊界會對優化有阻礙做用,它就像個黑箱同樣讓虛擬機難以分析也將其內聯,因而運行時間長了以後反而是託管版本的代碼更快些。

2.3.3 調用 MethodAccessor#invoke 實現方法的調用

在生成 MethodAccessor 之後,就調用其 invoke 方法進行最終的反射調用。這裏咱們對 Java 版本的 MethodAccessorImpl 作個簡單的分析,Native 版本暫時不作分析。在前面咱們提到過 MethodAccessorImpl 是經過 MethodAccessorGenerator#generate 生成動態字節碼而後動態加載到 JVM 中的。
到此,基本上 Java 方法反射的原理就介紹完了。

3、反射爲何慢?

3.1 爲何慢?

Java實現的版本在初始化時須要較多時間,但長久來講性能較好;native版本正好相反,啓動時相對較快,但運行時間長了以後速度就比不過Java版了。這是HotSpot的優化方式帶來的性能特性,同時也是許多虛擬機的共同點:跨越native邊界會對優化有阻礙做用,它就像個黑箱同樣讓虛擬機難以分析也將其內聯,因而運行時間長了以後反而是託管版本的代碼更快些。 爲了權衡兩個版本的性能,Sun的JDK使用了「inflation」的技巧:讓Java方法在被反射調用時,開頭若干次使用native版,等反射調用次數超過閾值時則生成一個專用的MethodAccessor實現類,生成其中的invoke()方法的字節碼,之後對該Java方法的反射調用就會使用Java版。 當該反射調用成爲熱點時,它甚至能夠被內聯到靠近Method.invoke()的一側,大大下降了反射調用的開銷。而native版的反射調用則沒法被有效內聯,於是調用開銷沒法隨程序的運行而下降。

總結來講,緣由以下:

  1. jit 沒法優化反射 。 JIT編譯器沒法對反射有效作優化,引用一段java doc中的解釋:

因爲反射涉及動態解析的類型,致使沒法執行某些Java虛擬機優化。因此,反射操做的性能比非反射操做慢,所以應避免在對性能敏感的應用程序中使用反射

  1. 參數的封裝/解封和方法的校驗。invoke方法是傳Object類型的,若是是簡單類型如long,在接口處必須封裝成object,從而生成大量的Long的Object,致使了額外的沒必要要的內存浪費,甚至有可能致使GC;須要進行參數校驗和方法的可見性校驗。
  2. 難之內聯

3.2 反射慢爲何還要用它?

反射這種技術被普遍應用於框架的設計中,但反射的確帶來了必定的性能損耗,既然如此爲何還要用反射呢?

  • 絕大部分系統的性能瓶頸還遠遠沒有到須要考慮反射這裏,邏輯層和數據層上的優化對性能的提高比優化反射高n個數量級。
  • 框架的設計是性能、標準和開發效率等多個方面的權衡。
  • 反射多少會有性能損耗,但通常能夠忽略,而java對javabean方面的反射支持,java底層都有PropertyDescriptor和MethodDescriptor支持,能夠必定程度的減小反射消耗。 AOP方面,cglib是經過類的字節碼生成其子類去操做的,一旦子類生成就是純粹的反射調用,再也不操做字節碼了,而通常AOP調用是在單例上,不會頻繁的去用cglib生成子類。

關於反射性能的具體測試數據,可參考:https://www.jianshu.com/p/4e2b49fa8ba1
其實經過以上數據能夠看出,當量很是大的時候,反射確實是會影響性能。但通常的應用,即便不借助高性能工具包也不會是程序掣肘。固然,這也不是意味着能夠隨意使用,仍是要結合實際的應用來。

4、反射優缺點

4.1 反射的優勢

  • 賦予程序在運行時能夠操做對象的能力。
  • 解耦,提升程序的可擴展性。

4.2 反射的缺點

  • **性能開銷
    反射涉及類型動態解析,因此JVM沒法對這些代碼進行優化。所以,反射操做的效率要比那些非反射操做低得多。咱們應該避免在常常被執行的代碼或對性能要求很高的程序中使用反射。
  • 安全限制
    使用反射技術要求程序必須在一個沒有安全限制的環境中運行。若是一個程序必須在有安全限制的環境中運行,如Applet,那麼這就是個問題了。
  • 內部曝光
    因爲反射容許代碼執行一些在正常狀況下不被容許的操做(好比訪問私有的屬性和方法),因此使用反射可能會致使意料以外的反作用--代碼有功能上的錯誤,下降可移植性。反射代碼破壞了抽象性,所以當平臺發生改變的時候,代碼的行爲就有可能也隨着變化。

4.3 原則

若是使用常規方法可以實現,那麼就不要用反射。

5、參考文獻

https://www.jianshu.com/p/607ff4e79a13
https://www.cnblogs.com/chanshuyi/p/head_first_of_reflection.html
https://blog.csdn.net/zhenghongcs/article/details/103143144

相關文章
相關標籤/搜索