Java高級工程師面試:Java中的反射機制的理解洗鸵!反射機制的使用原理

655dc10bbf2d4a058f8c45663a33f604.jpg

反射的概念

  • 反射: Refelection,反射是Java的特征之一,允許運行中的Java程序獲取自身信息,并可以操作類或者對象的內(nèi)部屬性
    • 通過反射,可以在運行時獲得程序或者程序中的每一個類型的成員活成成員的信息
    • 程序中的對象一般都是在編譯時就確定下來,Java反射機制可以動態(tài)地創(chuàng)建對象并且調(diào)用相關(guān)屬性,這些對象的類型在編譯時是未知的
    • 也就是說 ,可以通過反射機制直接創(chuàng)建對象,即使這個對象類型在編譯時是未知的
  • Java反射提供下列功能:
    • 在運行時判斷任意一個對象所屬的類
    • 在運行時構(gòu)造任意一個類的對象
    • 在運行時判斷任意一個類所具有的成員變量和方法,可以通過反射調(diào)用private方法
    • 在運行時調(diào)用任意一個對象的方法

反射的原理

  • 反射的核心: JVM在運行時才動態(tài)加載類或者調(diào)用方法以及訪問屬性,不需要事先(比如編譯時)知道運行對象是什么
  • 類的加載:
    • Java反射機制是圍繞Class類展開的
    • 首先要了解類的加載機制:
      • JVM使用ClassLoader將字節(jié)碼文件,即 class文件加載到方法區(qū)內(nèi)存中
Class clazz = ClassLoader.getSystemClassLoader().loadClass("com.mypackage.MyClass");

ClassLoader類根據(jù)類的完全限定名加載類并返回一個Class對象

  • ReflectionData:
    • 為了提高反射的性能,必須要提供緩存
    • class類內(nèi)部使用一個useCaches靜態(tài)變量來標(biāo)記是否使用緩存
    • 這個值可以通過外部的sun.reflect.noCaches配置是否禁用緩存
    • class類內(nèi)部提供了一個ReflectionData內(nèi)部類用來存放反射數(shù)據(jù)的緩存,并聲明了一個reflectionData
    • 由于稍后進(jìn)行按需延遲加載并緩存,所以這個域并沒有指向一個實例化的ReflectionData對象
// 標(biāo)記是否使用緩存,可以通過外部的sun.reflect.noCaches配置是否禁用緩存
private static boolean useCaches = true;

static class ReflectionData<T> {
    volatile Field[] declaredFields;
    volatile Field[] publicFields;
    volatile Method[] declaredMethods;
    volatile Method[] publicMethods;
    volatile Constructor<T>[] declaredConstructors;
    volatile Constructors<T>[] publicConstructors;
    volatile Field[] declaredPublicFields;
    volatile Method[] declaredPublicMethods;
    final int redefinedCount;

    ReflectionData(int redefinedCount) {
        this.redefinedCount = redefinedCount;
    }
}
    
    // 這個是SoftReference,在內(nèi)存資源緊張的時候可能會被回收
    // volatile保證多線程環(huán)境下讀寫的正確性
     private volatile transient SoftReference<RefelectionData<T>> reflectionData;

    // 這個主要用于和ReflectionData中的redefinedCount進(jìn)行比較
    // 如果兩個值不相等,說明ReflectionData緩存的數(shù)據(jù)已經(jīng)過期了
    private volatile transient classRedefinedCount = 0;

反射的主要用途

  • 反射最重要的用途就是開發(fā)各種通用框架
    • 很多框架都是配置化的,通過XML文件配置Bean
    • 為了保證框架的通用性,需要根據(jù)配置文件加載不同的對象或者類,調(diào)用不同的方法
    • 要運用反射,運行時動態(tài)加載需要加載的對象
  • 示例:
    • 在運用Struts 2框架的開發(fā)中會在struts.xml中配置Action:
      <action name="login"
               class="org.ScZyhSoft.test.action.SimpleLoginAction"
               method="execute">
           <result>/shop/shop-index.jsp</result>
           <result name="error">login.jsp</result>
       </action>
  • 配置文件與Action建立映射關(guān)系
  • 當(dāng)View層發(fā)出請求時,請求會被StrutsPrepareAndExecuteFilter攔截
  • StrutsPrepareAndExecuteFilter會動態(tài)地創(chuàng)建Action實例
    • 請求login.action
    • StrutsPrepareAndExecuteFilter會解析struts.xml文件
    • 檢索actionnameloginAction
    • 根據(jù)class屬性創(chuàng)建SimpleLoginAction實例
    • 使用invoke方法調(diào)用execute方法
  • 反射是各種容器實現(xiàn)的核心

