java 反射 座右铭:规则是用来打破的

在 Java语言中,反射是一种 强大而优秀 的机制,通过反射,可以在运行时 检查修改 类、接口、字段和方法的信息,甚至 动态地创建对象调用方法访问私有成员

可以毫不夸张地说,没有反射,很多优秀的框架不复存在,没有这些优秀的框架(比如Spring),Java可能会逊色很多。Show Time,一起来探讨Java反射以及其背后的原理。

一、什么是反射?

什么是反射?
Java 的反射机制是指在运行状态中,对于任意一个类都能够知道它所有的属性和方法,并且对于任意一个对象,都能够调用它的任何一个方法,这种动态获取信息以及动态调用对象方法的功能就是Java反射

Java反射最核心的类位于JDK源码 java.lang.reflect包下,比如Class、Constructor、Field 和 Method等,他们提供了对类和对象运行时信息进行检查和操作的方法。JDK源码截图如下:
在这里插入图片描述

二、反射的原理

反射的原理主要可以从下面 4个点来阐述:

  1. 类加载:当 Java程序运行时,类加载器会根据类的名称查找并加载类的字节码文件,然后将字节码文件转换为可执行的 Java类,并将其存储在运行时数据区域的方法区中。

  2. 创建 Class对象:在类加载过程中,Java虚拟机会自动创建对应的Class对象,Class对象包含了类的元数据信息,并提供了访问和操作类的接口。

  3. 获取 Class对象:Class对象通过多种方式获取,最常见的方式有 3种: 类的 .class属性、类实例的 getClass()方法、Class.forName()。

  4. 访问和操作:通过Class对象获取类的字段、方法、构造函数等信息,使用Field类和Method类来访问和操作字段和方法,甚至可以调用私有的字段和方法。

通过上述的分析可以看出:反射机制需要基于Java虚拟机对类的加载、存储和访问机制的支持,通过反射,可以在运行时动态地探索和操作类的信息,实现灵活的编程和代码的动态行为。

三、如何使用反射?

了解了Java反射原理,我们再通过一个例子来演示如何使用它,如下示例,通过反射给 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

运行结果如下图:
在这里插入图片描述
上述示例详细地展示了反射获取类的信息和动态调用方法。

四、部分源码解读

为了更好地理解反射,我们接下来分析部分核心源码,在上述示例的最后一步通过调用Method.invoke(),实现 Person.greet()的反射调用,因此,这里主要分析invoke(),官方源码截图如下:
在这里插入图片描述
从源码可以看出:Method.invoke()方法,真实返回的是接口MethodAccessor.invoke()方法。MethodAccessor接口有三个实现类,具体是调用哪个类的 invoke 方法?
在这里插入图片描述

进入acquireMethodAccessor()方法,可以看到MethodAccessorReflectionFactorynewMethodAccessor()方法决定,如下图:
在这里插入图片描述
在这里插入图片描述

接着进入DelegatingMethodAccessorImpl.invoke()方法,如下图:
在这里插入图片描述

DelegatingMethodAccessorImpl的invoke方法返回的是MethodAccessorImpl的invoke方法,而MethodAccessorImpl的invoke方法,由它的子类NativeMethodAccessorImpl重写,这时候返回的是native invoke0,如下图:

在这里插入图片描述
跟到源码最后可以发现:Method.invoke()方法最终调用 native的invoke0(),应用层面的操作最终转换成对操作系统 c/c++方法的调用。

五、反射优缺点

上面内容的讲解已经侧面反映出了Java反射的一些优点,这里再详细的总结下反射的优缺点:

优点:

  1. 动态性:反射允许我们在运行时动态地获取和操作类的信息,而不需要在编译时确定。这为编写灵活的、可扩展的代码提供了便利。

  2. 灵活性:通过反射,我们可以绕过访问修饰符的限制,访问和修改私有成员、调用私有方法等。这为我们在特殊情况下进行一些高级操作提供了可能。

  3. 框架开发:反射在开发框架和库时非常有用。通过反射,框架可以动态地加载和实例化类,解析注解,处理回调等。这为框架提供了更大的灵活性和可扩展性。

  4. 调试和探索:反射使得我们可以在运行时探索代码背后的信息,例如获取类的结构、方法、字段等。这对于调试和理解复杂的代码非常有帮助。

