Java反射机制的原理和用途

article/2025/10/10 0:15:16

看了好多关于Java反射机制的文章,大多都太过官方,消化起来比较稍显费劲,本篇,我会依据自己的理解去阐述什么是Java的反射机制,反射用在什么地方,以及怎么来使用?

 

开篇前,我们还是要了解一下,什么是Java的反射机制:

 

 

“程序运行时,允许改变程序结构或变量类型,这种语言称为动态语言”。从这个观点看,Perl、Python看过我写的Python3学习系列的博文,不止一次突出Python动态语言的特点)、Ruby是动态语言,C++、Java、C#不是动态语言。但是JAVA有着一个非常突出的动态相关机制——Reflection(反射),用在Java身上指的是可以于运行时加载探知使用编译期间完全未知的classes。换句话说,Java程序可以加载一个运行时才得知名称的class,获悉其完整构造(但不包括methods定义),并生成其对象实体(newInstance)或对其fields设值,或唤起(invoke)其methods方法。

注意 方法的声明和定义不是一回事,
声明:public string Method(string parm1,int param2,...)
定义:public string Method(string parm1,int param2,...)
          {
                // do something
          }


反射用在什么地方?


由于,我们还不清楚反射究竟是什么玩意,怎么用,是不是我们平时写代码的时候会用得上? 这些,都不知道的话,我们也没法定论,这个Java反射机制,用在什么地方比较和合适(注意,一项技术的诞生,一定是为了方便另一项技术的使用,否则会失去本身存在的意义!)


因此,我们先来说一下,反射怎么用?

 



一、反射的应用



       我们可能听过,Java编写的程序,一次编译,到处运行。这也是Java程序为什么是无关平台的所在,原因在于,java的源代码会被编译成.class文件字节码,只要装有Java虚拟机JVM的地方(Java提供了各种不同平台上的虚拟机制,第一步由Java IDE进行源代码编译,得到相应类的字节码.class文件,第二步,Java字节码由JVM执行解释给目标计算机,第三步,目标计算机将结果呈现给我们计算机用户;因此,Java并不是编译机制,而是解释机制),.class文件畅通无阻。

       Java的反射机制,操作的就是这个.class文件,首先加载相应类的字节码(运行eclipse的时候,.class文件的字节码会加载到内存中),随后解剖(反射 reflect)出字节码中的构造函数、方法以及变量(字段),或者说是取出,我们先来定义一个类Animal,里面定义一些构造函数,方法,以及变量:




Animal.java:

 

