反射机制详解

✅作者简介:大家好,我是Leo,热爱Java后端开发者,一个想要与大家共同进步的男人😉😉
🍎个人主页:Leo的博客
💞当前专栏:Java从入门到精通
✨特色专栏:MySQL学习
🥭本文内容:反射机制详解
📚个人知识库: Leo知识库,欢迎大家访问

目录

    • 前言
    • 什么是反射
    • 反射的场景
      • 1. 动态对象创建
        • 示例代码:
      • 2. 动态方法调用
        • 示例代码:
      • 3. 访问和修改私有字段
        • 示例代码:
      • 4. 框架和库的实现
        • Spring 示例:
      • 5. 动态代理
        • 示例代码:
    • 反射的基础
    • 认识Class类
    • 认识类加载
      • 类加载机制
      • 类的加载流程
    • 学会使用反射
      • 获取Class对象的几种方式
        • 1. 通过类名(静态方式)
        • 2. 通过对象的 `getClass()` 方法(动态方式)
        • 3. 通过 `Class.forName()` 方法(动态加载)
        • 4. 通过类加载器(ClassLoader)
        • 说明:
      • API基本使用
    • 反射的基本原理

前言

大家好,我是Leo哥🫣🫣🫣,今天一起来回顾一下反射机制。

什么是反射

反射(Reflection) 是编程语言中的一种能力,它允许程序在运行时动态地检查和操作程序元素,比如类、方法、字段等。通俗地说,反射就是程序能够在运行时查看自己并做出相应调整的能力。

如果了解过框架底册的话,那么对反射一定不陌生。

反射之所以被称为框架的灵魂,主要是因为它赋予了我们在运行时分析类以及执行类中方法的能力。

假设你有一辆汽车,通常你需要知道汽车的型号、颜色等信息来驾驶它。但如果汽车具备 反射 能力,它自己就能告诉你它的型号、颜色,甚至你可以通过特定的方法直接改变它的某些属性。编程中的反射就是这样一种机制,让程序能够动态地查看和修改自身。

反射的场景

平时大部分时候都是在写业务代码,很少会接触到直接使用反射机制的场景。

但是,这并不代表反射没有用。相反,正是因为反射,你才能这么轻松地使用各种框架。像 Spring、Spring Boot、MyBatis 等等框架中都大量使用了反射机制。

其实反射有许多具体的应用场景的。

反射是一种强大的工具,它允许程序在运行时检查和操作类的结构,包括类的属性、方法和构造函数。反射有许多具体的应用场景,以下是一些常见的应用场景和示例:

1. 动态对象创建

反射可以根据类名在运行时创建对象实例。这对于工厂模式或需要根据配置文件创建实例的场景非常有用。

示例代码:
Class<?> clazz = Class.forName("com.example.MyClass");
Object instance = clazz.getDeclaredConstructor().newInstance();

2. 动态方法调用

反射允许在运行时调用对象的方法,这在需要根据配置或用户输入决定调用哪个方法时特别有用。

示例代码:
Method method = clazz.getMethod("myMethod", String.class);
method.invoke(instance, "Hello");

3. 访问和修改私有字段

反射可以绕过访问控制机制访问和修改私有字段。这对于调试、框架开发和需要操作内部状态的场景非常有用。

示例代码:
Field field = clazz.getDeclaredField("privateField");
field.setAccessible(true);
field.set(instance, "newValue");

4. 框架和库的实现

许多流行的框架和库(如 Spring、Hibernate)使用反射来实现依赖注入、对象关系映射和自动化测试等功能。

Spring 示例:

Spring 使用反射来自动注入依赖项,例如:

@Autowired
private MyService myService;

Spring 在运行时使用反射来查找和注入 MyService 的实例。

5. 动态代理

Java 动态代理机制基于反射,允许在运行时创建接口的代理实例。动态代理常用于 AOP(面向切面编程)和拦截器模式。

示例代码:
InvocationHandler handler = new MyInvocationHandler(targetObject);
MyInterface proxy = (MyInterface) Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),targetObject.getClass().getInterfaces(),handler
);

其实注解中也大量使用到了反射机制,例如,我们比较熟悉的Spring框架,我们通过使用一个@Component注解就声明了一个类为 Spring Bean 呢?为什么你通过一个 @Value注解就读取到配置文件中的值呢?