反射的運用

  • 反射相關(guān)的類在StrutsPrepareAndExecuteFilter
  • 反射可以用于:
    • 判斷對象所屬的類
    • 獲得class對象
    • 構(gòu)造任意一個對象
    • 調(diào)用一個對象
  • 九大預(yù)定義的Class對象:
    • 基本的Java類型: boolean, byte, char, short, int, long, float, double
    • 關(guān)鍵字void通過class屬性的也表示為Class對象
    • 包裝類或者void類的靜態(tài)TYPE字段:
      • Integer.TYPE == int.class
      • Integer.class == int.class
    • 數(shù)組類型的Class實例對象:
      • Class<String[]> clz = String[].class;
      • 數(shù)組的Class對象如何比較是否相等:
        • 數(shù)組的維數(shù)
        • 數(shù)組的類型
        • Class類中的isArray(),用來判斷是否表示一個數(shù)組類型

獲得Class對象

  • 使用Class類的forName靜態(tài)方法:
public static Class<?> forName(String className);



/* 在JDBC中使用這個方法加載數(shù)據(jù)庫驅(qū)動 */
Class.forName(driver);
  • 直接獲取一個對象的class:
Class<?> klass=int.class;
Class<?> classInt=Integer.TYPE;
  • 調(diào)用對象的getClass()方法:
StringBuilder str=new StringBuilder("A");
Class<?> klass=str.getClass();

判斷是否是某個類的實例

  • 一般來說,使用instanceof關(guān)鍵字判斷是否為某個類的實例
  • 在反射中,可以使用Class對象的isInstance() 方法來判斷是否為某個類的實例,這是一個native方法
public native boolean isInstance(Object obj);

創(chuàng)建實例

通過反射生成對象的實例主要有兩種方式:

  • 使用Class對象的newInstance()方法來創(chuàng)建Class對象對應(yīng)類的實例:
Class<?> c = String.class;
Object str = c.newInstance();
  • 先通過Class對象獲取指定的Constructor對象,再調(diào)用Constructor對象的newInstance()方法來創(chuàng)建實例: 可以用指定的構(gòu)造器構(gòu)造類的實例
/* 獲取String所對應(yīng)的Class對象 */
Class<?> c=String.class;

/* 獲取String類帶一個String參數(shù)的構(gòu)造器 */
Constructor constructor=c.getConstructor(String.class);

/* 根據(jù)構(gòu)造器創(chuàng)建實例 */
Object obj=constructor.newInstance("abc");
System.out.println(obj);

獲取方法

獲取Class對象的方法集合,主要有三種方法:

  • getDeclaredMethods(): 返回類或接口聲明的所有方法:
    • 包括公共,保護(hù),默認(rèn)(包)訪問和私有方法
    • 不包括繼承的方法
public Method[] getDeclaredMethods() throws SecurityException {}
  • getMethods(): 返回某個類所有的public方法
    • 包括繼承類的public方法
public Method[] getMethods() throws SecurityException {}
  • getMethod(): 返回一個特定的方法
    • 第一個參數(shù) :方法名稱
    • 后面的參數(shù) :方法的參數(shù)對應(yīng)Class的對象
public Method getMethod(String name,Class<?>... parameterType) {}
  • 獲取方法示例:
public class MethodTest {
    public static void methodTest() throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        Class<?> c = methodClass.class;
        Object object = c.newInstance();
        Method[] methods = c.getMethods();
        Method[] declaredMethods = c.getDeclaredMethods();
        // 獲取methodClass類中的add方法
        Method method = c.getMethod("add", int.class, int.class);
        // getMethods()方法獲取的所有方法
        System.out.println("getMethods獲取的方法:");
        for (Method m:methods)
            System.out.println(m);
        // getDeclaredMethods()方法獲取的所有方法
        System.out.println("getDeclaredMethods獲取的方法:");
        for (Method m:declaredMethods)
            System.out.println(m);
    }
}

class methodClass {
    public final int n = 3;
    
