2021年3月28日星期日

【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();}

由于能力有限,可能存在错误,感谢指出。以上内容为本人在学习过程中所做的笔记。参考的书籍、文章或博客如下:
[1]Mr羽墨青衫.深入分析Java ClassLoader原理.知乎.https://zhuanlan.zhihu.com/p/81759029
[2]愚公要移山.学了这么久的java反射机制,你知道class.forName和classloader的区别吗?.知乎.https://zhuanlan.zhihu.com/p/101114197









原文转载:http://www.shaoqun.com/a/647726.html

跨境电商:https://www.ikjzd.com/

logo免费制作:https://www.ikjzd.com/w/1998

zappos.com:https://www.ikjzd.com/w/330


要自定义自己的类加载器来加载类,需要先对类加载器和类加载机制有一些基本的了解。1、类加载器类加载器ClassLoader的作用有两个:①是用于将class文件加载到JVM。②是用于判断JVM运行时两个类是否相等。2、类加载的时机类的加载可分为隐式加载和显示加载。隐式加载隐式加载包括以下几种情况:遇到new(new一个实例对象的时候)、getstatic(获取一个类的静态字段的时候)、putstat
usps国际快递查询:https://www.ikjzd.com/w/513
卖家网:https://www.ikjzd.com/w/1569
易速:https://www.ikjzd.com/w/2389
Lazada增值税怎么算?Lazada GST增值税收取详情:https://www.ikjzd.com/home/114798
干货:Listing被亚马逊限制评论怎么办?:https://www.ikjzd.com/home/112218
亚马逊店铺应该怎样活跃?你知道多少?:https://www.ikjzd.com/home/111682