java反射机制用法详解

小伙伴们在使用java开发的时候,经常会遇到反射机制的问题,今天就来相机介绍下这种强大的机制。

Java反射机制是一种强大的功能,允许程序在运行时动态地获取类的信息并操作这些信息。以下是对Java反射的详细解释:

  • 定义:Java反射是指在运行时动态地获取类的信息并操作这些信息的能力[^1^][^2^][^3^]。通过反射,开发者可以在运行时检查类的结构(包括构造函数、方法、字段等),并调用这些方法或访问这些字段[^1^]。
  • 核心类:Java反射机制的核心是java.lang.reflect包,它提供了相关类和接口,供我们使用[^3^]。

在Java中,每个类都有一个对应的Class对象,可以通过以下几种方式获取:

  • 通过类名获取Class<?> clazz = Class.forName(\”com.example.MyClass\”);
  • 通过对象获取MyClass obj = new MyClass(); Class<?> clazz = obj.getClass();
  • 通过类字面量获取Class<MyClass> clazz = MyClass.class;

通过Class对象可以获取类的构造函数:

通过Class对象可以获取类的方法:

通过Class对象可以获取类的字段:

  • 框架开发:许多框架(如Spring、Hibernate)使用反射来实现依赖注入、对象关系映射等功能[^1^]。
  • 动态代理:反射可以用于创建动态代理,实现AOP(面向切面编程)[^1^]。
  • 单元测试:在单元测试中,反射可以用于模拟对象的行为[^1^]。
  • 插件系统:反射可以用于加载和执行插件[^1^]。
  • 优点:灵活性高,扩展性强[^1^]。
  • 缺点:性能开销大,安全性较低,可读性较差[^1^]。

假设有一个Person类,包含一个私有的name字段和一个公共的sayHello方法,通过反射机制,我们可以获取到Person类的构造函数和方法,并可以调用sayHello方法:

通过上述示例代码,我们可以看到反射机制的强大之处,在运行时获取到了Person类的构造函数和方法,并动态地创建对象并调用方法[^3^]。

Java反射原理详解(5步图解反射原理)

大家好,我是mikechen。

Java反射经常在大厂面试被问到,下面我重点来详解Java反射原理@mikechen

本篇已收于mikechen原创超30万字《阿里架构师进阶专题合集》里面。

Java反射原理的整个过程,主要分为如下的5大步骤:

1.java文件被编译成class文件

当我们编写完一个Java项目之后,每个java文件都会被编译成一个.class文件。

如下图所示:

2.class文件加载到JVM

Java程序在运行时,首先需要将需要的类加载到内存中。

如下图所示:

这些class文件在程序运行时,会被ClassLoader加载到JVM中,当一个类被加载以后,JVM就会在内存中自动产生一个Class对象,用于描述该类的信息。

3.获取类的Class对象

Java反射的第一步是获取需要操作的类的Class对象,如下所示:

Class对象是Java反射的核心,它包含了该类的所有信息,包括构造函数、方法、属性等。

4.获取类的构造函数、方法和属性

在获取到Class对象之后,可以通过反射获取类的构造函数、方法和属性等信息。

这样我们就可以通过Java反射获取到类的所有信息了。

5.Java反射方法

Java反射方法常见的有:

  • getConstructors():获取类的所有public构造函数;
  • getDeclaredConstructors():获取类的所有构造函数,包括public和非public;
  • getMethods():获取类的所有public方法,包括从父类继承而来的方法;
  • getDeclaredMethods():获取类的所有方法,包括public和非public;
  • getFields():获取类的所有public属性,包括从父类继承而来的属性;
  • getDeclaredFields():获取类的所有属性,包括public和非public;

Java反射应用场景,常见如下:

1.动态代理

使用反射可以在运行时动态地创建代理类,这在实现AOP(面向切面编程)时非常有用。

2.注解处理器

使用反射可以在运行时动态地解析注解,并执行注解所定义的操作,例如生成代码或加载配置文件。

3.依赖注入

使用反射可以在运行时动态地实例化对象,并将其注入到其他对象中,从而实现依赖注入的功能。

4.反射工厂