package com.appleyk.reflect;public class Animal {public String name ="Dog";private int   age  =30 ;//默认无参构造函数public Animal(){System.out.println("Animal");}//带参数的构造函数 public Animal(String name , int age){System.out.println(name+","+age);}//公开 方法  返回类型和参数均有public String sayName(String name){return "Hello,"+name;}}

 


 



我们再定义一个测试类:

ReflectTest.java

 

package com.appleyk.test;public class ReflectTest {public static void main(String args[]) throws Exception{//do something }}




 




我们运行一下我们的项目,会发现如下:



 


对应内存中就是:




 


 

我们借助javap命令查看一下,这个Animal.class里面的内容是什么:

 

F:\Java\ReflectClass\bin\com\appleyk\reflect>javap -c Animal.class
Compiled from "Animal.java"
public class com.appleyk.reflect.Animal {public java.lang.String name;public com.appleyk.reflect.Animal();Code:0: aload_01: invokespecial #12                 // Method java/lang/Object."<init>":
()V4: aload_05: ldc           #14                 // String Dog7: putfield      #16                 // Field name:Ljava/lang/String;10: aload_011: bipush        3013: putfield      #18                 // Field age:I16: getstatic     #20                 // Field java/lang/System.out:Ljava/
io/PrintStream;19: ldc           #26                 // String Animal21: invokevirtual #28                 // Method java/io/PrintStream.printl
n:(Ljava/lang/String;)V24: returnpublic com.appleyk.reflect.Animal(java.lang.String, int);Code:0: aload_01: invokespecial #12                 // Method java/lang/Object."<init>":
()V4: aload_05: ldc           #14                 // String Dog7: putfield      #16                 // Field name:Ljava/lang/String;10: aload_011: bipush        3013: putfield      #18                 // Field age:I16: getstatic     #20                 // Field java/lang/System.out:Ljava/
io/PrintStream;19: new           #39                 // class java/lang/StringBuilder22: dup23: aload_124: invokestatic  #41                 // Method java/lang/String.valueOf:(
Ljava/lang/Object;)Ljava/lang/String;27: invokespecial #47                 // Method java/lang/StringBuilder."<
init>":(Ljava/lang/String;)V30: ldc           #49                 // String ,32: invokevirtual #51                 // Method java/lang/StringBuilder.ap
pend:(Ljava/lang/String;)Ljava/lang/StringBuilder;35: iload_236: invokevirtual #55                 // Method java/lang/StringBuilder.ap
pend:(I)Ljava/lang/StringBuilder;39: invokevirtual #58                 // Method java/lang/StringBuilder.to
String:()Ljava/lang/String;42: invokevirtual #28                 // Method java/io/PrintStream.printl
n:(Ljava/lang/String;)V45: returnpublic java.lang.String sayName(java.lang.String);Code:0: new           #39                 // class java/lang/StringBuilder3: dup4: ldc           #64                 // String Hello,6: invokespecial #47                 // Method java/lang/StringBuilder."<
init>":(Ljava/lang/String;)V9: aload_110: invokevirtual #51                 // Method java/lang/StringBuilder.ap
pend:(Ljava/lang/String;)Ljava/lang/StringBuilder;13: invokevirtual #58                 // Method java/lang/StringBuilder.to
String:()Ljava/lang/String;16: areturn
}

 




我们发现,字节码里面包含了类Animal的构造函数、变量以及方法,但注意,全都是public类型的,我们的定义的类的私有变量 private int   age  =30 哪去了?当然,既然是类的私有部分,肯定不会暴露在外面的,但是不阻碍我们通过反射获得字节码中的私有成员(本篇只举例说明私有变量(字段field),其他私有类成员同理)。

我们的类Animal在Anima.java中定义,但在Animal.class文件中,我们的Animal类阐述如下:



 




下面,我们来写一段demo,来演示一下,如何使用反射机制,将.class文件中的类加载出来,并解剖出字节码中对应类的相关内容(构造函数、属性、方法):

看代码前,我们学两个小技巧:



(1)获得类的完全限定名:




 

copy以后,直接paste

 




(2)自动生成返回值对象

 




 

 

ReflectTest.java:

 

package com.appleyk.test;import java.lang.reflect.Constructor;import com.appleyk.reflect.Animal;public class ReflectTest {public static void main(String args[]) throws Exception{//do something //1、加载类 ,指定类的完全限定名:包名+类名Class c1 = Class.forName("com.appleyk.reflect.Animal");System.out.println(c1);//打印c1,发现值和字节码中的类的名称一样//2、解刨(反射)类c1的公开构造函数,且参数为null Constructor ctor1= c1.getConstructor();//3、构造函数的用途,就是创建类的对象(实例)的//除了私有构造函数外(单列模式,禁止通过构造函数创建类的实例,保证一个类只有一个实例)//ctor1.newInstance()默认生成一个Object对象,我们需要转化成我们要的Animal类对象// Object a1 = ctor1.newInstance();Animal a1 = (Animal)ctor1.newInstance(); //4、证明一下a1确实是Animal的实例,我们通过访问类中的变量来证明System.out.println(a1.name);}}

 





我们看下,上述demo 的执行结果:



 

 


 

 

我们接着走,获得类中的变量(字段)和方法,两种方式,一个是getXXX,一个是getDeclaredXXX,二者是有区别的,下面demo注释的很详细,并且,我们使用反射出的字段和方法,去获取相应实例的字段值和唤起方法(相当于执行某实例的方法),我们看下完整版demo:

 

加强版的 ReflectTest.java

 

package com.appleyk.test;import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;import com.appleyk.reflect.Animal;public class ReflectTest {public static void main(String args[]) throws Exception {// do somethingSystem.out.println("A(无参构造函数)--加载类、反射类的构造函数、利用构造函数new一个Animal实例instance--");// 1、加载类 ,指定类的完全限定名:包名+类名Class c1 = Class.forName("com.appleyk.reflect.Animal");System.out.println(c1);// 打印c1,发现值和字节码中的类的名称一样// 2.a、解刨(反射)类c1的公开构造函数,且参数为nullConstructor ctor1 = c1.getConstructor();// 3、构造函数的用途,就是创建类的对象(实例)的// 除了私有构造函数外(单列模式,禁止通过构造函数创建类的实例,保证一个类只有一个实例)// ctor1.newInstance()默认生成一个Object对象,我们需要转化成我们要的Animal类对象// Object a1 = ctor1.newInstance();Animal a1 = (Animal) ctor1.newInstance();// 4、证明一下a1确实是Animal的实例,我们通过访问类中的变量来证明System.out.println(a1.name);System.out.println("A(有参构造函数)--加载类、反射类的构造函数、利用构造函数new一个Animal实例instance--");// 2.b、 解刨(反射)类c1的公开构造函数,参数为string和intConstructor ctor2 = c1.getConstructor(String.class, int.class);Animal a2 = (Animal) ctor2.newInstance("Cat", 20);System.out.println("B--获得本类中的所有的字段----------------------------");// 5、获得类中的所有的字段 包括public、private和protected,不包括父类中申明的字段Field[] fields = c1.getDeclaredFields();for (Field field : fields) {System.out.println(field);}System.out.println("C--获得本类中的所有公有的字段,并获得指定对象的字段值-----");// 6、获得类中的所有的公有字段fields = c1.getFields();for (Field field : fields) {System.out.println(field + ", 字段值 = " + field.get(a1));// 注意:私有变量值,无法通过field.get(a1)进行获取值// 通过反射类中的字段name,修改name的值(注意,原值在类中name="Dog")// 如果,字段名称等于"name",且字段类型为String,我们就修改字段的值,也就是类中变量name的值if (field.getName() == "name" && field.getType().equals(String.class)) {String name_new = (String) field.get(a1);// 记得转换一下类型name_new = "哈士奇";// 重新给name赋值field.set(a1, name_new);// 设置当前实例a1的name值,使修改后的值生效}}System.out.println("利用反射出的字段,修改字段值,修改后的name = " + a1.name);System.out.println("D--获取本类中的所有的方法--------------------");// 7、获取本类中所有的方法 包括public、private和protected,不包括父类中申明的方法Method[] methods = c1.getDeclaredMethods();for (Method m : methods) {System.out.println(m);// 我们在类Animal中只定义了一个public方法,sayName}System.out.println("E--获取本类中的所有的公有方法,包括父类中和实现接口中的所有public方法-----------");// 8、获取类中所有公有方法,包括父类中的和实现接口中的所有public 方法methods = c1.getMethods();for (Method m : methods) {System.out.println(m);// 我们在类Animal中只定义了一个public方法,sayName}System.out.println("F--根据方法名称和参数类型获取指定方法,并唤起方法:指定所属对象a1,并给对应参数赋值-----------");// 9、唤起Method方法(执行) getMethod:第一个参数是方法名,后面跟方法参数的类Method sayName = c1.getMethod("sayName", String.class);System.out.println(sayName.invoke(a1, "Tom"));}}

 


我们看下对应的执行结果:

 

 

 


 

如果,你对上述执行的结果,一次性接收不了的话,建议将上述测试demo自己亲自敲一遍,先别急着一次性敲完,一点点来,按照序号来,你会发现,反射的机制,无非就是先加载对应字节码中的类,然后,根据加载类的信息,一点点的去解剖其中的内容,不管你是public的还是private的,亦或是本类的还是来自原继承关系或者实现接口中的方法,我们java的反射技术 reflect,均可以将其从字节码中拉回到现实,不仅可以得到字段的名字,我们还可以获得字段的值和修改字段的值,不仅可以得到方法的申明我们还可以拿到方法的定义和唤起方法(执行方法),当然,你会有一个这样的疑惑

 

为什么new一个对象那么简单,非要用反射技术中的newInstance?

为什么,我可以直接对象a1. 变量访问变量,却非要用反射那么费劲的获得name字段呢?

为什么,我几行代码就能搞定的事情,非要用反射呢?

 

 

 

 


 

 

回到最开始我们讲的地方:

 

 


 

ok,解密答案之前,我们先来思考一个问题?

 

假设我们定义了很多类,有Animal、Person、Car..... ,如果我想要一个Animal实例,那我就new Animal(),如果另一个人想要一个Person实例,那么他需要new Person(),当然,另一个说,我只要一个Car实例,于是它要new Car()......这样一来就导致,每个用户new的对象需求不相同,因此他们只能修改源代码,并重新编译才能生效。这种将new的对象写死在代码里的方法非常不灵活,因此,为了避免这种情况的方法,Java提供了反射机制,典型的应用如下:

 

 

 


 

我们知道Spring的IOC吧,即“控制反转”(通过第三方配置文件实现对 对象的控制)。简单说是将我们设计好的对象交给容器控制,而不是直接交给程序内部进行对象的控制。

 

比如,在Spring中,我们经常看到:

 

 


 

针对上述的配置,我们Spring是怎么帮助我们实例化对象,并放到容器中去了呢? 没错,就是通过反射!!!!

 

我们看下,下面的伪代码实现过程:

 

 

//解析<bean .../>元素的id属性得到该字符串值为"sqlSessionFactory" String idStr = "sqlSessionFactory";  //解析<bean .../>元素的class属性得到该字符串值为"org.mybatis.spring.SqlSessionFactoryBean"  String classStr = "org.mybatis.spring.SqlSessionFactoryBean";  //利用反射知识,通过classStr获取Class类对象  Class cls = Class.forName(classStr);  //实例化对象  Object obj = cls.newInstance();  //container表示Spring容器  container.put(idStr, obj);  //当一个类里面需要用另一类的对象时,我们继续下面的操作//解析<property .../>元素的name属性得到该字符串值为“dataSource”  String nameStr = "dataSource";  //解析<property .../>元素的ref属性得到该字符串值为“dataSource”  String refStr = "dataSource";  //生成将要调用setter方法名  String setterName = "set" + nameStr.substring(0, 1).toUpperCase()  + nameStr.substring(1);  //获取spring容器中名为refStr的Bean,该Bean将会作为传入参数  Object paramBean = container.get(refStr);  //获取setter方法的Method类,此处的cls是刚才反射代码得到的Class对象  Method setter = cls.getMethod(setterName, paramBean.getClass());  //调用invoke()方法,此处的obj是刚才反射代码得到的Object对象  setter.invoke(obj, paramBean);  

 


 

 

是不是很熟悉,虽然是伪代码,但是和我们本篇讲的反射机制的使用是相同的,现在知道我们的反射机制用在哪了吧,没错就是我们经常提到的Java web框架中,里面就用到了反射机制,只要在代码或配置文件中看到类的完全限定名(包名+类名),其底层原理基本上使用的就是Java的反射机制

 

 

因此,如果你不做框架的话,基本上是用不到反射机制的,我们大多时候是使用框架的一方,而反射机制都已经在底层实现过了,因此,我们不必担心,我们会写那么复杂的代码。但是,我们必须要理解这种机制的存在!

 

 

 

 

 

 

 

 


http://chatgpt.dhexx.cn/article/G9sMIMeJ.shtml

相关文章

Java反射详解及作用

参考视频链接: 哔哩哔哩视频. 1. 反射概述 能够分析类能力的程序叫做反射(reflective)&#xff0c;对于任何一个Class类&#xff0c;反射可以在运行时直接得到这个类的全部成分&#xff0c;包括构造器&#xff0c;成员方法&#xff0c;成员变量。获得的构造器对象为Construct…

JAVA反射机制分析-------spring的通过反射创建bean实例对象以及属性注入的原理解析

JAVA反射机制 java反射机制是在运行状态中&#xff0c;对于任意一个类&#xff0c; 能够知道这个类的所有属性和方法&#xff1b;对于任意一个对象,都能够调用他的任意一个方法和属性。这种动态获取的信息以及动态调用对象的方法的功能称为JAVA语言的反射机制。 巧妙的利用ja…

【Spring】spring的反射机制详解

一、什么是反射&#xff1a; &#xff08;1&#xff09;Java反射机制的核心是在程序运行时动态加载类并获取类的详细信息&#xff0c;从而操作类或对象的属性和方法。本质是JVM得到class对象之后&#xff0c;再通过class对象进行反编译&#xff0c;从而获取对象的各种信息。 &…

Spring————java的反射机制,Spring的IOC和DI

一、认识Spring 1.1、Spring家族 SpringFramework&#xff1a; Spring框架&#xff1a;是Spring中最早核心的技术&#xff0c;也是所有其他技术及的基础。 SpringBoot:Spring是用来简化开发。而SpringBoot是来帮助Spring在简化的基础上能更快速进行开发。 SpringCloud&#xf…

分布式定时任务调度实战

目录 1、为什么需要定时任务 2、定时任务调度框架 2.1 单机 2.2 分布 3、xxl-job和elastic-job对比 3.1 支持集群部署方式 3.2 多节点部署任务执行方式 3.3 日志可追溯 3.4 监控告警 3.5 弹性扩容缩容 3.6 支持并行调度 3.7 高可用策略 3.8 失败处理策略 3.9 动态…

浅谈传统定时任务和分布式定时任务

为什么用定时任务&#xff1f; 定时任务平台可以在后台自动检测数据并进行操作。主要应用在订单状态改变、后台统计、定时发送邮件或短信等。 定时任务怎么部署实现&#xff1f; 传统的定时任务可以通过可定时线程池、timertask、quartz、spring-schedule方式来进行处理。他…

分布式定时任务技术选型

1、目前的定时任务方案 Java中开发大多数使用Spring-Scheduler&#xff0c;只需要在Spring中的bean的对应方法加上sheduler注解即可完成我们的定时任务&#xff0c;但是光是用这个注解还远远不能保证定时任务执行多次&#xff0c;我们需要一些其他手段的保证&#xff0c;一般来…

java 分布式 定时任务_Java中实现分布式定时任务的方法

定时器Scheduler在平时使用比较频繁&#xff0c;在springboot中&#xff0c;配置好Scheduled和EnableScheduling之后&#xff0c;定时器就能正常执行&#xff0c;实现定时任务的功能。 但是在这样的情况下&#xff1a;如果开发的服务需要水平部署实现负载均衡&#xff0c;那么定…

【手把手】分布式定时任务调度解析之Quartz

1、任务调度背景 在业务系统中有很多这样的场景&#xff1a; 1、账单日或者还款日上午 10 点&#xff0c;给每个信用卡客户发送账单通知&#xff0c;还款通知。如何判断客户的账单日、还款日&#xff0c;完成通知的发送&#xff1f; 2、银行业务系统&#xff0c;夜间要完成跑批…

轻量级分布式定时任务框架XXL-Job

轻量级分布式定时任务框架XXL-Job: XXL-JOB是一款轻量级的分布式定时任务框架&#xff0c;上手简单&#xff0c;操作容易&#xff0c;XXL-Job可以到官网下载也可以去gitee上拉取源码&#xff0c;其中核心模块分页两个&#xff1a;1&#xff1a;是分布式调度服务&#xff0c; 2&…

Springboot结合Redis实现分布式定时任务

一、背景 之前分享过分布式定时任务的技术选型方案&#xff1a;分布式定时任务技术选型方案&#xff0c;个人青睐xxl_job&#xff0c;分享了搭建接入流程&#xff1a;xxl_job搭建方案&#xff0c;本次项目需求较为简单&#xff0c;同时时间紧张。下面介绍利用Redis锁实现分布式…

分布式定时任务框架说明

分布式定时任务框架说明 分布式定时任务框架说明Quartz概念架构组件springboot集成方式使用内存使用数据库 TBSchedule&#xff1a;elastic-job概念架构组件执行流程特性 satumxxl-job概念特性架构组件使用 分布式定时任务框架说明 Quartz 概念 Quartz&#xff1a;Java事实上…

分布式定时任务对比

1. 什么是分布式定时任务 把分散的&#xff0c;可靠性差的计划任务纳入统一的平台&#xff0c;并实现集群管理调度和分布式部署的一种定时任务的管理方式。叫做分布式定时任务。 2. 常见开源方案 elastic-job , xxl-job &#xff0c;quartz , saturn, opencron , antares el…

简单粗暴的分布式定时任务解决方案

分布式定时任务 1.为什么需要定时任务&#xff1f;2.数据库实现分布式定时任务3.基于redis实现 1.为什么需要定时任务&#xff1f; 因为有时候我们需要定时的执行一些操作&#xff0c;比如业务中产生的一些临时文件&#xff0c;临时文件不能立即删除&#xff0c;因为不清楚用户…

Java 实现分布式定时任务

文章目录 前言一、技术点二、代码实践1、引入库2、创建启动线程入口3、表结构4、任务解析5、任务拉取 三、结果展示四、总结 前言 最近有一个需求&#xff1a;需要实现分布式定时任务。而市面上的定时任务大多数都是基于Scheduled注解进行实现。不符合需求。所以根据需求整体思…

分布式定时任务调度

前言 什么是分布式定时任务? 把分散的&#xff0c;可靠性差的计划任务纳入统一的平台&#xff0c;并实现集群管理调度和分布式部署的一种定时任务的管理方式。叫做分布式定时任务。 为什么要采用分布式定时任务&#xff1f; 单点定时任务的缺点: 功能相对简单&#xff0c…

分布式定时任务-XXL-JOB-教程+实战

一.定时任务概述 1.定时任务认识 1.1.什么是定时任务 定时任务是按照指定时间周期运行任务。使用场景为在某个固定时间点执行&#xff0c;或者周期性的去执行某个任务&#xff0c;比如&#xff1a;每天晚上24点做数据汇总&#xff0c;定时发送短信等。 1.2.常见定时任务方案…

几种常用的分布式定时任务

1. 什么是分布式定时任务 把分散的&#xff0c;可靠性差的计划任务纳入统一的平台&#xff0c;并实现集群管理调度和分布式部署的一种定时任务的管理方式。叫做分布式定时任务。 2. 常见开源方案 elastic-job xxl-job quartz saturn opencron antares elastic-job el…

分布式定时任务

分布式定时任务 1&#xff0c;什么是分布式定时任务&#xff1b;2&#xff0c;为什么要采用分布式定时任务&#xff1b;3&#xff0c;怎么样设计实现一个分布式定时任务&#xff1b;4&#xff0c;当前比较流行的分布式定时任务框架&#xff1b; 1&#xff0c;什么是分布式定时…

python类型转换函数str

str函数&#xff0c;将数字转为字符串&#xff1a;