    public int add(int a, int b) {
        return a + b;
    }
    
    public int sub(int a, int b) {
        return a * b;
    }
}

程序運行結(jié)果:

getMethods獲取的方法:
public int org.ScZyhSoft.common.methodClass.add(int,int)
public int org.ScZyhSoft.common.methodClass.sub(int,int)
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public java.lang.String java.lang.Object.toString()
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()
getDeclaredMethods獲取的方法:
public int org.ScZyhSoft.common.methodClass.add(int,int)
public int org.ScZyhSoft.common.methodClass.sub(int,int)

通過getMethods() 獲取的方法可以獲取到父類的方法

獲取構(gòu)造器信息

  • 通過Class類的getConstructor方法得到Constructor類的一個實例
  • Constructor類中newInstance方法可以創(chuàng)建一個對象的實例:
public T newInstance(Objec ... initargs)

newInstance方法可以根據(jù)傳入的參數(shù)來調(diào)用對應(yīng)的Constructor創(chuàng)建對象的實例

獲取類的成員變量信息

  • getFileds: 獲取公有的成員變量
  • getDeclaredFields: 獲取所有已聲明的成員變量,但是不能得到父類的成員變量

調(diào)用方法

  • 從類中獲取一個方法后,可以使用invoke() 來調(diào)用這個方法
public Object invoke(Object obj, Object ... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {}
  • 示例:
public class InvokeTest {
    public static void main(String[] args) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        Class<?> klass = method.class;
        // 創(chuàng)建methodClass的實例
        Object obj = klass.newInstance();
        // 獲取methodClass的add方法
        Method method = klass.getMethod("add", int.class, int.class);
        // 調(diào)用method對應(yīng)的方法,實現(xiàn)add(1,4)
        Object result = method.invoke(obj, 1, 4);
        System.out.println(result);
    }
}

class methodClass {
    public final int n = 3;
    public int add(int a, int b) {
        return a + b;
    }
    public int sub(int a,int b) {
        return a * b;
    }
}

利用反射創(chuàng)建數(shù)組

  • 數(shù)組是Java中一種特殊的數(shù)據(jù)類型,可以賦值給一個Object Reference
  • 利用反射創(chuàng)建數(shù)組的示例:
public static void ArrayTest() throws ClassNotFoundException {
    Class<?> cls = class.forName("java.lang.String");
    Object array = Array.newInstance(cls, 25);
    // 在數(shù)組中添加數(shù)據(jù)
    Array.set(array, 0, "C");
    Array.set(array, 1, "Java");
    Array.set(array, 2, "Python");
    Array.set(array, 3, "Scala");
    Array.set(array, 4, "Docker");
    // 獲取數(shù)據(jù)中的某一項內(nèi)容
    System.out.println(Array.get(array, 3));
}

Array類是java.lang.reflect.Array類,通過Array.newInstance() 創(chuàng)建數(shù)組對象:

public static Object newInstance(Class<?> componentType, int length) throws NegativeArraySizeException {
    return newArray(componentType, length);
}

newArray方法是一個native方法,具體實現(xiàn)在HotSpot JVM中,源碼如下:

private static native Object newArray(Class<?> componentType, int length) throws NegativeArraySizeException;


------------------------------------------------------------------------------------------------------------------------------------------------------------------------------


- newArray源碼目錄: openjdk\hotspot\src\share\vm\runtime\reflection.cpp

arrayOop Reflection::reflect_new_array(oop element_mirror, jint length, TRAPS) {
  if (element_mirror == NULL) {
    THROW_0(vmSymbols::java_lang_NullPointerException());
  }
  if (length < 0) {
    THROW_0(vmSymbols::java_lang_NegativeArraySizeException());
  }
  if (java_lang_Class::is_primitive(element_mirror)) {
    Klass* tak = basic_type_mirror_to_arrayklass(element_mirror, CHECK_NULL);
    return TypeArrayKlass::cast(tak)->allocate(length, THREAD);
  } else {
    Klass* k = java_lang_Class::as_Klass(element_mirror);
    if (k->oop_is_array() && ArrayKlass::cast(k)->dimension() >= MAX_DIM) {
      THROW_0(vmSymbols::java_lang_IllegalArgumentException());
    }
    return oopFactory::new_objArray(k, length, THREAD);
  }
}
  • Array類的setget方法都是native方法,具體實現(xiàn)在HotSpot JVM中,對應(yīng)關(guān)系如下:
    • set: Reflection::array_set
    • get: Reflection::array_get

