-
【Java类加载】自定义类加载器
要自定义自己的类加载器来加载类,需要先对类加载器和类加载机制有一些基本的了解。
1、类加载器
类加载器ClassLoader的作用有两个:
①是用于将class文件加载到JVM。
②是用于判断JVM运行时两个类是否相等。
2、类加载的时机
类的加载可分为隐式加载和显示加载。
隐式加载
隐式加载包括以下几种情况:
- 遇到new(new 一个实例对象的时候)、getstatic(获取一个类的静态字段的时候)、putstatic(设置一个类的静态字段的时候)、invokestatic(调用一个类的静态方法的时候)这4条字节码指令时。
- 对类进行反射调用时。
- 初始化一个类时,如果父类还没有初始化,则先加载其父类并初始化(但是初始化接口时,不要求先初始化父接口)
- 虚拟机启动时,需要指定一个包含main函数的主类,优先加载并初始化这个主类。
显式加载
显示加载包含以下几种情况:
- 通过Class.forName()加载
- 通过ClassLoader的loaderClass方法加载
- 通过ClassLoader的findClass方法
3、Class.forName()加载类
Class.forName()和ClassLoader都可以对类进行加载。ClassLoader就是遵循双亲委派模型最终调用启动类加载器的类加载器,实现的功能是“通过一个类的全限定名来获取描述此类的二进制字节流”,获取到二进制流后放到JVM中。Class.forName()方法实际上也是调用的CLassLoader来实现的。
先看看Class.forName()的源码:
/**
* 参数解释:
* 1、className:要加载的类名
* 2、true:class被加载后是否要被初始化。初始化即执行static的代码(静态代码)
* 3、caller:指定类加载器
*/
public static Class<?> forName(String className) throws ClassNotFoundException {
Class<?> caller = Reflection.getCallerClass();
return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}
可以看出来最后正在实现forName()方法的是forName0这一个native方法。而使用forName0需要传递的参数之一,就是ClassLoader类加载器。因此可以知道Class.forName()本质还是使用classloader来进行类的加载的。
4、使用ClassLoader加载类
ClassLoader 里面有三个重要的方法 loadClass()、findClass() 和 defineClass()。
loadClass() 方法是加载目标类的入口,它首先会查找当前ClassLoader以及它的父类classloader里面是否已经加载了目标类,如果没有找到就会让父类Classloader尝试加载,如果父类classloader都加载不了,就会调用findClass()让自定义加载器自己来加载目标类。这实际上就是双亲委派机制的原理。
看一下loadClass()的源码:
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
//查看类是否已被加载,findLoadedClass最后调用native方法findLoadedClass0
Class<?> c = findLoadedClass(name);
//还未加载
if (c == null) {
long t0 = System.nanoTime();
try {
//若有父类加载器,则调用其loadClass(),请求父类进行加载
if (parent != null) {
c = parent.loadClass(name, false);
} else {
//若父类加载器为null,说明父类为BootstrapClassLoader(该类加载器无法被Java程序直接使用,用null代替即可),请求它来加载
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
//加载失败,抛出ClassNotFoundException异常
}
//父类无法加载,调用findClass方法,尝试自己加载这个类
//注意:在这个findClass方法中,目前只是抛出一个异常,没有任何进行类加载的动作
//因此,想要自己进行类加载,就要重写findClass()方法。
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
ClassLoader 的 findClass() 方法是需要子类来覆盖重写的,不同的加载器将使用不同的逻辑来获取目标类的字节码。得到字节码之后会调用 defineClass() 方法将字节码转换成 Class 对象。
findClass()的源码如下:
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name); //仅抛出异常
}
可见,ClassLoader的findClass()方法是没有具体实现的,如果要自定义类加载器,就需要重写findClass()方法,并且配合defineClass() 方法一起使用,defineClass()方法是用来将byte字节流解析成JVM能够识别的Class对象。
以上就是CLassLoader进行类加载的简单流程。
虽然Class.forName()方法本质上还是使用Classloader来进行类的加载的,但它和使用Classloader来进行类加载依然有着区别:
①Class.forName()方法除了将类的字节码加载到jvm中之外,还会执行类中的static块,即会导致类的初始化。Class.forName(name, initialize, loader)带参方法也可以指定是否进行初始化,执行静态块。
②ClassLoader只是将类的字节码加载到jvm中,不会执行static中的内容,即不会进行类加载,只有在newInstance才会去执行static块。
5、自定义类加载器
我们知道,除了BootstrapClassLoader是由C/C++实现的,其他的类加载器都是ClassLoader的子类。所以如果我们想实现自定义的类加载器,首先要继承ClassLoader。
根据我们前面的分析,ClassLoader进行类加载的核心实现就在loadClass()方法中。再根据loadClass()方法的源码,我们可以知道有两种方式来实现自定义的类加载,分别如下:
①如果不想打破双亲委派机制,那么只需要重写findClass方法。
②如果想要打破双亲委派机制,那么就需要重写整个loadClass方法。
如果没有特殊要求,Java官方推荐重写findClass方法,而不是重写整个loadClass方法。这样既让我们能够按照自己的意愿加载类,也能保证自定义的类加载器符合双亲委派机制。
明确了如何实现,我们只需要两步就可以实现自定义的类加载器:第一步是继承classloader,第二步是重写findClass方法。
不过由于在findClass()内需要调用defineClass()方法将字节数组转换成Class类对象,因此要先对输入的class文件做一些处理,使其变为字节数组。
实现自定义的类加载器:
public class MyClassLoader extends ClassLoader{
//默认ApplicationClassLoader为父类加载器
public MyClassLoader(){
super();
}
//加载类的路径
private String path = "";
//重写findClass,调用defineClass,将代表类的字节码数组转换为Class对象
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] dataByte = new byte[0];
try {
dataByte = ClassDataByByte(name);
} catch (IOException e) {
e.printStackTrace();
}
return this.defineClass(name, dataByte, 0, dataByte.length);
}
//读取Class文件作为二进制流放入byte数组, findClass内部需要加载字节码文件的byte数组
private byte[] ClassDataByByte(String name) throws IOException {
InputStream is = null;
byte[] data = null;
ByteArrayOutputStream arrayOutputStream = new ByteArrayOutputStream();
name = name.replace(".", "/"); // 为了定位class文件的位置,将包名的.替换为/
is = new FileInputStream(new File(path + name + ".class"));
int c = 0;
while (-1 != (c = is.read())) { //读取class文件,并写入byte数组输出流
arrayOutputStream.write(c);
}
data = arrayOutputStream.toByteArray(); //将输出流中的字节码转换为byte数组
is.close();
arrayOutputStream.close();
return data;
}
}
使用自定义的类加载器:
public static void main(String[] args) {
MyClassLoader myClassLoader = new MyClassLoader();
Class<?> clazz = myClassLoader.loadClass("com.fengjian.www.MyClassLoader");
clazz.newInstance();
}
出 处:https://www.cnblogs.com/jiuxing/p/14585580.html