倚楼听风雨
淡看江湖路

浅析Java反射系列相关基础知识(上)之类的加载以及反射的基本应用

Java知识体系中「反射」相关知识还是很重要的,尤其以后你再工作生活中使用 Spring 的 AOP(Aspect Oriented Programming,面向切面编程)或者面试中都是频繁出现的考点,之前在文章《Java十道由浅入深的笔面试题第三期 详细解析》中已经阐述了一些相关的知识,本次将Java反射中的知识点再次进行一下细化,从头到尾进行梳理一下,最后不识抬举的和你们一起阅读一些Spring AOP的源码,看看Spring AOP是如何通过 JVM(Java Virtual Machine,Java虚拟机) 的动态代理来实现切面编程的。

类反射的前提介绍之类的加载机制

类的加载、连接、初始化

首先我们简单的说一下 JVM,当我们调用了 Java 命令运行了一个 Java 程序的时候,JVM的一个进程也就启动了。同一个 JVM 的所有线程、变量都会处于在这个进程中,共享内存区。JVM 进程终止的几种情况:

  • 程序运行正常结束
  • System.exit() 或者 Runtime.getRuntime().exit()
  • 异常未捕获或者 error
  • 强制结束 JVM 进程

我们都知道 Java 中存在一个 java.lang.Class 类,当程序想要使用某个 Java 类的时候,JVM 需要通过加载、连接、初始化三个步骤来对这个类进行初始化操作,也就是将类的class文件读到内存当中并未这个类实例化一个 java.lang.Class 对象的过程,这个过程就叫做「类加载」或者「类初始化」。所以,类是对对象的抽象,那么类其实也是对象,在加载过程中,所有的类都是 java.lang.Class 的实例。

类加载的过程中最终的是类的加载需要使用类加载器,这个老四会详细介绍一下,至于类的连接和初始化过程这里简单的介绍一下:生成 Class 实例之后,类加载就会进入类的连接阶段,所谓的连接就是将类的字节码文件,即这些二进制的数据合并到JRE(Java Runtime Environment,Java运行环境)当中。类的连接的三个阶段:

  • 验证:验证阶段用于检验被加载的类是否有正确的内部结构,并和其他类协调一致。
  • 准备::类准备阶段负责为类的类变量分配内存,并设置默认初始值。
  • 解析::将类的二进制数据中的符号引用替换成直接引用。

接下来就是类的初始化操作了,所谓的初始化就是对类变量、静态初始块、类的结构块等进行初始化,就像下面这样:

类的初始化一般步骤如下:

  • 加入这个类还没有被加载和连接,则程序先加载并连接该类。
  • 假如该类的直接父类还没有被初始化,则先初始化其直接父类,依次往上进行初始化,所以 JVM 最先初始化的总是 java.lang.Object 类
  • 假如类中有初始化语句,则系统一次执行这些初始化语句

类初始化的时机一般有如下几种:

  • 创建类的实例 new 或者反射
  • 调用类方法,即静态方法
  • 访问类变量或者为类变量赋值
  • 使用反射创建 java.lang.Class 对象,例如 Class.forName()。
  • 初始化某个类的子类
  • 直接使用 java.exe 命令运行某个主类

类加载器

类的加载是靠系统中的类加载器来完成将 .class 文件读入到内存中,可能我们工作当中经常敲得是业务代码,很少会涉及使用到类加载器,甚至是自定义类加载器,但是不代表我们不需要知道了解这一块的内容,深入的了解一些 JVM 的底层内容也会帮助我们更好的理解 Java 这门语言的运行机制,从而更好的编写代码。

在JVM系统中,有三个层次类加载器,当然我们也说过我们也可以自定义类加载器,这些都离不开一个类 - ClassLoader 类,JVM 中除了下面的根类加载器不是 java.lang.ClassLoader 的派生类,剩下的系统类加载器以及我们要自定义的类加载器均要通过继承 ClassLoader 类来实现:

  • Bootstrap ClassLoader,根类加载器,也称作原始类加载器或者引导类加载器。负责加载Java核心类。然而根类加载器是由 JVM 自身实现的。
  • Extension ClassLoader,扩展类加载器。负责加载JRE的扩展目录,具体点说可以理解为主要加载%JAVA_HOME%\lib\ext目录下的库类。
  • System ClassLoader,系统类加载器,也叫作应用类加载器。负责在JVM启动时加载 主要负责加载Classpath指定的库类,一般情况下这是程序中的默认类加载器,也是ClassLoader.getSystemClassLoader() 的返回值。

