导图社区 类的加载机制与反射
类的加载机制与反射思维导图,内容有类的加载、连接和初始化、类加载器、.通过反射查看类信息、反射和泛型、使用反射生成JDK动态代理、使用反射生成并操作对象。
编辑于2023-03-18 14:18:13 江苏省类的加载机制与反射
1.类的加载、连接和初始化
JVM和类
同一个JVM的所有线程、所有变量都处于同一个进程里,它们都使用该JVM进程的内存区。
JVM进程被终止的条件:①程序运行到最后正常结束 ②程序运行到使用System.exit()或Runtimely.getRuntime().exit()代码处结束程序 ③程序执行过程中遇到未捕获的异常或错误而结束 ④程序在所有平台强制结束JBM进程
当Java程序运行结束时,JVM进程结束,该进程在内存中的状态会全丢失
类的加载
当程序主动使用某个类时,如果该类还未被加载到内存中,系统会通过加载、连接、初始化三个步骤来对类初始化
当程序中使用任何类时,系统会为之建立一个Java.lang.Class对象
类加载器通常无需等到“首次使用”该类时才加载该类,Java虚拟机规范允许系统预先加载某些类
类的连接
类连接的三个阶段:验证、准备、解析
类的初始化
虚拟机负责对类进行初始化,主要就是对类变量进行初始化。
在java类中对类变量指定初始值的两种方式:①声明类变量时指定初始值②使用静态初始化块为类变量指定初始值。
JVM最先初始化的总是Java.lang.Object类,JVM初始化类的步骤: ①假如这个类还没有被加载和连接,则程序先加载并连接该类。 ②假如该类的直接父类还没有被初始化,则先初始化其直接父类。 ③假如类中有初始化语句,则系统依次执行这些初始化语句。
类初始化的时机
系统初始化类或接口的情况: ➢创建类的实例。 ➢调用某个类的类方法(静态方法)。 ➢访问某个类或接口的类变量,或为该类变量赋值。 ➢使用反射方式来强制创建某个类或接口对应的java.lang.Class 对象。例如代码:>初始化某个类的子类。当初始化某个类的子类时,该子类的所有父类都会被初始化。 ➢直接使用java.exe命令来运行某个主类。当运行某个主类时,程序会先初始化该主类。 对于一个final型的类变量,如果该类变量的值在编译时就可以确定下来,那么这个类变量相当于“宏变量”。Java编译器会在编译时直接把这个类变量出现的地方替换成它的值,因此即使程序使用该静态类变量,也不会导致该类的初始化。
利用final修饰的类变量在程序其他地方使用时相当于使用常量
当使用ClassL oader类的loadClass方法来加载某个类时,该方法只是加载该类,并不会执行该类的初始化。使用Class的forName()静态方法才会导致强制初始化该类。
2.类加载器
类加载机制
在JVM中,一个类用其全限定类名和其类加载器作为唯一标识。
当JVM启动时,会形成由三个类加载器组成的初始类加载器层次结构。 ➢Bootstrap ClassL oader: 根类加载器。 ➢Extension ClassLoader:扩展类加载器。 ➢System ClassLoader:系统类加载器。 Bootstrap Class oader被称为引导(也称为原始或根)类加载器,它负责加载Java的核心类。
JVM的类加载机制主要有如下三种。 ➢全盘负责。所谓全盘负责,就是当一个类加载器负贵加载某个Class时,该Class所依赖的不用的其他Class也将由该类加载器负责载入,除非显式使用另外一个类加载器来载入。 ➢父类委托。所谓父类委托,则是先让parent (父)类加载器试图加载该Class,只有在父类器无法加载该类时才尝试从自己的类路径中加载该类。 ➢缓存机制。 缓存机制将会保证所有加载过的Class都会被缓存,当程序中需要使用某个Class类加载器先从缓存区中搜寻该Class,只有当缓存区中不存在该Class对象时,系统才会读类对应的二进制数据,并将其转换成Class对象,存入缓存区中。这就是为什么修改了Class必须重新启动JVM,程序所做的修改才会生效的原因。 类加载器之间的父子关系并不是类继承上的父子关系,这里的父子关系是类加载器实例之间的关系。
系统类加载器是Applass oader的实例,扩展类加载器PlatformClassLoader的实例。实际上,这两个类都是URLClassL oader类的实例。 JVM的根类加载器并不是Java实现的,而且由于程序通常无须访问根类加载器,因此访问扩展类加载器的父类加载器时返回null。
创建并使用自定义的类加载器
ClassLoader类有如下两个关键方法。 ➢loadClas(String name, boolean resolve): 该方法为ClassLoader的入口点,根据指定名称来加载类,系统就是调用ClassLoader的该方法来获取指定类对应的Class对象。 ➢findClass(String name):根据指定名称来查找类。 如果需要实现自定义的ClassLoader,则可以通过重写以上两个方法来实现,通常推荐重写findClass,重写findClass方法可以避免覆盖默认类加载器的父类委托、缓冲机制两种策略:如果重写loadClass方法,则实现逻辑更为复杂。
使用自定义的类加载器,可以实现如下常见功能: ➢执行代码前自动验证数字签名。 ➢根据用户提供的密码解密代码,从而可以实现代码混淆器来避免反编译*.class文件。 ➢根据用户需求来动态地加载类。 ➢根据应用需求把其他数据以字节码的形式加载到应用中。
URLClassLoader类
在应用程序中可以直接使用URLClassLoader加载类,URLClassLoader类提供了如下两个构造器。 ➢URLClass oader(URLO urls);: 使用默认的父类加载器创建一个 ClassLoader 对象,该对象将从 urls所指定的系列路径来查询并加载类。 ➢URLClass oader(URL[] urls, ClassLoader parent):使用指定的父类加载器创建个ClassLoader 对象,其他功能与前一个构造器相同。
直接从文件系统中加载MySQL驱动,并使用该驱动来获取数据库连接。通过这种方式来获取数据库连接,可以无须将MySQL驱动添加到CLASSPATH环境变量中。
3.通过反射查看类信息
获得Class对象
获得Class对象通常有如下三种方式: ➢使用Class类的forName(String clazzName)静态方法。 ➢调用某个类的class属性来获取该类对应的Class对象。 ➢调用某个对象的getClass()方法。
第二种方式有如下两种优势: ➢代码更安全。程序在编译阶段就可以检查需要访问的Class对象是否存在。 ➢程序性能更好。因为这种方式无须调用方法,所以性能更好。
从Class中获取信息
获取构造器时无须传入构造器名同一个类的所有构造器的名字都是相同的,所以要确定一个构造器只要指定形参列表即可。程序获取了ClassTest 类对应的Class对象后,通过调用该Class对象的不同方法来得到该Class 对象的详细信息。
通过Class对象可以得到大量的Method、Constructor、 Field 等对象,这些对象分别代表该类所包 括的方法、构造器和成员变量等,程序还可以通过这些对象来执行实际的功能,例如调用方法、创建 实例。
方法参数反射
Exccutable 提供了如下两个方法来获取该方法或参数的形参个数及形参名。 ➢int getParameter ount():获取该构造器或方法的形参个数。 ➢Parameter[] getParameters():获取该构造器或方法的所有形参。 使用javac命令编译Java源文件时,默认生成的class文件并不包含方法的形参名信息,因此调用isNamePresent()方法将会返回false,调用getName()方法也不能得到该参数的形参名。如果希望javac命令编译Java源文件时可以保留形参信息,则需要为该命令指定-parameters选项。
6.反射和泛型
泛型和Class类
使用反射来获取泛型信息
5.使用反射生成JDK动态代理
使用Proxy和InvacationHandler创建动态代理
动态代理和AOP
4.使用反射生成并操作对象
创建对象
通过反射来生成对象需要先使用Class对象获取指定的Constructor对象,再调用Constructor对象的newInstancc()方法来创建该Class对象对应类的实例。程序需要根据该字符串来创建对应的实例,就必须使用反射。
如果不想利用默认构造器来创建Java对象,而想利用指定的构造器来创建Java对象,则需要利用Constructor对象,每个Constructor对应一个构造器。为了利用指定的构造器来创建Java对象,需要如下三个步骤: ➊获取该类的Class对象。 ❷利用Class对象的getConstructor方法来获取指定的构造器。 ❸调用Constructor的newInstance方法来创建Java对象。
调用方法
当获得某个类对应的Class 对象后,就可以通过该Class对象的getMethods(方法或者getMethodO方法来获取全部方法或指定方法一这两个方法的返回值是Method数组,或者Method对象。 每个Method对象对应一个方法,获得Method对象后,程序就可通过该Method来调用它对应的方法。在Method里包含一个invoke(方法,该方法的签名如下。 ➢Object invoke(Object obj, Object.. args): 该方法中的obj是执行该方法的主调,后面的args是执行该方法时传入该方法的实参。
当通过Method的invoke()方法来调用对应的方法时,Java会要求程序必须有调用该方法的权限。如果程序确实需要调用某个对象的private方法,则可以先调用Method对象的如下方法。 ➢setAccessible(boolean flag): 将Method对象的acessible设置为指定的布尔值。值为true, 指示该Method在使用时应该取消Java语言的访问权限检查;值为false,则指示该Method在使用时要实施Java语言的访问权限检查。
访问成员变量值
通过Class对象的getFields()或getField)方法可以获取该类所包括的全部成员变量或指定成员变量。Field提供了如下两组方法来读取或设置成员变量值。 ➢getXxx(Object obj):获取obj对象的该成员变量的值。此处的Xxx对应8种基本类型,如果该成员变量的类型是引用类型,则取消get后面的Xxx。 ➢setXxx(Object obj, Xxx val);: 将obj对象的该成员变量设置成val值。此处的Xxx对应8种基本类型,如果该成员变量的类型是引用类型,则取消set后面的Xxx。
操作数组
在agreflet包下还提供了一个Array类,Array 对象可以代表所有的数组。程序可以通过使用Array来动态地创建数组,操作数组元素等。Array提供了如下几类方法。 ➢static Objeet newInstance(Class componentType, int... length);:创建一 - 个具有指定的元素类型、指定维度的新数组。 ➢static xxx getXxx(Object array, int index):返回array数组中第index个元素。其中xxx是各种基本数据类型,如果数组元素是引用类型,则该方法变为get(Object array, int index)。 ➢static void setXxx(Object aray, int index, xxx val):将aray数组中第index个元素的值设为val. 其中Xxx是各种基本数据类型,如果数组元素是引用类型,则该方法变成st(Object aray, intindex, Object val)。
Java11新增的嵌套访问权限
Java11引入了嵌套上下文的概念。通过嵌套访问权限的支持,Java11统一了通过反射访问和不通过反射访问时的权限一不致的问题。因此,如果使用Java11编译、运行上面程序,将可以看到程序可以成功运行。 与之对应的是,Java 11为Class类新增了如下方法。 ➢Class<?> getNestHost():返回该类所属的嵌套属主。 ➢boolean isNestmateOf(Class<?> c):判断该类是否为c的嵌套同伴(nestmate),只要两个类有相同的嵌套属主,它们就是嵌套同伴。 ➢Class<?>[] getNestMembers():获取该类的所有嵌套成员。