深入理解反射
Posted noyone
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入理解反射相关的知识,希望对你有一定的参考价值。
一、类的加载、连接和初始化
这是jvm那块的知识,复习一下,这其实是和反射有关系的。
① 类加载
指的是将类的class文件读入内存,并为之创建一个java.lang.Class对象(类是种定义,但在java里,万物都是对象,即类这种定义也是对象的一种),也就是说,当程序中使用任何类时,系统都会为之建立一个java.lang.Class对象。
系统中所有的class类实际上也是实例,它们都是java.lang.Class的实例。
类的加载由加载器完成,类加载器通常由JVM提供,这些类加载器也是前面所有程序运行的基础。JVM提供的这些类加载器通常被称为系统加载器。除此之外,开发者可以通过继承ClassLoader基类来创建自己的类加载器。
类加载通常无需等到“首次使用”该类是才加载该类,Java虚拟机规范允许系统预先加载某类。
② 类的连接
当类被加载之后,系统为之生成一个对应的Class对象,接着进入连接阶段。连接阶段负责把类的二进制数据合并到JRE中。
三阶段验证、准备、解析,看https://www.cnblogs.com/NoYone/p/8989916.html 连接阶段
③ 类的初始化
初始化阶段的重要工作是执行类的初始化方法<clinit>,方法<clinit>是由编译器自动生成,主要是对静态Field进行初始化(赋值和静态代码块)。
JVM初始化一个类包含如下几个步骤:
- 假如这个类还没有被加载和连接,则程序先加载并连接该类。
- 假如该类的父类还没有被初始化,则先初始化其父类。
- 假如类中有初始化语句(静态),则系统依次执行这些初始化语句。
由第二条可以推出,JVM最先初始化的总是java.lang.Object类。
注意:
- 调用类中的final常量(值在编译时可以确定),不会引起该类初始化(即使是static),因为它在编译其实是连接阶段被赋值,会被当成“宏变量”处理,使用常量时,常量会被不加思考替换成赋的值,所以无需初始化类。就是说在A类中 static final String a = "hello"; 则A.a不引起初始化,而当static final String a = System.currentTimeMills() + ""; (编译时确定不了),则A.a会引起A类的初始化。
- 当使用ClassLoader的loadClass()方法来加载某个类时,该方法只是加载该类,应不会执行类的初始化
- 使用Class的forName()静态方法才会导致强制初始化该类。
- clinit方法只会执行一次
public class 类的初始化测试 { public static void main(String[] args) throws ClassNotFoundException { // 取得系统类加载器,默认的 ClassLoader cl = ClassLoader.getSystemClassLoader(); // 下面语句仅仅加载Tester类 cl.loadClass("Test"); System.out.println("系统加载Tester类"); Syttem.out.printLn("----加载完成----"); // 下面语句才会初始化Tester类 Class.forName("Test"); } } public class Test { static { System.out.println("Test类的静态代码块"); } }
运行结果: JVM : -XX: TraceClassLoading
[Loaded DealReflect.类的初始化测试 from file:/E:/Intellij-Idea/fromXiaoXin/experiment/target/classes/] [Loaded Test from file:/E:/Intellij-Idea/fromXiaoXin/experiment/target/classes/] 系统加载Tester类
----加载完成----
Test类的静态代码块
可以看出loadClass只加载,Class.forName才会引出初始化。
二、类加载器
类加载器负责将.class文件(可能在磁盘上,也可能是网络上)加载到内存中,并为之生成java.lang.Class对象。
- BootStrap ClassLoader(): 根类加载器,它并不是java.lang.ClassLoader的子类(所以从extClassLoader.getParent()是null),而是由JVM自身实现的(非Java)。 jre/lib/rt.jar
- Extension ClassLoade(ExtClassLoader extends URLClassLoader): 扩展类加载器,jre/lib/ext/jar
- System ClassLoader(AppClassLoader extends URLClassLoader): 系统类加载器,它负责在JVM启动时加载来自java命令的-classpath选项、java.class.path系统属性,或CLASSPATH环境变量所指定的JAR包和路径。程序可以通过ClassLoader的静态方法getSystemClassLoader()来获取系统类加载器。如果没有特别指定,则用户自定义的类加载器都以系统类加载器作为父加载器。
注意:这里所说的父子关系不是继承上的父子关系,这里的父子关系是类加载器实例之间的关系(直接说运行顺序不完了。。) URLClassLoader可以从文件或者网络中加载类。例如:file: http:
类加载机制
- 全盘负责
- 父类委托。先让父加载器加载,父加载器加载不上自己再加载。
- 缓存机制。缓存机制将加载过Class都缓存,当程序中需要使用某个Class时,类加载器先从缓存区搜寻该Class。这就是为什么修改了Class后需要重启JVM,修改才会被应用上。
JVM中4种类加载器层次
三、通过反射查看类信息
① 获得Class对象
- Class类的forName(String clazzName)静态方法。该字符串参数是某个类的全限定类名(要包含包名)
- 调用某个类的class属性来获取到该类的Class对象。
- 调用某个类的getClass()方法。该方法是java.lang.Object的方法,所有的Java对象都可以调用该方法。
建议用第二种,因为能在编译阶段就能判断需要访问的Class对象是否存在。
获取到类对象后,程序就可以调用Class对象的方法来获取该对象和该类的真实信息了。
② 从Class中获取信息
四、通过反射生成并操作对象
五、通过反射生成动态代理
在Java的java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过使用这个类和接口可以生成JDK动态代理类或动态代理对象。
Proxy提供了用于创建动态代理类和动态代理对象的方法,它是所有动态代理类的父类。
执行代理对象的每个方法时都会被替换执行InvocationHandler对象的invoke方法。如何看生成的代理类里的代码(保存代理类过程在https://www.cnblogs.com/NoYone/p/8733868.html)
注意super.h.invoke,上面也说了,Proxy是父类,h就是传给Proxy的InvocationHandler,调用invoke方法。
Proxy类的两个方法:
-
public static Class<?> getProxyClass(ClassLoader loader,
Class<?>... interfaces):创建一个动态代理类对象的Class对象,该代理类将实现interfaces所指定的多个接口。第一个参数XXX.class.getClassLoader() -
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h):直接创建一个动态代理对象,该代理对象的实现类实现了interfaces指定的系列接口。
实际上,即是采用第一个方法获取了一个动态代理类之后,当程序需要通过该代理类来创建对象时一样需要传入一个InvocationHandler对象。也就是说,系统生成的每个代理类对象都有一个与之关联的InvocationHandler对象。即super.h.invoke(Object, Method, Object[])
小Demo看这里的就可以https://www.cnblogs.com/NoYone/p/8733868.html
以上是关于深入理解反射的主要内容,如果未能解决你的问题,请参考以下文章
深入理解设计模式-策略模式(结合简单工厂反射Spring详细讲解)