invoke方法

  • 在Java中很多方法都會調(diào)用invoke方法,很多異常的拋出多會定位到invoke方法:
java.lang.NullPointerException
  at ......
  at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
  at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
  at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
  at java.lang.reflect.Method.invoke(Method.java:497)

invoke執(zhí)行過程

  • invoke方法用來在運行時動態(tài)地調(diào)用某個實例的方法,實現(xiàn)如下:
@CallSensitive
public Object invoke(Object obj, Object ... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
    if (!override) {
        if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
            Class<?> caller = Refelection.getCallerClass();
            checkAccess(caller, clazz, obj, modifiers);
        }
    }
    MethodAccessor ma = methodAccessor;
    if (ma == null) {
        ma = acquireMethodAccessor();
    }
    return ma.invoke(obj, args);
}

權(quán)限檢查

  • AccessibleObject類是Field,MethodConstructor對象的基類:
    • 提供將反射的對象標(biāo)記為在使用時取消默認(rèn)Java語言訪問控制檢查的能力
  • invoke方法會首先檢查AccessibleObjectoverride屬性的值:
    • override默認(rèn)值為false:
      • 表示需要權(quán)限調(diào)用規(guī)則,調(diào)用方法時需要檢查權(quán)限
      • 也可以使用setAccessible() 設(shè)置為true
    • override如果值為true:
      • 表示忽略權(quán)限規(guī)則,調(diào)用方法時無需檢查權(quán)限
      • 也就是說,可以調(diào)用任意private方法,違反了封裝
  • 如果override屬性為默認(rèn)值false,則進(jìn)行進(jìn)一步的權(quán)限檢查:
  1. 首先用Reflection.quickCheckMemberAccess(clazz, modifiers) 方法檢查方法是否為public
    1.1 如果是public方法的話,就跳過本步
    1.2 如果不是public方法的話,就用Reflection.getCallerClass()方法獲取調(diào)用這個方法的Class對象,這是一個native方法
@CallerSensitive
    public static native Class<?> getCallerClass();

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------


- 在OpenJDK中可以找到getCallerClass方法的JNI入口-Reflection.c

JNIEXPORT jclass JNICALL Java_sun_reflect_Reflection_getCallerClass__
(JNIEnv *env, jclass unused)
{
    return JVM_GetCallerClass(env, JVM_CALLER_DEPTH);
}

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------



- JVM_GetCallerClass的源碼位于jvm.cpp中

VM_ENTRY(jclass, JVM_GetCallerClass(JNIEnv* env, int depth))
  JVMWrapper("JVM_GetCallerClass");
  // Pre-JDK 8 and early builds of JDK 8 don't have a CallerSensitive annotation; or
  // sun.reflect.Reflection.getCallerClass with a depth parameter is provided
  // temporarily for existing code to use until a replacement API is defined.
  if (SystemDictionary::reflect_CallerSensitive_klass() == NULL || depth != JVM_CALLER_DEPTH) {
    Klass* k = thread->security_get_caller_class(depth);
    return (k == NULL) ? NULL : (jclass) JNIHandles::make_local(env, k->java_mirror());
  }
  // Getting the class of the caller frame.
  //
  // The call stack at this point looks something like this:
  //
  // [0] [ @CallerSensitive public sun.reflect.Reflection.getCallerClass ]
  // [1] [ @CallerSensitive API.method                                   ]
  // [.] [ (skipped intermediate frames)                                 ]
  // [n] [ caller                                                        ]
  vframeStream vfst(thread);
  // Cf. LibraryCallKit::inline_native_Reflection_getCallerClass
  for (int n = 0; !vfst.at_end(); vfst.security_next(), n++) {
    Method* m = vfst.method();
    assert(m != NULL, "sanity");
    switch (n) {
    case 0:
      // This must only be called from Reflection.getCallerClass
      if (m->intrinsic_id() != vmIntrinsics::_getCallerClass) {
        THROW_MSG_NULL(vmSymbols::java_lang_InternalError(), "JVM_GetCallerClass must only be called from Reflection.getCallerClass");
      }
      // fall-through
    case 1:
      // Frame 0 and 1 must be caller sensitive.
      if (!m->caller_sensitive()) {
        THROW_MSG_NULL(vmSymbols::java_lang_InternalError(), err_msg("CallerSensitive annotation expected at frame %d", n));
      }
      break;
    default:
      if (!m->is_ignored_by_security_stack_walk()) {
        // We have reached the desired frame; return the holder class.
        return (jclass) JNIHandles::make_local(env, m->method_holder()->java_mirror());
      }
      break;
    }
  }
  return NULL;