缺点:

  1. 性能开销:相比于直接调用代码,使用反射会带来更高的性能开销。反射涉及到动态查找、方法调用等操作,这些操作比直接调用代码更加耗时。因此,在对性能要求较高的场景下,过度使用反射可能导致性能下降。

  2. 安全性和稳定性:反射打破了封装性和类型安全性,通过反射,我们可以绕过访问修饰符的限制,调用私有方法等,这可能导致代码的不稳定性和安全隐患。因此,使用反射时需要格外小心,确保代码的正确性和稳定性。

从整体上看,Java反射是以牺牲了小部分的性能换取了更好的扩展性和灵活性,牺牲小我成就大我,而且,随着现代硬件设备能力越来越强,这点小性能的牺牲是完全值得的。

六、为什么需要反射?

反射机制在 Java中的作用不言而喻,下面列举了反射机制的一些常见场景和原因:

  1. 运行时类型检查:反射机制允许在运行时获取类的信息,包括字段、方法和构造方法等。因此,在进行运行时类型检查,以确保代码在处理不同类型的对象时能够正确地进行操作。

  2. 动态创建对象:通过反射,可以在运行时动态地创建对象,而不需要在编译时知道具体的类名。这对于某些需要根据条件或配置来创建对象的情况非常有用,例如工厂模式或依赖注入框架。

  3. 访问和修改私有成员:反射机制可以绕过访问权限限制,访问和修改类的私有字段和方法。虽然这破坏了封装性原则,但在某些特定情况下,这种能力可以帮助我们进行一些特殊操作,例如单元测试、调试或框架的内部实现。

  4. 动态调用方法:反射机制允许我们在运行时动态地调用类的方法,甚至可以根据运行时的条件来选择不同的方法。这对于实现插件化系统、处理回调函数或实现动态代理等功能非常有用。

  5. 框架和库的实现:许多Java框架和库在其实现中广泛使用了反射机制。它们利用反射来自动发现和加载类、实现依赖注入、处理注解、配置文件解析和动态代理等。反射机制使得这些框架和库更加灵活和扩展。

七、常用框架

很多优秀的框架内部都使用了Java反射,这里重点讲解下给 Java打下半壁江山的 Spring生态(Spring Framework,Spring MVC,SpringBoot, SpringCloud…),以 Spring Framework为例:

  1. 依赖注入(Dependency Injection) : 依赖注入,可以把程序员主动创建对象的事情交给 Spring管理,大大提升了对象创建的灵活性。当我们在配置文件或用注解定义 Bean时,Spring会使用反射来动态地实例化对象,并将依赖的其他对象注入到这些实例中。

  2. 自动装配(Autowired) : 当 Spring容器启动时,它会扫描应用程序中的所有类,并使用反射来查找和识别带有 @Autowired注解的字段、方法或构造函数。再自动将 Bean注入到需要的位置,实现对象之间的自动连接。

  3. AOP(Aspect-Oriented Programming) : AOP 利用了动态代理和反射机制。通过定义切面(Aspect)和切点(Pointcut),Spring可以在运行时使用反射来创建代理对象,从而实现横切关注点(cross-cutting concerns)的功能,如日志记录、事务管理等。

  4. 动态代理(Dynamic Proxy) : Spring利用 Java反射机制动态地创建代理对象,并在代理对象中添加额外的逻辑,从而实现对目标对象的增强。

  5. 框架扩展和定制: Spring通过反射机制来实现对应用程序的扩展和定制的。例如,Spring提供了BeanPostProcessor接口,允许开发人员在 Bean初始化前后插入自定义逻辑,这是通过反射来实现的。

另外,还有一些耳熟能详的框架也使用了Java反射

  1. JUnit:JUnit是一个优秀的单元测试框架,它利用了 Java反射机制动态地加载和执行测试方法。

  2. Jackson:Jackson是一个 JSON处理的 Java库,它利用反射来实现 JSON与 Java对象之间的转换,动态读取和写入 Java对象的属性,并将其转换为 JSON格式。

  3. Hibernate ORM:Hibernate和 MyBatis一样,都是对象关系映射框架,通过反射来实现对象与数据库表之间的映射关系。

好文分享,一起加油!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值