这些都是因为反射机制,获取到类/属性/方法/方法的参数上的注解。你获取到注解之后就可以做进一步处理了。

反射的基础

简单来说,反射就是为把Java类中的各种成分映射成一个个的Java对象。

例如:一个类有:成员变量、方法、构造方法、包等等信息,利用反射技术可以对一个类进行解剖,把个个组成部分映射成一个个对象。

这里我们首先需要理解 Class类,以及类的加载机制。 然后基于此我们如何通过反射获取Class类以及类中的成员变量、方法、构造方法等。

认识Class类

在 Java 反射中,Class 类是核心部分。它表示正在运行的 Java 应用程序中的类或接口。每个类或接口都有一个与之对应的 Class 对象。当 JVM 加载某个类时,它会创建一个 Class 对象来表示这个类。

Class类,Class类也是一个实实在在的类,存在于JDK的java.lang包中。Class类的实例表示java应用运行时的类(class ans enum)或接口(interface and annotation)(每个Java类运行时都在JVM里表现为一个class对象,可通过类名.class、类型.getClass()、Class.forName(“类名”)等方法获取Class对象)。

image-20240620134343301

通过Class的源码,我们可以得出以下结论:

  • Class类也是类的一种,与class关键字是不一样的。
  • Class<T>类实现了多个接口,包括SerializableGenericDeclarationTypeAnnotatedElement,并且不能被继承(使用了final关键字)。
  • 定义了三个私有静态常量ANNOTATIONENUMSYNTHETIC,分别用于标识注解、枚举和合成类。
  • Class类有一个私有构造函数,只有JVM能够调用,用于初始化Class对象,防止默认构造函数的生成。
  • 私有构造函数通过参数ClassLoader初始化classLoader字段,确保其非空以避免JIT优化问题。

认识类加载

类加载机制

Java类的加载过程包括加载、验证、准备、解析、初始化、使用和卸载七个步骤。加载阶段将类字节码加载到内存中,验证阶段确保类的字节码符合规范,准备阶段为静态变量分配内存,解析阶段将符号引用转换为直接引用,初始化阶段执行类的初始化代码,使用阶段允许类实例化和使用,最后在卸载阶段回收类的内存。

image-20240620134403072

类的加载流程

首先就是编译过程。Java源代码文件(如Person.javaCar.java)被编译成字节码文件(如Person.classCar.class)。

然后就是进入了类加载,类加载器(Class Loader) 负责加载字节码文件。字节码文件可以来自本地文件系统、网络等不同来源。

在方法区中,类加载器将加载的类字节码文件存储到方法区中,并为每个类创建一个对应的数据结构(如Person类数据结构Car类数据结构)。

在堆区中,JVM在堆区中为每个类创建一个唯一的Class对象(如Person Class对象Car Class对象),用于表示该类的元数据。

最后,当程序创建类的实例时(如Person类的实例P1P2等,和Car类的实例C1C2等),这些实例对象在堆区中被分配内存。每个实例对象通过其Class对象获取类的元数据,从而实现反射机制,即可以获取类的内部信息(如方法、属性、构造函数等)以及反向控制实例对象的能力。

通过下面这张图你能更清晰的了解到类加载机制。

image-20240620134425190

学会使用反射

获取Class对象的几种方式

要通过反射获取具体的信息,那么首先需要获取到Class对象。Class 类对象将一个类的方法、变量等信息告诉运行的程序。

1. 通过类名(静态方式)

这是获取 Class 对象最常用和最简单的一种方式,适用于在编译时已知类的情况下。

public class MyClass {// 类的定义
}public class Main {public static void main(String[] args) {Class<?> clazz = MyClass.class;System.out.println("Class name: " + clazz.getName());}
}
  • 使用 MyClass.class 可以直接获取 MyClassClass 对象。
  • 这种方式在编译时检查类的存在和合法性,如果类不存在或拼写错误,编译时就会报错。
2. 通过对象的 getClass() 方法(动态方式)

当你已经有一个类的实例对象时,可以通过该对象的 getClass() 方法获取其 Class 对象。

public class MyClass {// 类的定义
}public class Main {public static void main(String[] args) {MyClass obj = new MyClass();Class<?> clazz = obj.getClass();System.out.println("Class name: " + clazz.getName());}
}
  • 使用实例对象的 getClass() 方法可以获取该对象的 Class 对象。
  • 这种方式适用于在运行时已经有类实例的情况下。