使用反射可以在运行时动态地创建对象,并执行对象的方法,这对于编写通用代码非常有用。

以上

本篇已收于mikechen原创超30万字《阿里架构师进阶专题合集》里面。

长篇图解java反射机制及其应用场景

在java的面向对象编程过程中,通常我们需要先知道一个Class类,然后new 类名()方式来获取该类的对象。也就是说我们需要在写代码的时候(编译期或者编译期之前)就知道我们要实例化哪一个类,运行哪一个方法,这种通常被称为「静态的类加载」

但是在有些场景下,我们事先是不知道我们的代码的具体行为的。比如,我们定义一个服务任务工作流,每一个服务任务都是对应的一个类的一个方法。

  • 服务任务B执行哪一个类的哪一个方法,是由服务任务A的执行结果决定的
  • 服务任务C执行哪一个类的哪一个方法,是由服务任务A和B的执行结果决定的
  • 并且用户不希望服务任务的功能在代码中写死,希望通过配置的方式执行不同的程序

面对这个情况,我们就不能用代码new 类名()来实现了,因为你不知道用户具体要怎么做配置,这一秒他希望服务任务A执行Xxxx类的x方法,下一秒他可能希望执行Yyyy类的y方法。当然你也可以说提需求嘛,用户改一次需求,我改一次代码。这种方式也能需求,但对于用户和程序员个人而言都是痛苦,那么有没有一种方法「在运行期动态的改变程序的调用行为的方法」呢?这就是要为大家介绍的“「java反射机制」”。

那么java的反射机制能够做那些事呢?大概是这样几种:

  • 在程序运行期动态的根据package名.类名实例化类对象
  • 在程序运行期动态获取类对象的信息,包括对象的成本变量和方法
  • 在程序运行期动态使用对象的成员变量属性
  • 在程序运行期动态调用对象的方法(私有方法也可以调用)

我们定义一个类叫做Student

如果不用反射的方式,我相信只要学过java的朋友肯定会调用dinner方法

如果是反射的方式我们该怎么调用呢?

通过上面的代码我们看到,com.zimug.java.reflection.Student类名和dinner方法名是字符串。既然是字符串我们就可以通过配置文件,或数据库、或什么其他的灵活配置方法来执行这段程序了。这就是反射最基础的使用方式。

java的类加载机制还是挺复杂的,我们这里为了不混淆重点,只为大家介绍和“反射”有关系的一部分内容。

java执行编译的时候将java文件编译成字节码class文件,类加载器在类加载阶段将class文件加载到内存,并实例化一个java.lang.Class的对象。比如:对于Student类在加载阶段

  • 在内存(方法区或叫代码区)中实例化一个Class对象,注意是Class对象不是Student对象
  • 一个Class类(字节码文件)对应一个Class对象
  • 该Class对象保存了Student类的基础信息,比如这个Student类有几个字段(Filed)?有几个构造方法(Constructor)?有几个方法(Method)?有哪些注解(Annotation)?等信息。

有了上面的关于Student类的基本信息对象(java.lang.Class对象),在运行期就可以根据这些信息来实例化Student类的对象。

  • 在运行期你可以直接new一个Student对象
  • 也可以使用反射的方法构造一个Student对象

但是无论你new多少个Student对象,不论你反射构建多少个Student对象,保存Student类信息的java.lang.Class对象都只有一个。下面的代码可以证明。

了解了上面的这些基础信息,我们就可以更深入学习反射类相关的类和方法了:

  • java.lang.Class: 代表一个类
  • java.lang.reflect.Constructor: 代表类的构造方法
  • java.lang.reflect.Method: 代表类的普通方法
  • java.lang.reflect.Field: 代表类的成员变量
  • Java.lang.reflect.Modifier: 修饰符,方法的修饰符,成员变量的修饰符。
  • java.lang.annotation.Annotation:在类、成员变量、构造方法、普通方法上都可以加注解

虽然有三种方法可以获取某个类的Class对象,但是只有第一种可以被称为“反射”。

Class类对象信息中几乎包括了所有的你想知道的关于这个类型定义的信息,更多的方法就不一一列举了。还可以通过下面的方法

  • 获取Class类对象代表的类实现了哪些接口:getInterfaces()
  • 获取Class类对象代表的类使用了哪些注解:getAnnotations()