JVM_END
  1. 獲取Class對象caller后使用checkAccess方法進(jìn)行一次快速的權(quán)限校驗 ,checkAccess方法實現(xiàn)如下:
volatile Object securityCheckCache;

    void checkAccess(Class<?> caller, Class<?> clazz, Object obj, int modifiers) throws IllegalAccessException {
        if(caller == clazz){    // 快速校驗
            return;             // 權(quán)限通過校驗
        }
        Object cache = securityCheckCache;  // 讀取volatile
        Class<?> targetClass = clazz;
        if (obj != null && Modifier.isProtected(modifiers) && ((targetClass = obj.getClass()) != clazz)) {  // 必須匹配caller,targetClass中的一個
            if (cache instanceof Class[]) {
                Class<?>[] cache2 = (Class<?>[]) cache;
                if (cache2[1] == targetClass && cache[0] == caller) {
                    return;     // 校驗通過
                }
            }
        } else if (cache == caller) {
            return;             // 校驗通過
        }
        slowCheckMemberAccess(caller, clazz, obj, modifiers, targetClass);
    }

首先先執(zhí)行一次快速校驗,一旦Class正確則權(quán)限檢查通過;如果未通過,則創(chuàng)建一個緩存,中間再進(jìn)行檢查

  • 如果上面所有的權(quán)限檢查都未通過,將會執(zhí)行更詳細(xì)的檢查:
void slowCheckMemberAccess(Class<?> caller, Class<?> clazz, Object obj, int modifiers, Class<?> targetClass) throws IllegalAccessException {
    Refelection.ensureMemberAccess(caller, clazz, obj, modifiers);
    // 如果成功,就更新緩存
    Object cache = ((targetClass == clazz) ? caller : new Class<?>[] {caller, targetClass});
    securityCheckCache = cache;
}

Reflection.ensureMemberAccess方法繼續(xù)檢查權(quán)限.若檢查通過就更新緩存,這樣下一次同一個類調(diào)用同一個方法時就不用執(zhí)行權(quán)限檢查了,這是一種簡單的緩存機制
由于JMMhappens-before規(guī)則能夠保證緩存初始化能夠在寫緩存之間發(fā)生,因此兩個cache不需要聲明為volatile

  • 檢查權(quán)限的工作到此結(jié)束.如果沒有通過檢查就會拋出異常,如果通過檢查就會到下一步

調(diào)用MethodAccessor的invoke方法

  • Method.invoke() 不是自身實現(xiàn)反射調(diào)用邏輯,而是通過sun.refelect.MethodAccessor來處理
  • Method對象的基本構(gòu)成:
    • 每個Java方法有且只有一個Method對象作為root, 相當(dāng)于根對象,對用戶不可見
    • 當(dāng)創(chuàng)建Method對象時,代碼中獲得的Method對象相當(dāng)于其副本或者引用
    • root對象持有一個MethodAccessor對象,所有獲取到的Method對象都共享這一個MethodAccessor對象
    • 必須保證MethodAccessor在內(nèi)存中的可見性
  • 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可以看出MethodAccessor的具體實現(xiàn)類:

  1. sun.reflect.DelegatingMethodAccessorImpl
  2. sun.reflect.MethodAccessorImpl
  3. sun.reflect.NativeMethodAccessorImpl
  • 第一次調(diào)用Java方法對應(yīng)的Method對象的invoke()方法之前,實現(xiàn)調(diào)用邏輯的MethodAccess對象還沒有創(chuàng)建
  • 第一次調(diào)用時,才開始創(chuàng)建MethodAccessor并更新為root, 然后調(diào)用MethodAccessor.invoke() 完成反射調(diào)用
/**
 * 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 {
        tmp = reflectionFactory.newMethodAccessor(this);
        setMethodAccessor(tmp);
    } 
    return tmp;
} 
  • methodAccessor實例由reflectionFactory對象操控生成 ,reflectionFactory是在AccessibleObject中聲明的:
/**
 * Reflection factory used by subclasses for creating field,
 * method, and constructor accessors. Note that this is called very early in the bootstrapping process.
 */
