>

Java反射机制

Java反射机制是什么?

Java反射机制是指在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法。

对于任意一个对象,都能够调用它的任意一个方法和属性,这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

用一句话总结就是反射可以实现在运行时可以知道任意一个类的属性和方法。

为什么要用反射?

Java Reflection功能非常强大,并且非常有用,比如:

  • 获取任意类的名称、package信息、所有属性、方法、注解、类型、类加载器等;

  • 获取任意对象的属性,并且能改变对象的属性;

  • 调用任意对象的方法;

  • 判断任意一个对象所属的类;

  • 实例化任意一个类的对象;

  • 通过反射我们可以实现动态装配,降低代码的耦合度,动态代理等。

Java反射机制原理

Java反射操作的是java.lang.Class对象,所以要理解Java反射机制,就需要搞懂Class对象。

比如举一个简单的例子,创建一个对象:

User user= new User();

在运行上面语句的时候,首先 JVM 启动,代码会编译成一个 .class 文件,然后被类加载器ClassLoader加载进 JVM 的内存中,并为之创建一个 java.lang.Class 对象。

我们的 User类会加载到方法区中,创建了的 User类的 Class 对象会到堆中。

理解 Java 的反射机制就是要理解 Class 类信息。

每个类的运行时的类型信息,用 Class 对象表示,又或者称之为字节码对象。

Class 文件的整体结构如下图所示:

它包含了与类相关的信息,而实例对象就是通过 Class 对象来创建的。

获取 Class 对象的方式:

  1. 实例对象调用 Object 类的getClass()方法;

  2. 通过属性类名.class直接获取;

  3. 调用 Class 类的forName()方法;

  4. 使用类加载器 ClassLoader 的getSystemClassLoader().loadClass()方法。

复制代码
package com.mikechen.reflection;
 
import com.mikechen.model.Person;
 
public class ReflectionTest {
    public static void main(String[] args) throws Exception {
        //1. 实例对象.getClass()
        Person person = new Person();
        Class clz1 = person.getClass();
        System.out.println(clz1);
 
        //2,类名.class属性
        System.out.println(Person.class);
 
        //3. Class.forName()
        Class clz2 = Class.forName("com.mikechen.reflection.Person");
        System.out.println(clz2);
    }
}
复制代码

以上三种方式都能获取 Person 类的字节码对象,但同时也存在区别:

  • 方法1:需要创建一个实例对象才能获取类的信息;

  • 方法2:则需要导入包否则无法通过编译;

  • 方法3:只需传入类名的字符串,这个类名是类完整路径;

我们一般平时是通过new的形式创建对象实际上就是通过这些Class来创建的,只不过这个class文件是编译的时候就生成的,程序相当于写死了给jvm去跑。

反射是什么呢?

当我们的程序在运行时,需要动态的加载一些类这些类可能之前用不到,所以不用加载到jvm,而是在运行时根据需要才加载。

Java反射的应用场景

面向开发,反射应用最广泛的是中间件和框架,比如:

  • JDBC的Class.forName(driverClass)加载驱动;

  • Spring MVC 通过反射调用 controller 的方法,动态代理处理请求;

  • Spring IOC 容器,在创建 Bean 实例时和依赖注入时的反射。

  • RMI 反序列化,反射调用远程方法;

  • RPC Dubbo动态代理利用;


 Java反射到底慢在哪?

摘自:https://www.jianshu.com/p/4e2b49fa8ba1

采用逐渐扩大测试次数、每次测试多次取平均值的方式,针对同一个方法分别就直接调用该方法、反射调用该方法、直接调用该方法对应的实例、反射调用该方法对应的实例分别从1-1000000,每隔一个数量级测试一次:
测试结果如下:

测试结论:

  • 反射的确会导致性能问题;

  • 反射导致的性能问题是否严重跟使用的次数有关系,如果控制在100次以内,基本上没什么差别,如果调用次数超过了100次,性能差异会很明显;

  • 四种访问方式,直接访问实例的方式效率最高;其次是直接调用方法的方式,耗时约为直接调用实例的1.4倍;接着是通过反射访问实例的方式,耗时约为直接访问实例的3.75倍;最慢的是通过反射访问方法的方式,耗时约为直接访问实例的6.2倍;

反射到底慢在哪?

  跟踪源码可以发现,四个方法中都存在实例化ProgramMonkey的代码,所以可以排除是这句话导致的不同调用方式产生的性能差异;通过反射调用方法中调用了setAccessible方法,但该方法纯粹只是设置属性值,不会产生明显的性能差异;所以最有可能产生性能差异的只有getMethod和getDeclaredField、invoke和set方法了,下面分别就这两组方法进行测试,找到具体慢在哪?

  首先测试invoke和set方法,修改getReflectCallMethodCostTime和getReflectCallFieldCostTime方法的代码如下:
测试结果如下:

修改getReflectCallMethodCostTime和getReflectCallFieldCostTime方法的代码如下,对getMethod和getDeclaredField进行测试:

测试结果如下:

测试结论:

  • getMethod和getDeclaredField方法会比invoke和set方法耗时;

  • 随着测试数量级越大,性能差异的比例越趋于稳定;

  由于测试的这四个方法最终调用的都是native方法,无法进一步跟踪。个人猜测应该是和在程序运行时操作class有关,比如需要判断是否安全?是否允许这样操作?入参是否正确?是否能够在虚拟机中找到需要反射的类?主要是这一系列判断条件导致了反射耗时;也有可能是因为调用natvie方法,需要使用JNI接口,导致了性能问题(参照Log.java、System.out.println,都是调用native方法,重复调用多次耗时很明显)。

如果避免反射导致的性能问题

通过上面的测试可以看出,过多地使用反射,的确会存在性能问题,但如果使用得当,所谓反射导致性能问题也就不是问题了,关于反射对性能的影响,参照下面的使用原则,并不会有什么明显的问题:

  • 不要过于频繁地使用反射,大量地使用反射会带来性能问题;

  • 通过反射直接访问实例会比访问方法快很多,所以应该优先采用访问实例的方式。

 
 
参考文章:
posted @   字节悦动  阅读(184)  评论(0编辑  收藏  举报
编辑推荐:
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
阅读排行:
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架
点击右上角即可分享
微信分享提示

目录导航