当前位置: 首页 > news >正文

反射机制详解

✅作者简介:大家好,我是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 文件不存在,也可以在运行时加载并使用反射机制操作。

相关文章:

反射机制详解

✅作者简介&#xff1a;大家好&#xff0c;我是Leo&#xff0c;热爱Java后端开发者&#xff0c;一个想要与大家共同进步的男人&#x1f609;&#x1f609; &#x1f34e;个人主页&#xff1a;Leo的博客 &#x1f49e;当前专栏&#xff1a;Java从入门到精通 ✨特色专栏&#xff…...

【数据库】七、数据库安全与保护

七、数据库安全与保护 文章目录 七、数据库安全与保护安全性访问控制数据库安全性控制用户标识和鉴别存取控制自主存取控制(DAC)存取控制方法&#xff1a;授权与回收GRANT授权REVOKE回收 强制存取控制(MAC) MySQL的安全设置用户管理1.创建登录用户2.修改用户密码3.修改用户名4.…...

卡尔曼滤波-剔除异常值的影响

二郎在看论文的时候&#xff0c;发现了一个针对卡尔曼滤波过程中&#xff0c;测量向量出现误差导致滤波发散的处理方法。 该方法也可以扩展到其他问题中使用&#xff0c;所以二郎在这里写一下。 论文原文&#xff1a;https://www.mdpi.com/1424-8220/20/17/4710 论文翻译对应…...

Java程序之动物声音“模拟器”

题目&#xff1a; 设计一个“动物模拟器”&#xff0c;希望模拟器可以模拟许多动物的叫声和行为&#xff0c;要求如下&#xff1a; 编写接口Animal&#xff0c;该接口有两个抽象方法cry()和getAnimalName()&#xff0c;即要求实现该接口的各种具体的动物类给出自己的叫声和种类…...

jieba中文分词器的使用

Jieba 是一个中文分词的第三方库&#xff0c;主要用于对中文文本进行分词。分词是将文本分割成一个个词语的过程&#xff0c;这在中文文本处理中尤为重要&#xff0c;因为中文不像英文那样有明显的空格来分隔词语。Jieba 的分词算法可以实现精确分词、全模式分词和搜索引擎模式…...

【杂记-浅谈OSPF协议中的RouterDeadInterval】

OSPF协议中的RouterDeadInterval 一、RouterDeadInterval概述二、设置RouterDeadInterval三、RouterDeadInterval的重要性 一、RouterDeadInterval概述 RouterDeadInterval&#xff0c;即路由器死区间隔&#xff0c;它涉及到路由器如何在广播网络上发现和维护邻居关系。Router…...

Django 模版变量

1&#xff0c;模版变量作用 模板变量使用“{{ 变量名 }}” 来表示模板变量前后可以有空格&#xff0c;模板变量名称&#xff0c;可以由数字&#xff0c;字母&#xff0c;下划线组成&#xff0c;不能包含空格模板变量还支持列表&#xff0c;字典&#xff0c;对象 2&#xff0c;…...

【数据结构与算法】图的存储(邻接矩阵,邻接表)详解

图的邻接矩阵数据结构 typedef enum { NDG, DG, NDN, DN } GraphKind;using VRType int; using InfoType int;typedef struct ArcCell {VRType adj;InfoType *info; } Arc[N][N];struct MGraph {ElemType vexs[N];Arc arc;int vexnum, arcnum;GraphKind kind; };ArcCell 结构…...

【深度C++】之“类与结构体”

0. 抽象数据类型 类&#xff08;class&#xff09; 和结构体&#xff08;struct&#xff09; 都是C中的自定义数据类型&#xff0c;是使用C实现面向对象编程思想的起点。 类的基本思想是数据抽象&#xff08;data abstraction&#xff09; 和封装&#xff08;encapsulation&a…...

CTO的职责是什么?

看《架构思维》作者是这样讲的&#xff1a; CTO 到底是做什么的&#xff1f; 我当下的答案是&#xff1a;“CTO 就是一个从技术视角出发&#xff0c;为公司或者所在的部门做正确决策的 CEO。”怎么理解这句话呢&#xff1f;作为一个 CTO&#xff0c;其长期目标和决策优先级与…...