static final ReflectionFactory reflectionFactory = AccessController.doPrivileged(
                                                    new sun.reflect.ReflectionFactory.GetReflectionFactoryAction());
  • 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;

    /**
     * "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 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;
        }
    }

    /**
     * We have to defer full initialization of this class until after the static initializer is run since java.lang.reflect
     * Method's static initializer (more properly, that for java.lang.reflect.AccessibleObject) causes this class's to be run, before the system properties are set up
     */
     private static void checkInitted() {
        if (initted) return;
        AccessController.doPrivileged(
            new PrivilegedAction<Void>() {
                public Void run() {
                /**
                 * Tests to ensure the system properties table is fully initialized
                 * This is needed because reflection code is called very early in the initialization process (before command-line arguments have been parsed and therefore these user-settable properties installed
                 * We assume that if System.out is non-null then the System class has been fully initialized and that the bulk of the startup code has been run
                 */
                 if (System.out == null) {
                        // java.lang.System not yet fully initialized
                        return null;
                    }
                    String val = System.getProperty("sun.reflect.noInflation");
                    if (val != null && val.equals("true")) {
                        noInflation = true;
                    }
                    val = System.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);
                        }
                    }
                    initted = true;
                    return null;
                }
            });
    }
}
  • 實際的MethodAccessor實現(xiàn)有兩個版本,一個是Java版本,一個是native版本,兩者各有特點:
    • 初次啟動時Method.invoke()Constructor.newInstance() 方法采用native方法要比Java方法快3-4倍
    • 啟動后native方法又要消耗額外的性能而慢于Java方法
    • Java實現(xiàn)的版本在初始化時需要較多時間,但長久來說性能較好
  • 這是HotSpot的優(yōu)化方式帶來的性能特性:
    • 跨越native邊界會對優(yōu)化有阻礙作用
  • 為了盡可能地減少性能損耗,HotSpot JDK采用inflation方式:
    • Java方法在被反射調(diào)用時,開頭若干次使用native版
    • 等反射調(diào)用次數(shù)超過閾值時則生成一個專用的MethodAccessor實現(xiàn)類,生成其中的invoke() 方法的字節(jié)碼
    • 以后對該Java方法的反射調(diào)用就會使用Java版本
  • ReflectionFactory.newMethodAccessor() 生成MethodAccessor對象的邏輯:
    • native版開始會會生成NativeMethodAccessorImplDelegatingMethodAccessorImpl兩個對象
  • DelegatingMethodAccessorImpl:
/* Delegates its invocation to another MethodAccessorImpl and can change its delegate at run time */
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;
    }
}

DelegatingMethodAccessorImpl對象是一個中間層,方便在native版與Java版的MethodAccessor之間進(jìn)行切換

  • native版MethodAccessor的Java方面的聲明: sun.reflect.NativeMethodAccessorImpl
/* Used only for the first few invocations of a Method; afterward,switches to bytecode-based implementation */
class NativeMethodAccessorImpl extends MethodAccessorImpl {
    private Method method;
    private DelegatingMethodAccessorImpl parent;
    private int numInvocations;
    NativeMethodAccessorImpl(Method method) {
        this.method = method;
    }
    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())) {
            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);
}
  • 每次NativeMethodAccessorImpl.invoke() 方法被調(diào)用時,程序調(diào)用計數(shù)器都會增加1, 看看是否超過閾值
  • 如果超過則調(diào)用MethodAccessorGenerator.generateMethod() 來生成Java版的MethodAccessor的實現(xiàn)類
  • 改變DelegatingMethodAccessorImpl所引用的MethodAccessorJava
  • 經(jīng)由DelegatingMethodAccessorImpl.invoke() 調(diào)用到的就是Java版的實現(xiàn)

JVM層invoke0方法

  • invoke0方法是一個native方法,在HotSpot JVM里調(diào)用JVM_InvokeMethod函數(shù):
JNIEXPORT jobject JNICALL Java_sun_reflect_NativeMethodAccessorImpl_invoke0
(JNIEnv *env, jclass unused, jobject m, jobject obj, jobjectArray args)
{
    return JVM_InvokeMethod(env, m, obj, args);
}
  • openjdk/hotspot/src/share/vm/prims/jvm.cpp:
JVM_ENTRY(jobject, JVM_InvokeMethod(JNIEnv *env, jobject method, jobject obj, jobjectArray args0))
  JVMWrapper("JVM_InvokeMethod");
  Handle method_handle;
  if (thread->stack_available((address) &method_handle) >= JVMInvokeMethodSlack) {
    method_handle = Handle(THREAD, JNIHandles::resolve(method));
    Handle receiver(THREAD, JNIHandles::resolve(obj));
    objArrayHandle args(THREAD, objArrayOop(JNIHandles::resolve(args0)));
    oop result = Reflection::invoke_method(method_handle(), receiver, args, CHECK_NULL);
    jobject res = JNIHandles::make_local(env, result);
    if (JvmtiExport::should_post_vm_object_alloc()) {
      oop ret_type = java_lang_reflect_Method::return_type(method_handle());
      assert(ret_type != NULL, "sanity check: ret_type oop must not be NULL!");
      if (java_lang_Class::is_primitive(ret_type)) {
        // Only for primitive type vm allocates memory for java object.
        // See box() method.
        JvmtiExport::post_vm_object_alloc(JavaThread::current(), result);
      }
    }
    return res;
  } else {
    THROW_0(vmSymbols::java_lang_StackOverflowError());
  }
JVM_END
  • 關(guān)鍵部分為Reflection::invoke_method: openjdk/hotspot/src/share/vm/runtime/reflection.cpp
oop Reflection::invoke_method(oop method_mirror, Handle receiver, objArrayHandle args, TRAPS) {
  oop mirror             = java_lang_reflect_Method::clazz(method_mirror);
  int slot               = java_lang_reflect_Method::slot(method_mirror);
  bool override          = java_lang_reflect_Method::override(method_mirror) != 0;
  objArrayHandle ptypes(THREAD, objArrayOop(java_lang_reflect_Method::parameter_types(method_mirror)));
  oop return_type_mirror = java_lang_reflect_Method::return_type(method_mirror);
  BasicType rtype;
  if (java_lang_Class::is_primitive(return_type_mirror)) {
    rtype = basic_type_mirror_to_basic_type(return_type_mirror, CHECK_NULL);
  } else {
    rtype = T_OBJECT;
  }
  instanceKlassHandle klass(THREAD, java_lang_Class::as_Klass(mirror));
  Method* m = klass->method_with_idnum(slot);
  if (m == NULL) {
    THROW_MSG_0(vmSymbols::java_lang_InternalError(), "invoke");
  }
  methodHandle method(THREAD, m);
  return invoke(klass, method, receiver, override, ptypes, rtype, args, true, THREAD);
}

Java的對象模型 :klassoop

Java版的實現(xiàn)

  • JavaMethodAccessor的生成使用MethodAccessorGenerator實現(xiàn)
    Generator for sun.reflect.MethodAccessor and
    sun.reflect.ConstructorAccessor objects using bytecodes to
    implement reflection. A java.lang.reflect.Method or
    java.lang.reflect.Constructor object can delegate its invoke or
    newInstance method to an accessor using native code or to one
    generated by this class. (Methods and Constructors were merged
    together in this class to ensure maximum code sharing.)

運用了asm動態(tài)生成字節(jié)碼技術(shù) - sun.reflect.ClassFileAssembler

invoke總結(jié)

  • invoke方法的過程:


    在這里插入圖片描述
  • MagicAccessorImpl:
    • 原本Java的安全機制使得不同類之間不是任意信息都可見,但JDK里面專門設(shè)了個MagicAccessorImpl標(biāo)記類開了個后門來允許不同類之間信息可以互相訪問,由JVM管理
/** <P> MagicAccessorImpl (named for parity with FieldAccessorImpl and
    others, not because it actually implements an interface) is a
    marker class in the hierarchy. All subclasses of this class are
    "magically" granted access by the VM to otherwise inaccessible
    fields and methods of other classes. It is used to hold the code
    for dynamically-generated FieldAccessorImpl and MethodAccessorImpl
    subclasses. (Use of the word "unsafe" was avoided in this class's
    name to avoid confusion with {@link sun.misc.Unsafe}.) </P>
    <P> The bug fix for 4486457 also necessitated disabling
    verification for this class and all subclasses, as opposed to just
    SerializationConstructorAccessorImpl and subclasses, to avoid
    having to indicate to the VM which of these dynamically-generated
    stub classes were known to be able to pass the verifier. </P>
    <P> Do not change the name of this class without also changing the
    VM's code. </P> */