3. 通过 Class.forName() 方法(动态加载)

这种方式适用于需要在运行时根据类名字符串动态加载类的场景。

public class Main {public static void main(String[] args) {try {Class<?> clazz = Class.forName("com.example.MyClass");System.out.println("Class name: " + clazz.getName());} catch (ClassNotFoundException e) {e.printStackTrace();}}
}
  • 使用 Class.forName("com.example.MyClass") 可以动态加载 MyClass 类。
  • 这种方式适用于在运行时需要根据类名字符串加载类的场景,例如配置文件中指定类名。
4. 通过类加载器(ClassLoader)

这种方式用于需要自定义类加载器的场景,例如在开发插件系统或模块化系统时。

public class Main {public static void main(String[] args) {try {ClassLoader classLoader = Main.class.getClassLoader();Class<?> clazz = classLoader.loadClass("com.example.MyClass");System.out.println("Class name: " + clazz.getName());} catch (ClassNotFoundException e) {e.printStackTrace();}}
}
说明:
  • 使用类加载器的 loadClass() 方法可以加载指定类。
  • 这种方式适用于需要自定义类加载逻辑的场景。

API基本使用

image-20240620134535255

image-20240620134602235

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;public class ReflectionExample {public static void main(String[] args) {try {// 获取类的 Class 对象Class<?> clazz = Class.forName("com.example.MyClass");// 获取类名System.out.println("Class Name: " + clazz.getName());// 获取修饰符int modifiers = clazz.getModifiers();System.out.println("Is Public: " + Modifier.isPublic(modifiers));System.out.println("Is Abstract: " + Modifier.isAbstract(modifiers));// 获取父类Class<?> superClass = clazz.getSuperclass();System.out.println("Superclass: " + superClass.getName());// 获取实现的接口Class<?>[] interfaces = clazz.getInterfaces();System.out.println("Interfaces:");for (Class<?> iface : interfaces) {System.out.println(iface.getName());}// 获取字段Field[] fields = clazz.getDeclaredFields();System.out.println("Fields:");for (Field field : fields) {System.out.println(field.getName() + " - " + field.getType().getName());}// 获取方法Method[] methods = clazz.getDeclaredMethods();System.out.println("Methods:");for (Method method : methods) {System.out.println(method.getName() + " - " + method.getReturnType().getName());}// 获取构造函数Constructor<?>[] constructors = clazz.getDeclaredConstructors();System.out.println("Constructors:");for (Constructor<?> constructor : constructors) {System.out.println(constructor.getName());}// 创建实例Constructor<?> constructor = clazz.getConstructor();Object instance = constructor.newInstance();System.out.println("Instance: " + instance);// 调用方法Method method = clazz.getMethod("methodName", String.class);method.invoke(instance, "Hello");// 访问和修改字段Field field = clazz.getDeclaredField("fieldName");field.setAccessible(true);field.set(instance, "newValue");System.out.println("Field Value: " + field.get(instance));} catch (Exception e) {e.printStackTrace();}}
}

反射的基本原理

首先我们编写的 Java 源文件(.java)被保存在磁盘上。然后,这些源文件通过 Java 编译器(javac)编译生成字节码文件(.class)。在编译过程中,编译器会检查源文件中的语法错误,如果存在语法错误,将无法生成字节码文件。接着,JVM 将这些字节码文件加载到内存中。

Java 是面向对象的编程语言,所有事物都可以被看作对象。当字节码文件被加载到内存中时,JVM 会将其表示为一个 Class 对象。这个 Class 对象包含了与类有关的所有信息。

一旦获得了 Class 对象,就可以通过反射机制操作类中的所有内容。Java 将类的成员变量、构造器、成员方法都看作对象,并将其封装到相应的类中。通过 Class 对象,可以调用相应的方法获取类的成员变量对象、构造器对象、成员方法对象。然后,通过这些对象调用方法,就相当于该类在操作自身的属性、构造器和成员方法。这就是反射的基本原理。

每个类在编译后都会生成一个同名的 .class 文件,该文件保存着 Class 对象的内容。类加载实际上是 Class 对象的加载,类在第一次使用时才动态加载到 JVM 中。也可以使用 Class.forName("com.mysql.jdbc.Driver") 这种方式来控制类的加载,该方法会返回一个 Class 对象。反射提供了运行时获取类信息的能力,允许在运行时加载类,即使在编译时期该类的 .class 文件不存在,也可以在运行时加载并使用反射机制操作。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/30773.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SM9加密算法:安全、高效的国产密码技术

随着信息技术的飞速发展&#xff0c;网络安全问题日益凸显。加密算法作为保障信息安全的核心技术&#xff0c;受到了广泛关注。在我国&#xff0c;一种名为SM9的加密算法逐渐崭露头角&#xff0c;凭借其卓越的安全性能和高效计算能力&#xff0c;成为了新一代国产密码技术的代表…

常用的Java日志框架:Log4j、SLF4J和Logback

日志是软件开发中不可或缺的一部分&#xff0c;它有助于记录应用程序的运行状态、调试问题和监控系统。Java中有多个流行的日志框架&#xff0c;如Log4j、SLF4J和Logback。 一、Log4j 1.1 什么是Log4j&#xff1f; Log4j是Apache基金会开发的一个开源日志框架&#xff0c;它…

Milvus跨集群数据迁移

将 Milvus 数据从 A 集群&#xff08;K8S集群&#xff09;迁到 B 集群&#xff08;K8S集群&#xff09;&#xff0c;解决方案很多&#xff0c;这里提供一个使用官方 milvus-backup 工具进行数据迁移的方案。 注意&#xff1a;此方案为非实时同步方案&#xff0c;但借助 MinIO 客…

C++基础std::bind

目录 说明 举例子&#xff1a; 说明 std::bind是一个函数模板&#xff0c;用于创建一个可调用对象&#xff0c;该对象可以在稍后的时候被调用。bind的作用是将函数与参数绑定在一起&#xff0c;在调用时可以自动传入预定的参数值。 std::bind的基本语法如下&#xff1a; templ…

1. zabbix监控服务器部署

zabbix监控服务器部署 一、监控的作用1、监控的方式2、zabbix监控获取数据的方式 二、zabbix server部署1、确保时间同步2、添加epel源3、添加zabbix仓库4、安装zabbix服务端软件5、在数据库创建zabbix需要的表、授权用户6、编辑zabbix server配置文件&#xff0c;指定数据库连…

在WordPress中使用AI的实用方法:入门级

随着人工智能&#xff08;AI&#xff09;的快速发展&#xff0c;WordPress平台上引入了越来越多的工具和插件&#xff0c;为网站管理员提供了强大的功能。这些工具不仅可以提升网站的用户体验&#xff0c;还能简化网站管理过程。本文将介绍几种在WordPress中使用AI的实用方法&a…

广州化工厂可燃气体报警器检定检验:安全生产新举措显成效

随着科技的不断发展&#xff0c;可燃气体报警器的检定检验技术也在不断进步。 广州的一些化工厂开始采用先进的智能检测系统和数据分析技术&#xff0c;对报警器的性能进行更加精准和全面的评估。 这些新技术不仅能够提高检定检验的效率和准确性&#xff0c;还能够为化工厂的…

大数据的力量:推动战略决策和业务转型

在当前全球化的时代背景下&#xff0c;国际间的联系日益紧密&#xff0c;世界变得更加互联互通。面对各种危机&#xff0c;数据驱动决策和分析显得愈发重要。从医学研究到市场趋势分析&#xff0c;大数据技术在各个领域发挥着关键作用&#xff0c;推动着一场深刻的变革浪潮。 大…

打开IE自动跳转EDGE的解决方法

目录 1. 创建快捷方式的解决方案 2. 其他可以尝试但未必靠谱的方法 2.1 通过设置EDGE浏览器实现 2.2 设置internet属性 2.3 BHO拓展管理 找到Windows10中的IE浏览器的方法&#xff1a; WIN Q&#xff0c;打开搜索栏&#xff1b;键入IE&#xff0c;即可看到IE浏览器 1. …

Java Stream流应用

Stream流的核心方法 Stream流的方法主要包含如图的几种 提供部分应用场景做个思考&#xff1a; (1)从员工集合中筛选出salary大于8000的员工&#xff0c;并放置到新的集合里。 (2)统计员工的最高薪资、平均薪资、薪资之和。 (3)将员工按薪资从高到低排序&#xff0c;同样薪资…

深度理解微信小程序技术架构:从前端到后台

在当今移动互联网的时代&#xff0c;微信小程序作为一种轻量级、便捷的应用形式&#xff0c;已经成为许多用户和开发者的首选。本文将深入探讨微信小程序的技术架构&#xff0c;从前端视角到后台支撑&#xff0c;为读者全面解析这一新兴应用形式的奥秘。 #### 一、微信小程序的…

opencv中凸包运算函数convexHull()的使用

操作系统&#xff1a;ubuntu22.04OpenCV版本&#xff1a;OpenCV4.9IDE:Visual Studio Code编程语言&#xff1a;C11 1.功能描述 该函数cv::convexHull用于寻找一组二维点集的凸包&#xff0c;采用的是Sklansky算法[242]&#xff0c;当前实现中具有O(N logN)的时间复杂度。 1…

2024: 有效使用OKR的10个技巧

2023年是许多前所未有的一年。从真正意义上讲&#xff0c;这一年让我们为不可预测的事情做好了准备&#xff0c;也为不确定的事情提供了训练。在我们身边发生了这么多事情&#xff0c;而下一步的行动却依然不甚明朗的情况下&#xff0c;领导者们更应该开始制定战略&#xff0c;…

Linux服务器挖矿病毒处理

文章目录 Linux服务器挖矿病毒处理1.中毒表现2.解决办法2.1 断网并修改root密码2.2 找出隐藏的挖矿进程2.3 关闭病毒启动服务2.4 杀掉挖矿进程 3. 防止黑客再次入侵3.1 查找异常IP3.2 封禁异常IP3.3 查看是否有陌生公钥 补充知识参考 Linux服务器挖矿病毒处理 情况说明&#x…

FuTalk设计周刊-Vol.033

&#x1f525;AI漫谈 热点捕手 1、Stable Video Diffusion —— Stable Diffusion 推出的 AI 生成视频模型 Stable Video Diffusion 也是开源的&#xff0c;可以免费下载部署。支持文本/图片生成视频&#xff0c;最高支持 576*1024 分辨率 25 帧。 链接https://huggingface.…

DV、OV通配符SSL证书有什么区别

通配符SSL证书是经常提及的一种SSL证书类型&#xff0c;也被称为泛域名SSL证书。通配符证书在SSL证书当中是比较特殊的&#xff0c;它具有保护主域名及其下一级所有子域名的功能&#xff0c;非常适合子域名多的域名网站&#xff0c;能够有效的节省成本&#xff0c;并降低证书管…

申办乙级资信证书,河南工程咨询单位流程详解

河南工程咨询单位申办乙级资信证书的流程详解如下&#xff1a; 一、前期准备阶段 研读政策文件&#xff1a; 研读《工程咨询行业管理办法》&#xff08;国家发展改革委2017年第9号令&#xff09;以及《国家发展改革委关于印发<工程咨询单位资信评价标准>的通知》&#x…

调建堆的时间复杂度的计算与topK问题

建堆复杂度的计算 向下调整建堆 第一层有2^0个节点&#xff0c;最坏向下调整h-1次&#xff0c;第二层有2^1个节点&#xff0c;最坏向下调整h-2次&#xff0c;以此类推&#xff0c;将每一层所有节点最坏情况需要调整的次数相加&#xff0c;就能得到一个式子&#xff1a; 最后到…

FanySkill源文件修改,快捷键以及新增任务栏选项

FanySkill源文件修改&#xff0c;快捷键以及新增任务栏选项 一、文件下载二、快捷键设置三、任务栏四、本人配置 一、文件下载 自行去PCB联盟网下载&#xff0c;因为那边下载要联盟币&#xff0c;我不清楚我免费提供会不会给我带来没必要的损失&#xff0c;敬请谅解。 要下载的…

怎么为自己的VPS选择合适的CPU和RAM?

为网站选择VPS&#xff08;虚拟专用服务器&#xff09;与为家庭或办公室选择台式机或笔记本电脑没有什么不同。基本上&#xff0c;您要做的就是查看硬件配置并比较功能和价格。 然而&#xff0c;虽然您可能对个人计算机所需的资源类型有一个粗略的估计&#xff0c;但为服务器获…