深入理解Java中的反射机制和使用原理!详细解析invoke方法的执行和使用(1)

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;

}

}

程序运行结果:

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() 获取的方法可以获取到父类的方法

获取构造器信息

  • 通过 Class 类的 getConstructor 方法得到 Constructor 类的一个实例

  • Constructor 类中 newInstance 方法可以创建一个对象的实例:

public T newInstance(Objec … initargs)

newInstance方法可以根据传入的参数来调用对应的 Constructor 创建对象的实例

获取类的成员变量信息

  • getFileds: 获取公有的成员变量

  • getDeclaredFields: 获取所有已声明的成员变量,但是不能得到父类的成员变量

调用方法

  • 从类中获取一个方法后,可以使用 invoke() 来调用这个方法

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;

// 创建methodClass的实例

Object obj = klass.newInstance();

// 获取methodClass的add方法

Method method = klass.getMethod(“add”, int.class, int.class);

// 调用method对应的方法,实现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;

}

}

利用反射创建数组

  • 数组是Java中一种特殊的数据类型,可以赋值给一个 Object Reference

  • 利用反射创建数组的示例:

public static void ArrayTest() throws ClassNotFoundException {

Class<?> cls = class.forName(“java.lang.String”);

Object array = Array.newInstance(cls, 25);

// 在数组中添加数据

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”);

// 获取数据中的某一项内容

System.out.println(Array.get(array, 3));

}

Array类是 java.lang.reflect.Array 类,通过 Array.newInstance() 创建数组对象:

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

return newArray(componentType, length);

}

newArray方法是一个 native 方法,具体实现在 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类的set和get方法都是native方法,具体实现在HotSpot JVM中,对应关系如下:

  • set: Reflection::array_set

  • get: Reflection::array_get

invoke方法


  • 在Java中很多方法都会调用 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执行过程

  • invoke 方法用来在运行时动态地调用某个实例的方法,实现如下:

@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);

}

权限检查
  • AccessibleObject类是Field,Method和Constructor对象的基类:

  • 提供将反射的对象标记为在使用时取消默认Java语言访问控制检查的能力

  • invoke方法会首先检查AccessibleObject的override属性的值:

  • override默认值为false:

  • 表示需要权限调用规则,调用方法时需要检查权限

  • 也可以使用 setAccessible() 设置为 true

  • override如果值为true:

  • 表示忽略权限规则,调用方法时无需检查权限

  • 也就是说,可以调用任意private方法,违反了封装

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

  1. 首先用 Reflection.quickCheckMemberAccess(clazz, modifiers) 方法检查方法是否为 public

1.1 如果是public方法的话,就跳过本步

1.2 如果不是public方法的话,就用Reflection.getCallerClass()方法获取调用这个方法的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 方法进行一次快速的权限校验 ,checkAccess 方法实现如下:

volatile Object securityCheckCache;

void checkAccess(Class<?> caller, Class<?> clazz, Object obj, int modifiers) throws IllegalAccessException {

if(caller == clazz){ // 快速校验

return; // 权限通过校验

}

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);

}

首先先执行一次快速校验,一旦 Class 正确则权限检查通过;如果未通过,则创建一个缓存,中间再进行检查

  • 如果上面所有的权限检查都未通过,将会执行更详细的检查:

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 方法继续检查权限.若检查通过就更新缓存,这样下一次同一个类调用同一个方法时就不用执行权限检查了,这是一种简单的缓存机制

由于 JMMhappens-before 规则能够保证缓存初始化能够在写缓存之间发生,因此两个cache不需要声明为volatile

  • 检查权限的工作到此结束.如果没有通过检查就会抛出异常,如果通过检查就会到下一步
调用MethodAccessor的invoke方法
  • Method.invoke() 不是自身实现反射调用逻辑,而是通过 sun.refelect.MethodAccessor 来处理

  • Method对象的基本构成:

  • 每个 Java 方法有且只有一个 Method 对象作为 root, 相当于根对象,对用户不可见

  • 当创建 Method 对象时,代码中获得的 Method 对象相当于其副本或者引用

  • root 对象持有一个 MethodAccessor 对象,所有获取到的 Method 对象都共享这一个 MethodAccessor 对象

  • 必须保证 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 可以看出 MethodAccessor 的具体实现类:

  1. sun.reflect.DelegatingMethodAccessorImpl

  2. sun.reflect.MethodAccessorImpl

  3. sun.reflect.NativeMethodAccessorImpl

  • 第一次调用 Java 方法对应的 Method 对象的 invoke() 方法之前,实现调用逻辑的 MethodAccess 对象还没有创建

  • 第一次调用时,才开始创建 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 {

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() {

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实现有两个版本,一个是Java版本,一个是native版本,两者各有特点:

  • 初次启动时 Method.invoke()Constructor.newInstance() 方法采用native方法要比Java方法快3-4倍

  • 启动后 native 方法又要消耗额外的性能而慢于 Java 方法

  • Java实现的版本在初始化时需要较多时间,但长久来说性能较好

  • 这是HotSpot的优化方式带来的性能特性:

  • 跨越native边界会对优化有阻碍作用

  • 为了尽可能地减少性能损耗,HotSpot JDK采用inflation方式:

  • Java方法在被反射调用时,开头若干次使用native版

  • 等反射调用次数超过阈值时则生成一个专用的 MethodAccessor 实现类,生成其中的 invoke() 方法的字节码

  • 以后对该Java方法的反射调用就会使用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 之间进行切换

  • 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);

独家面经总结,超级精彩

本人面试腾讯,阿里,百度等企业总结下来的面试经历,都是真实的,分享给大家!

image

image

image

image

Java面试准备

准确的说这里又分为两部分:

  1. Java刷题
  2. 算法刷题

Java刷题:此份文档详细记录了千道面试题与详解;

image

image

asses 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);

独家面经总结,超级精彩

本人面试腾讯,阿里,百度等企业总结下来的面试经历,都是真实的,分享给大家!

[外链图片转存中…(img-iHzWIW4M-1714740326074)]

[外链图片转存中…(img-zV0SI1jM-1714740326075)]

[外链图片转存中…(img-hswUJtRM-1714740326075)]

[外链图片转存中…(img-B0t819X6-1714740326076)]

Java面试准备

准确的说这里又分为两部分:

  1. Java刷题
  2. 算法刷题

Java刷题:此份文档详细记录了千道面试题与详解;

[外链图片转存中…(img-hMiuaqtl-1714740326076)]

[外链图片转存中…(img-5jLJsB3H-1714740326076)]

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

  • 14
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值