【GD32】从零开始学兆易创新32位微处理器——RTC实时时钟+日历例程

1 简介 RTC实时时钟顾名思义作用和墙上挂的时钟差不多&#xff0c;都是用于记录时间和日历&#xff0c;同时也有闹钟的功能。从硬件实现上来说&#xff0c;其实它就是一个特殊的计时器&#xff0c;它内部有一个32位的寄存器用于计时。RTC在低功耗应用中可以说相当重要&#xf…...

HTTP网络协议

1.HTTP &#xff08;1&#xff09;概念&#xff1a; Hyper Text Transfer Protocol&#xff0c;超文本传输协议规定了浏览器和服务器之间数据传输的规则。 &#xff08;2&#xff09;特点 基于TCP协议:面向连接&#xff0c;安全基于请求-响应模型的:一次请求对应一次响应HTTP协…...

Kubernetes相关生态

1、Prometheus、Metrics Server与Kubernetes监控体系 简介&#xff1a; Prometheus 项目与 Kubernetes 项目一样&#xff0c;也来自于 Google 的 Borg 体系&#xff0c;它的原型系统&#xff0c;叫作 BorgMon&#xff0c;是一个几乎与 Borg 同时诞生的内部监控系统 Pro…...

C语言入门4-函数和程序结构

函数举例 读取字符串&#xff0c;如果字符串中含有ould则输出该字符串&#xff0c;否则不输出。 #include <stdio.h>// 函数声明 int getLine(char s[], int lim); int strindex(char s[], char t[]);int main() {char t[] "ould"; // 要查找的目标子字符串…...

分行业二氧化碳排放数据

分行业二氧化碳排放量 资源名称&#xff1a;分行业二氧化碳排放量 数据来源&#xff1a;中国能源统计年鉴 时间范围&#xff1a;1995-2018年指标&#xff1a;八类能源和总量&#xff1a;煤炭、焦炭、原油、汽油、煤油、柴油、燃料油、天然气...

【OS基础】符合AUTOSAR标准的RTAOS-Alarms详解

目录 前言 正文 7.报警Alarms 7.1配置Alarms 7.1.1激活一个任务 7.1.2 设置一个事件 7.1.3报警回调Alarm Callback 7.1.4 增加计数器值 7.2设置Alarms 7.2.1 绝对Alarms 7.2.2 相对Alarm 7.3自启动Alarms 7.4 删除Alarms 7.5确认何时会发生Alarm 7.6非周期Alarm…...

基于Java的学生成绩管理系统

你好呀&#xff0c;我是计算机学姐码农小野&#xff01;如果有相关需求&#xff0c;可以私信联系我。 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;Java技术&#xff0c;B/S结构 工具&#xff1a;MyEclipse&#xff0c;MySQL 系统展示 首页 个人中…...

都2024年了,还有人不懂动态代理么?

文章目录 一、定义二、静态代理三、动态代理1. JDK代理1.1 JDK代理实现流程1.2 动态生成的类字节码 2. Cglib代理2.1 Cglib实现流程 四、总结 一、定义 静态代理和动态代理都反映了一个代理模式&#xff0c;代理模式是一种经典的设计模式&#xff0c;常用于为其他对象提供一种…...

ARM功耗管理框架之PPU

安全之安全(security)博客目录导读 思考&#xff1a;功耗管理框架&#xff1f;SCP&#xff1f;PPU&#xff1f;LPI&#xff1f;之间的关系&#xff1f;如何配合&#xff1f; 目录 一、功耗管理框架中的PPU 二、PPU的结构与连接关系 三、PPU操作模式和电源模式及其之间的转…...

说说 SSL 的错误认识和不足之处

最近明月在学习折腾 LNMP 期间无意中建了一个 Typecho 的博客小站&#xff0c;近一周的折腾下来&#xff0c;收获真的不少&#xff0c;致使兴趣也越来越浓了&#xff0c;在升级 LNMP 的时候捎带手的给这个 Typecho 博客也启用了 SSL。并且开启了 memcached 和 OPcache 优化加速…...

