长篇图解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类封装性,类的信息隐藏性和边界被破坏
「码文不易,如果您觉得有帮助,请帮忙点击在看或者分享,没有您的支持我可能无法坚持下去!」
一篇文章全面了解Java反射机制
Java的反射机制在实践中可谓无处不在,如果你已经工作几年,还对Java的反射机制一知半解,那么这篇文章绝对值得你读一读。
反射 (Reflection) 是Java的特征之一,它允许运行中的Java程序获取自身的信息,并且可以操作类或对象的内部属性。
通俗的来讲就是:通过反射机制,可以在运行时获得程序或程序集中每一个类型的成员和成员的信息。
注意这里的重点是:运行时,而不是编译时。我们常规情况下写的对象类型都是在编译期就确定下来的。而Java反射机制可以动态地创建对象并调用其属性,这样创建对象的方式便异常灵活了。
虽然通过反射可以动态的创建对象,增加了灵活性,但也不是什么地方都可用,还要考虑性能、编码量、安全、面向对象性等。
我们知道Java是面向对象的,如果通过反射机制去操作对象里面的属性或方法,一定程度上破坏了面向对象的特性。同时,通过反射机制还可以修改私有变量,也存在一定的安全性问题。
但这并不影响反射在实践中的应用,几乎各大框架多多少少都在使用Java反射机制。特别是主流的Spring框架。
Java反射主要提供以下功能:
- 在运行时判断任意一个对象所属的类;
- 在运行时构造任意一个类的对象;
- 在运行时判断任意一个类所具有的成员变量和方法(通过反射甚至可以调用private方法);
- 在运行时调用任意一个对象的方法
反射最重要的用途之一就是开发各类通用框架。以Spring为例,当基于XML进行配置Bean时,我们通常写如下代码:
Spring在启动的时候便会利用反射机制去加载对应的UserServiceImpl类,然后进行实例化。如果不存在该类则会抛出异常,通常异常中还会出现invoke方法调用的堆栈信息。
当Spring基于注解去实例化对象时,同样利用的是反射机制。下面通过一个简单demo示例,演示一下如何通过反射获得注解信息:
上述代码是之前写《一篇文章,全面了解Java自定义注解》中的示例,相关文章可关注公众号“程序新视界”,回复“注解”获得全文。
更多关于Java反射的例子我们就不多说了。上面的示例现在看不懂也没关系,下面我们就来详细介绍一下Java反射机制的具体使用。
我们通过一个简单的示例对比,来了解一下Java反射机制。首先来看正常情况下创建对象并使用对象的示例:
那么,当基于反射机制来达到统一效果该怎么做呢?看下面的具体实现:
在上述过程中通过Class.forName获得User所对应的Class对象,获得构造器Constructor,通过构造器创建出来一个User对象,然后调用对应的方法。
当然,后面的步骤中也可以完全不出现User类,直接通过Class对象获得对应的Method进行调用。示例如下:
关于get方法也是如此操作,就不再赘述。
经过上面的实例我们已经能够正常创建对象,并使用对象了。下面就看看反射常用的API,通过这些API我们可以实现更多的更复杂的功能。
第一种方法:当你知道类的全路径名时,可使用Class.forName静态方法来获得Class对象。上面的示例就是通过这种方法获得:
第二种方法:通过“.class”获得。前提条件是在编译前就能够拿到对应的类。
第三种:使用类对象的getClass()方法。
可以通过Class对象的newInstance()方法和通过Constructor 对象的newInstance()方法创建类的实例对象。
第一种:通过Class对象的newInstance()方法。
第二种:通过Constructor对象的newInstance()方法。
其中第二种方法创建类对象可以选择特定构造方法,而通过 Class对象则只能使用默认的无参数构造方法。
通过Class对象的getFields()方法获取非私有属性。
上述实例中的User对象属性都是private,无法直接通过上述方法获取,可将其中一个属性改为public,即可获取。
通过Class对象的getDeclaredFields()方法获取所有属性。
执行打印结果:
当然针对属性的其他值也是可以获取的,针对私有属性的修改需要先调用field.setAccessible(true)方法,然后再进行赋值。关于具体应用,回头看我们最开始关于注解的实例中的使用。
获取方法的示例如下:
打印结果:
可以看到,不仅获取到了当前类的方法,还获取到了该类父类Object类中定义的方法。
关于获取构造器的方法上面已经讲到了,就不再赘述。而上述的这些方法在Class中都有相应的重载的方法,可根据具体情况进行灵活使用。
最后,我们再看一个通过反射创建数组的实例。
想必经过上述的学习,对Java反射机制有了更进一步的了解,在最开始我们已经说了反射机制也是有不足的。因此,如果可能尽量使用正统的写法,但如果你在开发通用框架,则可考虑使用。
后面,有机会我们再讲解反射机制的源码,别忘记关注公众号:程序新视界。
本文首发来自微信公众号:程序新视界。一个软实力、硬技术同步学习的平台。
0202年了,还有人不懂Java反射机制?
JAVA反射机制是在运行状态中,对于任意一个类,都能够获取这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取类信息以及动态调用对象内容就称为java语言的反射机制。
在运行时判断任意一个对象所属的类;在运行时构造任意一个类的对象;在运行时判断任意一个类所具有的成员变量和方法;在运行时调用任意一个对象的方法;
我们知道,要使用一个类,就要先把它加载到虚拟机中,生成一个Class对象。这个class对象就保存了这个类的一切信息。
反射机制的实现,就是获取这个Class对象,通过Class对象去访问类、对象的元数据以及运行时的数据。
有三种方法获得类的Class对象:Class.forName(String className)、className.class、实例对象.getClass();
反射首先获取Class对象;然后获取Method类和Field类;最后通过Method和Field类进行具体的方法调用或属性访问。
1:在运行时获取对象所属类的类名等信息
2:通过反射机制创建class对象(三种方法)
3:在运行时,通过创建class对象,获取自己的父类信息
4:通过反射机制创建一个类的对象
5:获取类的全部方法,存于一个数组中
6:获取类的全部字段,存于一个数组中
7:操作类/对象 的某个属性(包括私有)
8:调用类/对象 的某个方法(包括私有)
本文作者及来源:Renderbus瑞云渲染农场https://www.renderbus.com
文章为作者独立观点不代本网立场,未经允许不得转载。