上面三个类加载器的层次由高到低,所以你可能听说的双亲委托的类加载机制(也称作「父类委托」),这个机制指的就是JVM的类加载机制,或者说类加载器加载类的方式:先让父层次级别的类加载器视图加载该 Class,当父类加载器无法加载该类才会尝试从自己的类路径加载该类。所以每一层的类加载器都是如此,将请求委托给自己的父加载器,所有的类加载请求最终都应该传送到顶层的 Bootstrap ClassLoader 中,只有当父加载器反馈自己无法完成加载请求时,子加载器才会尝试自己加载。除了比较重点的双亲委托,此外还有两种类加载的机制:

  • 全盘负责。全盘负责指的是当一个类加载器负责加载某个 Class 时,该 Class 所依赖的和引用的 Class 也都是这个类加载器负责加载。
  • 缓存机制。缓存机制解释了我们修改源代码的时候需要重新运行程序(排除热部署的情况),就是因为缓存机制保证了加载过的 Class 首先会被缓存程序首先查询缓存,缓存没有再进行来加载器加载并转换 Class 对象。

自定义类加载器:

首先我们需要了解为什么需要自定义类加载器,最佳实践在哪里?其实自定义类加载器在实际的应用中多数用于「对接」,所谓对接就是 Java 类的网络传输的处理,首先为了安全性,为了网络传输过程Java类不被轻易的反编译,我们需要加密,那么解密的过程就需要我们使用自定义类加载器,其次就是网络传输的来源,非标准的代码来源就需要我们根据相应的场景创建自定义加载器来从特定源加载Class。

下面老四举一个简单自定义类加载器代码示例,实现一个简单的 .class 类文件读取并实例化对象。

先创建一个 Glorze 类作为本文讲解的基础示例类:

URLClassLoader 类

前面说到类加载器有三个层次级别的类加载器,这个具体的 URLClassLoader 类是介于扩展类加载器和根类加载器之间,主要负责的就是从文件系统或者网络传输中获取二进制 class 文件来加载类。其中比较重要的源码如下:

从源码中我们可以看到我们可以传入URL数组参数,这里的url不仅是网络传输的 url,也可以是本地系统文件路径,当然ftp这样路径也是支持,所以功能比较强大。因为使用方法就是利用源代码中构造来加载类,所以老四这里就不再举例。

Java 中的反射

通过反射查看类详细信息

我们都知道Java 除基本类型外其他都是 class(包括interface),但是其实 JVM 也会为基本数据类创建class文件,如int.class。class(包括interface)的本质其实也是数据类型(Type),所以没有继承关系的数据类型就无法赋值(String s = new Double(888.88)),那么理所当然,类或者接口也要有自己的数据类型,即 Java 类的数据类型-Class,每加载一个 class 或者 interface,JVM 就为其创建一个 Class 类型的实例并关联。JVM 持有的每个Class实例都指向一个数据类型(class 或者 interface)。

JVM 为每个加载的 class 创建对应的 Class 实例,并在实例中保存该 class 的所有信息。如果获取了某个 Class 实例,则可以获取到该实例对应的 class 的所有信息。通过 Class 实例获取 class 信息的方法我们称为反射(Reflection)。也就是说,当我们遇到这种程序只能够依靠运行时才能知道对象和类真实信息的情况时,就需要我们使用反射。

获取一个 class 的 Class 实例的三种方式:

  1. Type.class -- 推荐使用,代码更安全,因为在编译阶段就可以检查 Class 对象是否存在,其次是程序性能更好,无须调用方法。
  2. getClass() -- 该方法能够属于 Object 类,所以所有的 Java 对象都可以调用此方法。
  3. Class.forName(String clazzName) -- clazzName代表的类的完整包名加类名