Go语言day1

下载go语言的安装程序&#xff1a; All releases - The Go Programming Language 配置go语言的环境变量&#xff1a; 写第一个go语言 在E:\go_workspace当前窗口使用cmd命令: 输入 go run test.go...

【Python机器学习】利用t-SNE进行流形学习

虽然PCA通常是用于变换数据的首选方法&#xff0c;使你能够用散点图将其可视化&#xff0c;但这一方法的性质限制了其有效性。 有一类用于可视化的算法叫做流形学习算法&#xff0c;它允许进行更复杂的映射&#xff0c;通常也可以给出更好的可视化。其中特别有用的一个就是t-S…...

03 - matlab m_map地学绘图工具基础函数 - 设置坐标系(m_coord)

03 - matlab m_map地学绘图工具基础函数 - 设置坐标系&#xff08;m_coord&#xff09; 0. 引言1. m_proj使用方法2. 结语 0. 引言 上一篇介绍了m_proj函数用于初始化投影&#xff0c;本篇介绍的函数m_coord用于初始化地理坐标系或地磁坐标系&#xff0c;地理/地磁坐标系和投影…...

UEC++ 虚幻5第三人称射击游戏(一)

UEC 虚幻5第三人称射击游戏&#xff08;一&#xff09; 创建一个空白的C工程 人物角色基本移动 创建一个Character类添加一些虚幻商城中的基础动画 给角色类添加Camera与SPringArm组件 UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category "SpringArm")clas…...

java小代码(1)

代码 &#xff1a; 今日总结到此结束&#xff0c;拜拜&#xff01;...

SLAM ORB-SLAM2(27)词袋模型

SLAM ORB-SLAM2(27)词袋模型 1. 词袋模型1.1. 词汇树1.2. 逆向索引表1.3. 逆向索引表2. 词袋向量3. 匹配候选帧3.1. 找出和当前帧具有公共单词的所有关键帧3.2. 找出和当前帧最多公共单词的关键帧3.3. 剔除共享单词数较少的关键帧3.4. 计算关键帧的共视关键帧组的总得分3.5. …...

OpenAI 的 GPT-5:CTO米拉-穆拉提说,到 2026 年将实现博士级智能(Ph.D.-Level))

据首席技术官米拉-穆拉提&#xff08;Mira Murati&#xff09;介绍&#xff0c;GPT-5 是 OpenAI 人工智能的下一代进化产品&#xff0c;将于 2025 年底或 2026 年初在特定任务中实现博士级智能。 GPT-5 内部代号为 "Gobi "和 “Arrakis”&#xff0c;将是一个多模态…...

macbook配置adb环境和用adb操作安卓手机

&#xff08;参考&#xff1a;ADB工具包的安装与使用_adb工具箱-CSDN博客&#xff09; 第一步&#xff1a;从Android开发者网站下载Android SDK&#xff08;软件开发工具包&#xff09;。下载地址为&#xff1a; 第二步&#xff1a;解压下载的SDK压缩文件到某个目录中。 进入解…...

微软TTS最新模型,发布9种更真实的AI语音

很高兴与大家分享 Azure AI 语音翻译产品套件的两个重大更新&#xff1a; 视频翻译和增强的实时语音翻译 API。 视频翻译&#xff08;批量&#xff09; 今天&#xff0c;我们宣布推出视频翻译预览版&#xff0c;这是一项突破性的服务&#xff0c;旨在改变企业本地化视频内容…...

python爬虫 -爬取 json 格式数据

在Python中&#xff0c;爬取JSON格式的数据通常涉及到发送 HTTP请求到某个URL&#xff0c;并解析返回的JSON数据。以下是一个简单的示例&#xff0c;说明如何使用Python的requests库来爬取JSON格式的数据&#xff1a; 1. 首先&#xff0c;确保你已经安装了requests库。如果没…...