大白话说Java反射:入门、使用、原理转载

原创
小哥 3年前 (2022-10-27) 阅读数 50 #大杂烩

这种反射包含一个“反”字,所以要解释这种反射,你必须首先从“积极”开始。

一般来说,当我们使用一个类时,我们必须知道它是什么以及它被用来做什么。因此,我们直接实例化类,然后使用类对象进行操作。

Apple apple = new Apple(); //直接初始化,“正字法”
apple.setPrice(4);

这样的类对象的初始化可以理解为“积极的”。

另一方面,反射在开始时不知道我要初始化的类对象是什么,也不能自然地使用。 new 创建对象的关键字。

此时,我们使用 JDK 所提供的反射 API 发出反射呼叫:

Class clz = Class.forName("com.chenshuyi.reflect.Apple");
Method method = clz.getMethod("setPrice", int.class);
Constructor constructor = clz.getConstructor();
Object object = constructor.newInstance();
method.invoke(object, 4);

上述两段代码的执行结果实际上完全相同。然而,这个想法完全不同。第一段代码已经确定了不运行时要运行的类(Apple),第二个代码是在运行时通过字符串值(com.chenshuyi.reflect.Apple)。

那么,什么是反思呢?

反射是当您知道要在运行时操作的类是什么时,您可以在运行时获得类的完整构造并调用相应的方法。

一个简单的例子

上面提到的示例程序具有以下完整的程序代码:

public class Apple {

    private int price;

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }

    public static void main(String[] args) throws Exception{
        //正常呼叫
        Apple apple = new Apple();
        apple.setPrice(5);
        System.out.println("Apple Price:" + apple.getPrice());
        //使用反射调用
        Class clz = Class.forName("com.chenshuyi.api.Apple");
        Method setPriceMethod = clz.getMethod("setPrice", int.class);
        Constructor appleConstructor = clz.getConstructor();
        Object appleObj = appleConstructor.newInstance();
        setPriceMethod.invoke(appleObj, 14);
        Method getPriceMethod = clz.getMethod("getPrice");
        System.out.println("Apple Price:" + getPriceMethod.invoke(appleObj));
    }
}

从代码中,我们可以看到我们使用反射来调用。 setPrice 方法,并传递 14 价值。在那之后,我带着沉思给他打电话。 getPrice 方法,输出其价格。以上代码的全部输出为:

Apple Price:5
Apple Price:14

从这个简单的例子中,我们可以看到,通常我们使用反射来获取对象步骤:

  • 获取类的 Class 对象实例

    Class clz = Class.forName("com.zhenai.api.Apple");

  • 根据 Class 对象实例获取 Constructor 对象

    Constructor appleConstructor = clz.getConstructor();

  • 使用 Constructor 对象的 newInstance 方法获取反射类对象。

    Object appleObj = appleConstructor.newInstance();

如果您想要调用一个方法,您需要经历以下步骤:

  • 获取方法。 Method 对象

    Method setPriceMethod = clz.getMethod("setPrice", int.class);

  • 利用 invoke 方法调用该方法。

    setPriceMethod.invoke(appleObj, 14);

在这里,我们已经能够掌握反射的基本用法。但是,如果你想进一步掌握反思,你还需要反思常用的用法。 API 对此有更深的理解。

在 JDK 在中,与反射相关 API 它可以分为以下几个方面:获得反思 Class 对象,通过反射创建类对象,通过反射获得类属性方法和构造函数。

反射常用API

拿到倒影。Class对象

在反射中,要获取类或调用类方法,我们首先需要获取类。 Class 对象。

在 Java API 中,获取 Class 有三种方法可以对对象进行分类:

首先,使用 Class.forName 静态方法。 当您知道类的完整路径名时,可以使用此方法获取它。 Class 类对象。

Class clz = Class.forName("java.lang.String");

第二,使用 .class 方法。

这种方法只适用于在编译前知道操作的情况。 Class。

Class clz = String.class;

第三,使用类对象的 getClass() 方法。

String str = new String("Hello");
Class clz = str.getClass();

通过反射创建类对象

通过反射创建类对象有两种主要方式:通过。 Class 对象的 newInstance() 方法,通过 Constructor 对象的 newInstance() 方法。

第一条:直通。 Class 对象的 newInstance() 方法。

Class clz = Apple.class;
Apple apple = (Apple)clz.newInstance();

第二: Constructor 对象的 newInstance() 方法

Class clz = Apple.class;
Constructor constructor = clz.getConstructor();
Apple apple = (Apple)constructor.newInstance();

通过 Constructor 对象创建类对象可以选择特定的构造方法,并通过。 Class 对象只能使用默认的参数化构造方法。下面的代码调用带有参数的构造方法来初始化类对象。

Class clz = Apple.class;
Constructor constructor = clz.getConstructor(String.class, int.class);
Apple apple = (Apple)constructor.newInstance("红富士", 15);

通过反射获取类属性、方法、构造函数。

我们通过 Class 对象的 getFields() 方法可以获得 Class 类,但无法获取私有属性。

Class clz = Apple.class;
Field[] fields = clz.getFields();
for (Field field : fields) {
    System.out.println(field.getName());
}

输出为:

price

如果您使用 Class 对象的 getDeclaredFields() 方法可以获取所有属性,包括私有属性:

Class clz = Apple.class;
Field[] fields = clz.getDeclaredFields();
for (Field field : fields) {
    System.out.println(field.getName());
}

输出为:

name
price

