在 Java语言中,反射是一项强大而神秘的技术,它为我们提供了一种探索代码背后力量的方式。通过反射,我们可以在运行时检查和修改类、接口、字段和方法的信息,甚至可以动态地创建对象、调用方法和访问私有成员。今天我们将深入探讨 Java反射机制的原理以及使用。
一、什么是反射
先看看 Oracle官方对java反射的说明:
Reflection enables Java code to discover information about the fields, methods and constructors of loaded classes, and to use reflected fields, methods, and constructors to operate on their underlying counterparts, within security restrictions. The API accommodates applications that need access to either the public members of a target object (based on its runtime class) or the members declared by a given class. It also allows programs to suppress default reflective access control.
Java 的反射机制是指在运行状态中,对于任意一个类都能够知道这个类所有的属性和方法;并且对于任意一个对象,都能够调用它的任意一个方法;这种动态获取信息以及动态调用对象方法的功能成为Java语言的反射机制。
它是通过 Java反射 API 来实现,其中最核心的类位于 java.lang.reflect 包下,如 Class、Constructor、Field 和 Method等,这些类提供了对类和对象的运行时信息进行检查和操作的方法。如下图,展示了 JDK源码中 java.lang.reflect 包所有的类:
二、反射的原理
反射的原理主要可以从下面4个点来阐述:
- 类加载:当 Java程序运行时,类加载器根据类的名称查找并加载类的字节码文件。类加载器将字节码文件转换为可执行的 Java类,并将其存储在运行时数据区域的方法区中。
- 创建Class对象:在类加载过程中,Java虚拟机会自动创建对应的 Class对象。这个Class对象包含了类的元数据信息,并提供了访问和操作类的接口。
- 获取Class对象:我们可以通过多种方式获取 Class对象。常见的方式有 3种: 类的 .class属性、类实例的 getClass()方法、Class.forName()。
- 访问和操作:通过Class对象,我们可以获取类的字段、方法、构造函数等信息。我们可以使用Field类和Method类来访问和操作字段和方法,甚至可以调用私有的字段和方法。
反射机制的原理是基于Java虚拟机对类的加载、存储和访问机制的支持。通过反射,我们可以在运行时动态地探索和操作类的信息,实现灵活的编程和代码的动态行为。
三、如何使用反射
在讲解了 Java反射原理之后,我们通过一个真实的例子来展示如何使用 Java反射机制。如下示例 demo,通过反射给 Person 类中的 greet() 方法传入一个 name,然后输出:
分析:
- 首先,在这个示例中,我们通过获 Person.class 取了 Person 的 Class对象;
- 然后,使用clazz.getName()获取了类的名称,通过 clazz.getModifiers()获取了类的修饰符,并打印输出;
- 接下来,通过 clazz.getDeclaredMethods() 获取类的所有方法,并依次打印输出方法的名称;
- 接着,通过 clazz.getDeclaredConstructor().newInstance()方法创建了 Person 的实例;
- 再接着,使用 clazz.getDeclaredMethod()方法获取了greet()方法的引用。为了调用私有方法,我们需要调用setAccessible(true)来设置方法的可访问性。
- 最后,使用 Method.invoke()方法调用了greet()方法,传递参数 name = Java。
运行示例结果如下图:
上述示例,我们通过详细的步骤展示了如何使用反射获取类的信息和动态调用方法。你也可以尝试在 Person 中添加更多的方法和字段,并使用反射来获取和操作它们。
四、部分源码解读
在上述示例讲解时,最后是调用 Method.invoke() 实现 Person.greet()的调用,因此,这里我们主要分析 invoke()方案,官方源码截图:
从上面源码截图看出:Method.invoke() 方法,真实返回的是接口 MethodAccessor.invoke()方法。MethodAccessor 接口有三个实现类,具体是调用哪个类的 invoke 方法?
进入acquireMethodAccessor方法,可以看到MethodAccessor由ReflectionFactory 的 newMethodAccessor方法决定。
再进入 DelegatingMethodAccessorImpl 的 invoke方法:
DelegatingMethodAccessorImpl的invoke方法返回的是MethodAccessorImpl的invoke方法,而MethodAccessorImpl的invoke方法,由它的子类NativeMethodAccessorImpl重写,这时候返回的是native invoke0,如下图:
因此,Method.invoke() 方法,是由 native 的invoke0决定的,该方法是操作系统再 c/c++ 提供。
五、反射优缺点
反射是一项强大的技术,可以让我们在运行时动态地获取和操作类的信息。然而,反射也有其优点和缺点。下面是反射的一些优缺点:
优点:
- 动态性:反射允许我们在运行时动态地获取和操作类的信息,而不需要在编译时确定。这为编写灵活的、可扩展的代码提供了便利。
- 灵活性:通过反射,我们可以绕过访问修饰符的限制,访问和修改私有成员、调用私有方法等。这为我们在特殊情况下进行一些高级操作提供了可能。
- 框架开发:反射在开发框架和库时非常有用。通过反射,框架可以动态地加载和实例化类,解析注解,处理回调等。这为框架提供了更大的灵活性和可扩展性。
- 调试和探索:反射使得我们可以在运行时探索代码背后的信息,例如获取类的结构、方法、字段等。这对于调试和理解复杂的代码非常有帮助。
缺点:
- 性能开销:相比于直接调用代码,使用反射会带来更高的性能开销。反射涉及到动态查找、方法调用等操作,这些操作比直接调用代码更加耗时。因此,在对性能要求较高的场景下,过度使用反射可能导致性能下降。
- 安全性和稳定性:反射打破了封装性和类型安全性。通过反射,我们可以绕过访问修饰符的限制,调用私有方法等。这可能导致代码的不稳定性和安全隐患。使用反射时需要格外小心,确保代码的正确性和稳定性。
- 可读性和可维护性:反射使得代码变得更加动态和复杂,增加了代码的复杂性和可读性的难度。使用过多的反射可能导致代码难以理解和维护,降低代码的可读性和可维护性。
综上所述,反射是一项强大的技术,但需要谨慎使用。在实际项目中,应根据具体情况权衡利弊,合理使用反射,遵循封装和类型安全的原则,以确保代码的可读性、可维护性和性能。
六、为什么需要反射
反射机制在 Java中具有重要的作用和价值,它为我们提供了在运行时动态地获取和操作类的能力。以下是一些使用反射机制的常见场景和原因:
- 运行时类型检查:反射机制允许我们在运行时获取类的信息,包括字段、方法和构造方法等。这使得我们可以进行运行时类型检查,以确保代码在处理不同类型的对象时能够正确地进行操作。
- 动态创建对象:通过反射机制,我们可以在运行时动态地创建对象,而不需要在编译时知道具体的类名。这对于某些需要根据条件或配置来创建对象的情况非常有用,例如工厂模式或依赖注入框架。
- 访问和修改私有成员:反射机制使我们能够绕过访问权限限制,访问和修改类的私有字段和方法。虽然这破坏了封装性原则,但在某些特定情况下,这种能力可以帮助我们进行一些特殊操作,例如单元测试、调试或框架的内部实现。
- 动态调用方法:反射机制允许我们在运行时动态地调用类的方法,甚至可以根据运行时的条件来选择不同的方法。这对于实现插件化系统、处理回调函数或实现动态代理等功能非常有用。
- 框架和库的实现:许多Java框架和库在其实现中广泛使用了反射机制。它们利用反射来自动发现和加载类、实现依赖注入、处理注解、配置文件解析和动态代理等。反射机制使得这些框架和库更加灵活和扩展。
需要注意的是,虽然反射机制提供了灵活性和动态性,但它也带来了一些潜在的性能开销和安全风险。因此,在使用反射时需要谨慎,并权衡其优缺点。它应该被视为一种强大的工具,用于特定的情况和需求,而不是滥用或不必要地使用。
七、常用框架
反射在Java开发中有许多常用的框架和库,它们利用了反射的能力来提供更强大的功能和灵活性。以下是一些常用的使用反射的框架和库:
- Spring Framework:Spring是一个广泛使用的 Java开发框架,它在很多地方使用了反射。例如,Spring的依赖注入(DI)机制通过反射来实现自动装配,将对象的依赖关系动态地注入到目标对象中。
- Hibernate:Hibernate是一个 Java持久化框架,它利用反射来实现对象与数据库表之间的映射。通过反射,Hibernate可以动态地获取实体类的字段和属性,并将其映射到数据库表的列。
- JUnit:JUnit是一个流行的 Java单元测试框架,它使用反射来执行测试方法。JUnit 通过反射动态地查找并执行带有特定注解的测试方法,从而实现自动化的单元测试。
- Jackson:Jackson是一个用于 JSON处理的 Java库,它利用反射来实现 JSON 与 Java对象之间的转换。Jackson可以通过反射来动态地读取和写入 Java对象的属性,并将其转换为 JSON格式。
- Spring MVC:Spring MVC是 Spring框架的一个模块,用于构建 Web应用程序。它使用反射来处理 HTTP请求并调用相应的处理方法。通过反射,Spring MVC可以根据请求的URL和参数动态地调用对应的控制器方法。
这里只列举了一小部分使用反射的框架和库的示例。实际上,反射在许多 Java开发领域都有广泛的应用,包括依赖注入框架、ORM框架、测试框架、序列化库等等。通过利用反射,这些框架和库能够提供更大的灵活性和功能扩展性,帮助我们更好地开发和维护Java应用程序。
八、总结
本文讲解了 Java反射的原理和使用方式,有了反射机制可以让我们更灵活的操作 Java,因此,很多优秀的框架也应孕而生,从而使得 Java 生态越来越完善,毫不夸张的说:反射是绝大多数框架的基石。
了解完 Java反射机制,我们能很清晰的知道,为什么很多框架在完全不知道业务代码会如何编写的前提下,能够灵活操作代码,比如:Spring 的 DI(依赖注入),这就是反射的强大之处,即实现了业务代码和框架的解耦,又方便框架灵活管理与使用业务代码。