通过反射实例化并操作对象

通过反射实例化生成对象的两种方式:

  • 使用 Class 的 newInstance() 方法,推荐
  • 先用 Class 对象获取指定的构造对象(Constructor),再调用构造对象的 newInstance() 方法创建 Class 实例 。

简单的示例如下:

Class 实例和 instanceof 的差别:

Java 中还有一个 instanceof 运算符,用于判断该运算符前面的对象是否属于后面的类,所以 instanceof 运算符前面一般是引用类型变量,后面是一个类,后面的这个类可以是当前引用变量归属对象的类、子类、接口的实现类。而相对 Class 实例在 JVM 中是唯一的,所以 Class 实例我们通常使用==来判断。示例如下:

接着列举几个常用的 Constructor 类的一些常用方法和构造方法,其余的可以参考JDK文档查阅。

  • Constructor<?> getConstructor(Class<?>... parameterTypes):返回该 Class 对象对应类的指定形参列表的 public 构造器。
  • Constructor<?>[] getConstructors():返回Class对象对应类所有的public构造器。
  • Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes):返回该 Class 对象对应类的指定形参列表的构造器,与访问权限无关。
  • Constructor<T>[] getDeclaredConstructors():返回Class对象对应类所有的构造器,与访问权限无关。

通过反射调用方法

我们既然可以实例化生成对象,那么对应的我们就可以调用类中的方法,通过使用 Class 类的 getMethods() 或者 getMethod() 放获取全部方法或者某个方法,返回 Method 对象或者 Method[] 数组。然后在 Method 对象中提供了一个 invoke() 方法来帮助我们实现对反射类中的方法进行调用。invoke 方法源码以及 getMethod 相关方法如下所示:

  • Method getMethod(String name, Class<?>... parameterTypes):返回该 Class 对象对应类的指定形参列表的 public 方法。
  • Method[] getMethods():返回该 Class 对象对应类的所有的 public 方法。
  • Method getDeclaredMethod(String name, Class<?>... parameterTypes):返回该 Class 对象对应类的指定形参列表的 public 方法,与访问权限无关。
  • Method[] getDeclaredMethods():返回该 Class 对象对应类所有的 public 方法,与访问权限无关。

这里有个地方需要我们注意一下,就是当我们使用 invoke() 方法回调类中方法的时候,Java会要求我们必须有权限,如代码中所示,sayBye() 方法是私有方法,我们需要设置 Method 对象中的 setAccessible(boolean flag) 方法flag值为 true 才可以调用。代表着取消对访问权限的检查,同理,后面的 Field 也是通过这个方法实现访问私有变量的。但是请注意,setAccessible(true) 并不是万能的,你看上面老四贴出来的源码,其中是绑定了 SecurityManager 的,SecurityManager 的规则可以阻止对方法或者对变量设置accessible的,例如:规则应用于所有 java 和javax 开头的 package 的类。

通过反射访问成员变量

跟调用方法类似,反射中使用 getField 方法来获得 Field 对象或者数组,从而实现访问反射类的全部成员变量值,同时我们也可以通过方法来给反射类的成员变量设置值,代码参考上方,Class 类中获取反射类的成员变量方法如下:

  • Field getField(String name):返回该 Class 对象对应类的指定名称的 public 成员变量。
  • Field[] getFields():返回该 Class 对象对应类的指定名称的所有 public 成员变量。
  • Field getDeclaredField(String name):返回该 Class 对象对应类的指定名称的成员变量,与访问权限无关。
  • Field[] getDeclaredFields():返回该 Class 对象对应类的指定名称的所有成员变量,与访问权限无关。

通过反射获取继承关系

获取当前反射类的继承关系需要使用 getSuperclass() 方法,接口则使用 getInterfaces() 方法返回对应类所实现的全部接口。那么特殊的Object的父类是null,interface的父类是null,getInterfaces() 不包括间接实现的 interface,没有 interface 的 class 返回空数组,interface 返回继承的 interface。

