Java提供的反射机制允许你在运行时检查类的信息
Java的类加载
Java在真正需要使用一个类时才会去加载类,而不是在启动程序时就载入所有的类,因为大多数使用者都只使用到程序的部分资源,在需要某些功能时再载入某些资源,可以让系统资源运用的更高效。
类的加载指的是将类的.class
文件中的二进制数据读入到内存
中,将其放在Jvm的方法区
内,然后在堆
区创建一个java.lang.Class
对象,用来封装类在方法区内的数据结构。类的加载的最终产品是位于堆区中的Class对象
,Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口。
Java 中的所有类型包括基本类型(int
, long
, float
等等),即使是数组都有与之关联的 Class 类的对象。
Class对象是由Jvm自动生成的,每当一个类被载入时,Jvm就自动为其生成一个Class对象
Class对象
实例.getClass()
通过Object的getClass()
获取每一个实例
对应的Class对象
1 | java复制代码String name = "hello"; |
1 | 复制代码类的名称:java.lang.String |
类名.class
你也可以直接使用一下方式来获取String类的Class对象
1 | java复制代码Class stringClass = String.class; |
Class.forName()
在一些应用中,你无法事先知道使用者将载入什么类别,你可以使用Class的静态方法forName()
来动态加载类别
1 | java复制代码Class c = Class.forName(args[0]); |
1 | 复制代码$ java Demo1 java.util.Scanner |
Class.forName()有两个版本,上面的版本只指定了全限定类名,而另一个版本可以让你指定类名,载入时是否执行静态代码块,执行类加载器(ClassLoader
)
1 | java复制代码static Class forName(String name, boolean initialize, ClassLoader loader) |
1 | java复制代码ClassLoader loader = Thread.currentThread().getContextClassLoader(); |
从Class对象中获取信息
Class对象表示所载入的类别,获取Class对象后,你就可以获取类别相关的信息,入 package
, constructor
, field
, method
等信息。
而每一种信息,都有相对应的类别
- package: java.lang.reflect.Package
- constructor: java.lang.reflect.Constructor
- field: java.lang.reflect.Field
- method: java.lang.reflect.Method
1 | java复制代码Class c = Class.forName(args[0]); |
1 | 复制代码$ java Demo1 java.util.ArrayList |
ClassLoader 类加载器
Java在需要使用类的时候才会将类载入,Java中类的载入是由Class Loader
来实现的.
当你尝试执行java xxx
命令时,java会尝试找到JRE
的安装目录,然后寻找jvm.dll
,接着启动JVM并进行初始化操作,接着产生BootstrapLoader
,Bootstrap Loader
会载入Extended Loader
, 并设定Extended Loader
的parent 为 BootstrapLoader
, 接着Bootstrap Loader
会载入 Application Loader
, 并将Application Loader
的parent 设定为Extended Loader
启动类加载器
BootstrapLoader
搜寻 sun.boot.library.path
中指定的类, 你可以使用System.getProperty("sun.boot.library.path")
来获取
1 | 复制代码/Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib |
扩展类加载器
Extended Loader
(sun.misc.Launcher$ExtClassLoader
) 是由Java编写的,会搜寻系统参数java.ext.dirs
中指定的类别,可以通过System.getProperty("java.ext.dirs")
来获取
1 | 复制代码/Users/dsying/Library/Java/Extensions: |
应用程序类加载器
Application Loader
(sun.misc.Launcher$AppClassLoader
) 是由Java编写的,会搜寻系统参数java.class.path
中指定的类别,可以通过System.getProperty("java.class.path")
来获取, 在使用java xxx
命令执行.class
字节码文件时,可以通过-cp
参数设定classpath
1 | shell复制代码java –cp ./classes SomeClass |
类加载器之间的关系
1 | java复制代码ClassLoader loader Thread.currentThread().getContextClassLoader(); |
1 | 复制代码sun.misc.Launcher$AppClassLoader@18b4aac2 |
类加载有三种方式:
- 命令行启动应用时候由JVM初始化加载
- 通过
Class.forName()
方法动态加载 - 通过
ClassLoader.loadClass()
方法动态加载
Class.forName()和ClassLoader.loadClass()区别
Class.forName()
:将类的.class文件加载到jvm中之外,还会对类进行解释,执行类中的static块;ClassLoader.loadClass()
:只干一件事情,就是将.class文件加载到jvm中,不会执行static中的内容,只有在newInstance才会去执行static块。Class.forName(name, initialize, loader)
带参函数也可控制是否加载static块。并且只有调用了newInstance()方法采用调用构造函数,创建类的对象。
JVM类加载机制
全盘负责
,当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入父类委托
,先让父类加载器试图加载该类,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类缓存机制
,缓存机制将会保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区寻找该Class,只有缓存区不存在,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓存区。这就是为什么修改了Class后,必须重启JVM,程序的修改才会生效
双亲委派模型
双亲委派模型的工作流程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上,因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围中没有找到所需的类时,即无法完成该加载,子加载器才会尝试自己去加载该类。
双亲委派机制:
- 当
AppClassLoader
加载一个class时,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器ExtClassLoader
去完成。 - 当
ExtClassLoader
加载一个class时,它首先也不会自己去尝试加载这个类,而是把类加载请求委派给BootStrapClassLoader
去完成。 - 如果
BootStrapClassLoader
加载失败(例如在$JAVA_HOME/jre/lib
里未查找到该class),会使用ExtClassLoader
来尝试加载; - 若
ExtClassLoader
也加载失败,则会使用AppClassLoader
来加载,如果AppClassLoader
也加载失败,则会报出异常ClassNotFoundException
。
ClassLoader源码分析:
1 | java复制代码public Class<?> loadClass(String name)throws ClassNotFoundException { |
双亲委派模型意义:
- 系统类防止内存中出现多份同样的字节码
- 保证Java程序安全稳定运行
自定义加载器
自定义类加载器一般都是继承自ClassLoader
类,从上面对loadClass
方法来分析来看,我们只需要重写 findClass
方法即可。下面我们通过一个示例来演示自定义类加载器的流程:
1 | java复制代码package com.github.hcsp.classloader; |
自定义类加载器的核心在于对字节码文件的获取
,如果是加密的字节码则需要在该类中对文件进行解密。由于这里只是演示,我并未对class文件进行加密,因此没有解密的过程.
其它
使用反射创建对象
你可以使用Class的newInstance()
方法来实例化
1 | java复制代码Class c = Class.forName(className); |
调用方法
使用反射可以取回类中的方法,方法对应的类为java.lang.reflect.Method
, 你可以使用它的invoke()
方法来调用指定的方法
1 | java复制代码Class testClass = myClassLoader.loadClass("com.github.hcsp.MyTestClass"); |
本文转载自: 掘金