Java 核心基础——反射
一、前言
在Java语言中,反射是一个非常核心的基础特性,在Java语言本身,以及以Java为基础的整个Java技术体系中,都承担着非常重要的角色。如工作当中通常会用到的Spring、Mybatis等框架中,都利用了反射这一重要特性。可以说,在Java的整个技术栈世界里,反射无处不在,理解Java反射这一重要特性,是能够自由翱翔于其中的基石。在各技术社区中,讲Java反射的文章多如牛毛,但笔者依然想简单说一下,因为它实在太重要了。
二、什么是反射
通俗地说,Java反射机制,是指在Java程序运行过程中,对任意一个类,都能够获取该类所有的属性和方法,并能够创建对象;对任意一个对象,都能够调用该对象中的属性和方法。即,能够动态地获取类的信息,动态地调用对象的方法和属性。
接下来,通过一个例子进行反射使用的对比演示,让大家对反射有个初步的直观感受。
假设我们有如下的一个类,有几个基础属性,有1个业务方法:
那么,我们创建对象、调用对象中的属性与方法,通过new
关键字方式、反射方式代码实现对比如下:
通过以上的例子,相信你对反射会有一个相对直观的感受,但是,同时可能又会有很多的疑惑,如示例当中,反射方式用到的Class.forName()
到底是什么意思,method.invoke()
又是什么意思? 下一节,笔者将对反射的基本原理与相关核心类进行简单说明。
三、反射的基本原理
一说到基本原理,有些同学可能就会觉得有些头痛,坦率地说,如果真要从源头说起的话,可能需要从JVM的类加载机制与对象的创建过程说起,但这两点稍微展开一说,都需要很长的篇幅,而这不是本文的重点。所以,此处大家只需要知道有这样一个过程就好了,JVM类加载、对象创建大概过程简化如下:(实际过程复杂的多,此处简化细节便于理解反射,以User对象为例)
Tips:
1、将
User.java
源码编译成User.class
二进制字节码文件;2、JVM将
User.class
二进制字节码文件加载到JVM内存中,并生成User对应的唯一的java.lang.Class
对象; (任意实际对象,JVM类加载器加载时都会创建其对应的唯一的1个Class对象)3、当我们通过
User user = new User();
这种方式创建一个User对象时,JVM就会通过User对象对应的java.lang.Class
对象来实例化一个User对象;(JVM通过new
这个关键字,为我们“屏蔽”了类的加载检测、加载、初始化等过程,从而使我们实例化一个对象变得非常容易)4、反射,则是JDK为我们提供了几个基础核心类,让我们可以通过API的方式直接去加载JVM内存中相关类的Class对象,从而可以获取类的构造函数、方法、属性等,并实例化对象。(如果JVM内存中已经存在了实例化的对象,通过反射的方式,则可以修改该对象的属性和方法内容等)
JDK中,关于反射相关操作的类、方法众多,笔者此处对其中核心的类、方法简单说明:
1、java.lang.Class
表示类的实体,在运行中的Java应用程序中表示类和接口。获取一个类的Class对象,通常有如下3种方式:
当获取到 java.lang.Class
对象后,就可以通过相应API获取对象的构造函数、方法、属性、访问修饰符等信息,根据这些信息就可以实例化一个对象,并对其进行相关操作。
2、 java.lang.reflect.Constructor
表示类的构造函数(构造方法)。
例: Constructor constructor = clazz.getConstructor();
3、java.lang.reflect.Method
表示类的方法。
例: Method method = clazz.getDeclaredMethod("doSomethingByName",String.class);
4、java.lang.reflect.Field
表示类的属性。
例:Field field = clazz.getDeclaredField("name");
5、java.lang.reflect.Modifier
用于判断类、方法、属性的访问修饰符,如private
、public
。
例: Modifier.isPublic(method.getModifiers());
说到此处,相信你对上文中笔者通过反射方式创建对象、调用对象的方法、属性有了进一步的理解,此处笔者简单说明如下:
当然,JDK操作反射相关的类、方法远不止这几个,反射的真正使用也比笔者的示例要复杂地多,毕竟真正的类的结构情况、方法的参数情况、属性的访问作用域情况等,都是需要我们考虑的。不过,也不必太过于纠结这些操作反射的类与方法,因为只要理解了笔者以上的这个示例,基本上其它的相关操作方法,也就是API的相关使用了,其主要流程是没有本质区别的。
因此,笔者此处不再对反射操作相关API进行一一列举,不过,需要特别指出的是,需要特别注意method.invoke(Object obj, Object... args);
这个方法,因为通常反射的目的是为了调用对象实例的某个方法;而在各种通用框架源码中(如Spring、Mybatis),也经常会看到类似写法的代码,其本质上很多都是利用了Java的反射机制,从而实现特定的目标。所以,理解反射调用方法的思想,有利于我们读懂源码,有利于我们理解各框架的设计思想。
四、反射的应用场景
说到此处,你可能会有一些疑惑,就是这个反射到底有啥用? 甚至可能有一种感觉,就是自己日常工作当中,很少用到这东西,真的是这样吗?
诚然,如果说日常工作中,只是在现有项目框架之上,针对具体业务需求写CRUD相关代码的话,确实几乎不会需要我们写反射相关代码,这是因为框架层面帮我们封装了许多操作,让我们可以更便利地实现业务需求。
不过,细想一下,Mybatis是如何实现配置文件与实体的映射的?Spring的IOC又是如何实现的?其实,这些技术的背后,几乎都利用了Java的反射机制。
以下列出几项利用了Java反射机制的应用场景:
动态代理实现;
自定义注解;
序列化与反序列化;
JDBC实现;
Spring的IOC、AOP;
...
很多很多,可以说,在我们日常工作中用到的Java技术,其背后几乎都有反射的存在。
五、小结
1、Java反射特性的存在,是Java语言被认为是半动态语言的重要原因之一。因为反射机制的存在,我们可以灵活地实现很多功能。
2、JDK提供了一个功能丰富的反射库工具集(java.lang.reflect
包下),通过该工具集,我们可以方便地操作反射相关的功能。
3、反射的真正运用,远比本文示例中的复杂,如Modifier类,本文仅从基础的角度帮助理解大概流程。
4、如果需要深入理解反射机制,需要知道JVM类加载机制以及Java类的创建过程,有兴趣的可以去了解相关知识。
5、日常工作中,如果不是写底层框架相关代码的话,直接写反射代码的情况不会很多,但是几乎所有的Java技术栈相关的框架都有用到反射,所以,理解反射的基本原理,有利于我们理解整个Java技术栈相关技术的运行原理,可以说是刚需。
6、需要注意Java中对象和实例的区别,如:User 是一个用户对象,但不是实例,因为其只是一个抽象的概念,不表示某一个具体存在的实体,User实例化的 xiaojiang 这个用户才是一个具体的实体。
版权声明: 本文为 InfoQ 作者【老农小江】的原创文章。
原文链接:【http://xie.infoq.cn/article/8d6b3937b136d3759139f61be】。
本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。
评论