class MagicAccessorImpl {
}
  • @CallerSensitive注解
Summary: Improve the security of the JDK’s method-handle implementation by replacing the existing
 hand-maintained list of caller-sensitive methods with a mechanism that accurately identifies
  such methods and allows their callers to be discovered reliably.
/**
 * A method annotated @CallerSensitive is sensitive to its calling class,
 * via {@link sun.reflect.Reflection#getCallerClass Reflection.getCallerClass},
 * or via some equivalent.
 *
 * @author John R. Rose
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({METHOD})
public @interface CallerSensitive {
}
  • @CallerSensitive注解修飾的方法從一開始就知道具體調(diào)用此方法的對象
    • 不用再經(jīng)過一系列的檢查就能確定具體調(diào)用此方法的對象
    • 實際上是調(diào)用sun.reflect.Reflection.getCallerClass方法
  • Reflection類位于調(diào)用棧中的0幀位置
    • sun.reflect.Reflection.getCallerClass() 方法返回調(diào)用棧中從0幀開始的第x幀中的類實例
    • 該方法提供的機制可用于確定調(diào)用者類,從而實現(xiàn)"感知調(diào)用者(Caller Sensitive)"的行為
    • 即允許應(yīng)用程序根據(jù)調(diào)用類或調(diào)用棧中的其它類來改變其自身的行為

反射注意點

  • 反射會額外消耗系統(tǒng)資源,如果不需要動態(tài)地創(chuàng)建一個對象,就不要使用反射
  • 反射調(diào)用方法時可以忽略權(quán)限檢查.可能會破壞封裝性而導(dǎo)致安全問題

<br />
<br />
<br />
<br />
<br />
<br />

萌新小號主在線求關(guān)注同名公眾號!分享技術(shù)干貨排宰,面試題和攻城獅故事。
您的關(guān)注支持是我持續(xù)進(jìn)步的最大動力搓劫!一起學(xué)習(xí)粟瞬,共同進(jìn)步同仆。
[圖片上傳失敗...(image-128021-1624200793135)]

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市裙品,隨后出現(xiàn)的幾起案子俗批,更是在濱河造成了極大的恐慌,老刑警劉巖市怎,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件岁忘,死亡現(xiàn)場離奇詭異,居然都是意外死亡区匠,警方通過查閱死者的電腦和手機干像,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來辱志,“玉大人蝠筑,你說我怎么就攤上這事】粒” “怎么了什乙?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長已球。 經(jīng)常有香客問我臣镣,道長,這世上最難降的妖魔是什么智亮? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任忆某,我火速辦了婚禮,結(jié)果婚禮上阔蛉,老公的妹妹穿的比我還像新娘弃舒。我一直安慰自己,他們只是感情好状原,可當(dāng)我...
    茶點故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布聋呢。 她就那樣靜靜地躺著,像睡著了一般颠区。 火紅的嫁衣襯著肌膚如雪削锰。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天毕莱,我揣著相機與錄音器贩,去河邊找鬼颅夺。 笑死,一個胖子當(dāng)著我的面吹牛蛹稍,可吹牛的內(nèi)容都是我干的吧黄。 我是一名探鬼主播,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼稳摄,長吁一口氣:“原來是場噩夢啊……” “哼僵娃!你這毒婦竟也來了砰琢?” 一聲冷哼從身側(cè)響起狸吞,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤炬灭,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后仗阅,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體昌讲,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年减噪,在試婚紗的時候發(fā)現(xiàn)自己被綠了短绸。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡筹裕,死狀恐怖醋闭,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情朝卒,我是刑警寧澤证逻,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站抗斤,受9級特大地震影響囚企,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜瑞眼,卻給世界環(huán)境...
    茶點故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一龙宏、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧伤疙,春花似錦银酗、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至厨姚,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間键菱,已是汗流浹背谬墙。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工今布, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人拭抬。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓部默,卻偏偏與公主長得像,于是被迫代替她去往敵國和親造虎。 傳聞我的和親對象是個殘疾皇子傅蹂,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,033評論 2 355