结合上文中的Student类的定义理解下面的代码

  • getFields()方法获取类的非私有的成员变量,数组,包含从父类继承的成员变量
  • getDeclaredFields方法获取所有的成员变量,数组,但是不包含从父类继承而来的成员变量
  • getMethods() : 获取Class对象代表的类的所有的非私有方法,数组,「包含从父类继承而来的方法」
  • getDeclaredMethods() : 获取Class对象代表的类定义的所有的方法,数组,「但是不包含从父类继承而来的方法」
  • getMethod(methodName): 获取Class对象代表的类的指定方法名的非私有方法
  • getDeclaredMethod(methodName): 获取Class对象代表的类的指定方法名的方法

上面代码的执行结果如下:

可以看到getMethods获取的方法中包含Object父类中定义的方法,但是不包含本类中定义的私有方法sleep。另外我们还可以获取方法的参数及返回值信息:

  • 获取参数相关的属性:
    • 获取方法参数个数:getParameterCount()
    • 获取方法参数数组对象:getParameters() ,返回值是java.lang.reflect.Parameter数组
  • 获取返回值相关的属性
    • 获取方法返回值的数据类型:getReturnType()

实际在上文中已经演示了方法的调用,如下invoke调用dinner方法

dinner方法是无参的那么有参数的方法怎么调用?看看invoke方法定义,第一个参数是Method对象,无论后面 Object… args有多少参数就按照方法定义依次传参就可以了。

  • 通过配置信息调用类的方法
  • 结合注解实现特殊功能
  • 按需加载jar包或class

将上文的hello world中的代码封装一下,你知道类名className和方法名methodName是不是就可以调用方法了?至于你将className和 methodName配置到文件,还是nacos,还是数据库,自己决定吧!

大家如果学习过mybatis plus都应该学习过这样的一个注解TableName,这个注解表示当前的实例类Student对应的数据库中的哪一张表。如下问代码所示,Student所示该类对应的是t_student这张表。

下面我们自定义TableName这个注解

有了这个注解,我们就可以扫描某个路径下的java文件,至于类注解的扫描我们就不用自己开发了,引入下面的maven坐标就可以

看下面代码:先扫描包,从包中获取标注了TableName注解的类,再对该类打印注解value信息

输出结果是:

有的朋友会问这有什么用?这有大用处了。有了类定义与数据库表的对应关系,你还能通过反射获取类的成员变量,之后你是不是就可以根据表明t_student和字段名nickName,age构建增删改查的SQL了?全都构建完毕,是不是就是一个基础得Mybatis plus了?

反射和注解结合使用,可以演化出许许多多的应用场景,特别是在架构优化方面,等待你去发觉啊!

在某些场景下,我们可能不希望JVM的加载器一次性的把所有的jar包装载到JVM虚拟机中,因为这样会影响项目的启动和初始化效率,并且占用较多的内存。我们希望按需加载,需要用到哪些jar,按照程序动态运行的需求取加载这些jar。

同样的把.class文件放在一个路径下,我们也是可以动态加载到的

类的动态加载能不能让你想到些什么?是不是可以实现代码修改,不需要重新启动容器?对的,就是这个原理,因为一个类的Class对象只有一个,所以不管你重新加载多少次,都是使用最后一次加载的class对象(上文讲过哦)。

  • 优点:自由,使用灵活,不受类的访问权限限制。可以根据指定类名、方法名来实现方法调用,非常适合实现业务的灵活配置。
  • 缺点:
    • 也正因为反射不受类的访问权限限制,其安全性低,很大部分的java安全问题都是反射导致的。
    • 相对于正常的对象的访问调用,反射因为存在类和方法的实例化过程,性能也相对较低
    • 破坏java类封装性,类的信息隐藏性和边界被破坏

「码文不易,如果您觉得有帮助,请帮忙点击在看或者分享,没有您的支持我可能无法坚持下去!」

本文作者及来源:Renderbus瑞云渲染农场https://www.renderbus.com

点赞 0
收藏 0

文章为作者独立观点不代本网立场,未经允许不得转载。