除了以上常用的反射类操作方法,Class类中还有许多实例方法类获取 Class 对象所对应类的详细信息,篇幅有限,老四列出部分可能也比较常用的一些方法,剩下的请参阅官方文档来查看。

获取注解信息相关方法

  • boolean isAnnotation():返回此 Class 对象是否表示一个注解类型(由 @interface 定义)。
  • boolean isAnnotationPresent(Class<? extends Annotation> annotationClass):判断此 Class 对象是否使用了 Annotation 修饰。
  • <A extends Annotation>A getAnnotation(Class<A> annotationClass):尝试获取该 Class 对象对应类上存在的、指定类型的 Annotation;如果该类型的注解不存在,则返回 null。
  • <A extends Annotation>A getDeclaredAnnotation(Class<A> annotationClass):这是Java8新增的方法,该方法尝试获取直接修饰该 Class 对象对应类的、指定类型的 Annotation;如果该类型的注解不存在,则返回 null。
  • Annotation[] getAnnotations():返回修饰该 Class 对象对应类上存在的所有 Annotation。
  • Annotation[] getDeclaredAnnotations: 返回直接修饰该 Class 对应类的所有 Annotation。
  • <A extends Annotation>A[] getAnnotationsByType(Class<A> annotationClass):该方法的功能与前面介绍的 getAnnotation()方法基本相似。但由于 Java8 增加了重复注解功能,因此需要使用该方法获取修饰该类的、 指定类型的多个 Annotation。
  • <A extends Annotation>A[] getDeclaredAnnotationsByType(Class<A> annotationClass):该方法的功能与前面介绍的 getDeclaredAnnotations() 方法基本相似。但由于 Java8 增加了重复注解功能,因此需要使用该方法获取直接修饰该类的、指定类型的多个 Annotation。

获取反射类对应内/外部类的相关方法

  • boolean isAnonymousClass):返回此 Class 对象是否是一个匿名类。
  • Class<?>[] getDeclaredClasses():返回该Class对象对应类里包含的全部内部类。
  • Class<?>getDeclaringClass:返回该Class对象对应类所在的外部类.

获取反射类接口以及继承关系相关方法

  • boolean isInterface():返回此 Class 对象是否表示一个接口(使用 interface 定义)。
  • Class<>[] getInterfaces:返回该 Class 对象对应类所实现的全部接口。
  • Class<? super T> getSuperclass():返回该 Class 对象对应类的超类的 Class 对象。

获取 Class 对象对应类的修饰符、 所在包、 类名等基本信息

  • int getModifiers():返回此类或接口的所有修饰符。修饰符由 public,protected,private,final,static, abstract 等对应的常量组成, 返回的整数应使用 Modifier 工具类的方法来解码, 才可以获取真实的修饰符。
  • Package getPackage:获取此类的包。
  • String getNam():以字符串形式返回此 Class 对象所表示的类的名称。
  • String getSimpleName():以字符串形式返回此 Class 对象所表示的类的简称。
  • boolean isArray():返回此 Class 对象是否表示一个数组类。
  • boolean isEnum():返回此 Class 对象是否表示一个枚举(由 enum 关键字定义)。
  • boolean isInstance(Object obj):判断 obj 是否是此 Class 对象的实例, 该方法可以完全代替 instanceof 操作符。

更博不易,如果觉得文章对你有帮助并且有能力的老铁烦请捐赠盒烟钱,点我去赞助。或者扫描文章下面的微信/支付宝二维码打赏任意金额(点击「给你买杜蕾斯」),也可扫描小站放的支付宝领红包二维码,线下支付享受优惠的同时老四也可以获得对应赏金,老四这里抱拳谢谢诸位了。捐赠时请备注姓名或者昵称,因为您的署名会出现在赞赏列表页面,您的捐赠钱财也会被用于小站的服务器运维上面,再次抱拳感谢。

赞(22) 给你买杜蕾斯
本站原创文章受自媒体平台原创保护,未经允许不得转载高老四博客 » 浅析Java反射系列相关基础知识(上)之类的加载以及反射的基本应用
分享到: 更多 (0)

开始你的表演 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下老四,鼓励我更好的创作

支付宝扫一扫打赏

微信扫一扫打赏