与获取类属性一样,当我们获取类方法、类构造函数时,如果我们想获取私有方法或私有构造函数,则必须使用 declared 关键字的方法。

反射源代码分析

当我们了解如何使用反射时,让我们今天来看看。 JDK 如何在源代码中实现反射。也许每个人在平时都没有使用过反射,但它正在发展中。 Web 在项目期间将遇到以下例外情况:

java.lang.NullPointerException 
...
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
  at java.lang.reflect.Method.invoke(Method.java:497)

您可以看到异常堆栈指示异常在中。 Method 的第 497 的 invoke 方法,实际上这指的是 invoke 该方法是我们的反射调用方法。 invoke。

Method method = clz.getMethod("setPrice", int.class); 
method.invoke(object, 4);   //就是这个invoke方法

例如,我们经常使用 Spring 配置时,往往有相关的 Bean 的配置:


当我们在 XML 在文件中配置上述配置后,Spring 反射将用于加载相应的 Apple 类。而当 Apple 当类不存在或发生启发式异常时,异常堆栈将异常指向被调用的 invoke 方法。

从这里可以看出,我们通常在许多框架中使用反射,最最终的反射是 Method 类的 invoke 方法了。

让我们来看看 JDK 的 invoke 这个方法到底做了什么。

进入 Method 的 invoke 方法我们可以看到,首先我们做了一些权限检查,最后我们调用 MethodAccessor 类的 invoke 该方法被进一步处理,如下面的红色框所示。

那么 MethodAccessor 那是什么?

其实 MethodAccessor 是定义方法调用的特定操作的接口,它有三个特定的实现类:

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

而要看 ma.invoke() 哪个类被称为 invoke 方法,您需要查看 MethodAccessor 该对象返回哪个类对象,因此我们需要输入。 acquireMethodAccessor() 看看它的方法。

从 acquireMethodAccessor() 方法我们可以看到,代码首先确定是否存在对应的 MethodAccessor 对象,如果存在那么就复用之前的 MethodAccessor 对象,否则调用 ReflectionFactory 对象的 newMethodAccessor 方法会生成一个 MethodAccessor 对象。

在 ReflectionFactory 类的 newMethodAccessor 方法,我们可以看到第一个是生成一个 NativeMethodAccessorImpl 对象,然后将该对象作为参数调用。 DelegatingMethodAccessorImpl 类的构造方法。

这里的实现是使用代理模式,这将是。 NativeMethodAccessorImpl 对象交给 DelegatingMethodAccessorImpl 对象代理。我们认为 DelegatingMethodAccessorImpl 类的构造方法是可以知道的,实际上是会知道的。 NativeMethodAccessorImpl 对象分配 DelegatingMethodAccessorImpl 类的 delegate 属性。

所以说ReflectionFactory 类的 newMethodAccessor 该方法最终返回 DelegatingMethodAccessorImpl 类对象。所以我们在前面 ma.invoke() 在,它将进入。 DelegatingMethodAccessorImpl 类的 invoke 方法中。

进入 DelegatingMethodAccessorImpl 类的 invoke 在该方法之后,在这里调用它。 delegate 属性的 invoke 方法,该方法又有两个实现类,即:DelegatingMethodAccessorImpl 和 NativeMethodAccessorImpl。根据我们前面所说的,在这里 delegate 它实际上是一个 NativeMethodAccessorImpl 对象,所以这里将进入。 NativeMethodAccessorImpl 的 invoke 方法。

而在 NativeMethodAccessorImpl 的 invoke 方法,该方法确定调用次数是否超过阈值(numInvocations)。如果超过此阈值,则会出现另一个MethodAccessor 对象,以及原始的 DelegatingMethodAccessorImpl 对象中的 delegate 属性指向最新的 MethodAccessor 对象。

在这里,事实上,我们可以知道 MethodAccessor 对象实际上是生成反射类的入口。通过查看源代码上的注释,我们可以看到 MethodAccessor 该对象的一些设计信息。

"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.

Inflation 机械装置。初始加载用于反射的字节码,使用 Method.invoke() 和 Constructor.newInstance() 加载所需的时间就是加载本机代码所需的时间。 3 - 4 时代周刊。这使得频繁使用反射的应用程序需要更长时间才能启动。

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.

为了避免这种痛苦的加载时间,我们在第一次加载时重用了它。 JVM 切换到字节码实现后的实现。

正如注释中所述,实际的 MethodAccessor 有两个版本的实现,一个是。 Native 版本,一个是 Java 版本。

Native 这个版本一开始启动得很快,但随着运行时间的延长,速度会变慢。Java 这个版本一开始加载速度很慢,但随着运行时间的延长,它会变得更快。正是由于这两个问题,我们会发现使用第一次加载。 NativeMethodAccessorImpl 实现,以及当反射调用的数量超过时。 15 在下一步之后,使用 MethodAccessorGenerator 生成的 MethodAccessorImpl 对象来实现反射。

Method 类的 invoke 该方法的整个过程可以用时间序列图表示如下:

在这点上,我们理解 Method 类的 invoke 该方法的具体实现。知道原版 invoke 该方法中有两个实现,一个是。 native 本机实现,一个是 Java 这两者是不同的。为了最大限度地提高性能,JDK 源代码使用代理设计模式来最大化性能。

原始地址: https://www.cnblogs.com/chanshuyi/p/head_first_of_reflection.html

版权声明

所有资源都来源于爬虫采集,如有侵权请联系我们,我们将立即删除

热门