当前位置: 首页 > 编程笔记 >

浅谈Java自定义类加载器及JVM自带的类加载器之间的交互关系

张英范
2023-03-14
本文向大家介绍浅谈Java自定义类加载器及JVM自带的类加载器之间的交互关系,包括了浅谈Java自定义类加载器及JVM自带的类加载器之间的交互关系的使用技巧和注意事项,需要的朋友参考一下

JVM自带的类加载器:

其关系如下:

其中,类加载器在加载类的时候是使用了所谓的“父委托”机制。其中,除了根类加载器以外,其他的类加载器都有且只有一个父类加载器。

关于父委托机制的说明:

当生成 一个自定义的类加载器实例时,如果没有指定它的父加载器,那么系统类加载器将成为该类加载器的父类加载器

下面,自定义类加载器。自定义的类加载器必须继承java.lang.ClassLoader类

import java.io.*;
public class MyClassLoader extends ClassLoader {
  private String name;  //类加载器的名字
  private String path;  //加载类的路径
  private final String fileType = ".class"; //class文件的扩展名
  public MyClassLoader(String name){
    super(); //让系统类加载器成为该类加载器的父 类加载器,该句可省略不写
    this.name = name;
  }
  public MyClassLoader(ClassLoader parent,String name){
    super(parent); //显示指定该类加载器的父 类加载器
    this.name = name;
  }
  @Override
  public String toString() {
    return this.name;
  }
  public String getPath() {
    return path;
  }
  public void setPath(String path) {
    this.path = path;
  }
  //实现自定义的类加载器必须重写findClass方法,否则ClassLoader类中的findClass()方法是抛出了异常
  @Override
  public Class findClass(String name)throws ClassNotFoundException{
    byte[] data = this.loadClassData(name);
    return this.defineClass(name,data,0,data.length);
  }
  private byte[] loadClassData(String name){
    InputStream is = null;
    byte[] data = null;
    ByteArrayOutputStream baos = null;
    try {
      this.name = this.name.replace(".","\\"); //com.dream.it---->com\dream\it
      is = new FileInputStream(new File(path + name + fileType));
      int ch;
      while(-1 != (ch = is.read())){
        baos.write(ch);  //将数据写入到字节数组输出流对象中去
      }
      data = baos.toByteArray();
    } catch (Exception e) {
      e.printStackTrace();
    }finally {
      try {
        is.close();
        baos.close();
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
    return data;
  }
  public static void main(String[] args) throws Exception {
    MyClassLoader loader1 = new MyClassLoader("loader1");
    loader1.setPath("d:/myapp/serverlib/");
    MyClassLoader loader2 = new MyClassLoader(loader1,"loader2"); //loader1作为loader2的父 类加载器
    loader2.setPath("d:/myapp/clientlib");
    MyClassLoader loader3 = new MyClassLoader(null,"loader3");//父类加载器为null,表明其父类加载器为根类加载器
    loader3.setPath("d:/myapp/otherlib");
    test(loader2);
    test(loader3);
  }
  public static void test(ClassLoader cl) throws Exception {
    Class clazz = cl.loadClass("Sample");
    Object object = clazz.newInstance();
  }
}

附上findClass()方法的JDK说明

protected Class<?> findClass(String name) throws ClassNotFoundException
Finds the class with the specified binary name. 
This method should be overridden by class loader
implementations that follow the delegation model 
for loading classes, and will be invoked by the 
loadClass method after checking the parent class
loader for the requested class. The default 
implementation throws a ClassNotFoundException. 

大致说明一下意思:通过指定的name来查找类。该方法应该被类加载器的实现类重写,从而能够保证在加载类的时候可以遵循委托机制模型。在loadClass()方法(该方法是由JVM调用的)中,检查其父类加载器之后,该方法再被调用去加载请求的类。默认该方法的实现是抛出了一个ClassNotFoundException异常。

其实,所谓的加载类,无非就是读取.class文件到内存中,所以在findClass()方法中,loadClassData()方法用于读取.class文件的数据,并返回一个字节数组。然后利用ClassLoader类的defineClass()方法将字节数组转换为Class对象。

上述自定义的类加载器loader1,loader2,loader3及JVM自带的类加载器之间的关系如下:

对于各个类加载器,系统的类加载器是从环境变量classpath中读取.class文件实现类的加载;loader1是从目录d:/myapp/serverlib/下读取.class文件;loader2是从目录d:/myapp/clientlib/下读取.class文件,loader3是从目录d:/myapp/otherlib/下读取.class文件

执行结果:

此处我们分析一下出现这种执行结果的原因:

当执行loader2.loadClass(“Sample”)时先由它上层的所有父类加载器尝试加载Sample类。

loader1从D:\myapp\serverliv目录下成功加载了Sample类,所以loader1是Sample类的定义类加载器,loader1和loader2是Sample类的初始类加载器。

当执行loader3.loadClass(“Sample”)时,先由它上层的所有父类加载器尝试加载Sample类。

loader3的父加载器为根类加载器,它无法加载Sample类,接着loader3从D:\myapp\otherlib目录下成功加载Sample类,所以loader3是Sample类的定义类加载器及初始类加载器。

在Sample类中主动使用了Dog类(new Dog()),当执行Sample类的构造方法中的new Dog()语句时,JVM需要先加载Dog类,到底用哪个类加载器家在呢?

从上述的打印结果中可以看出,加载Sample类的loader1还加载了Dog类,JVM会用Sample类的定义类加载器去加载Dog类,加载过程中也同样采用了父亲委托机制。

为了验证这一点,可以吧D:\myapp\serverlib目录下Dog.class文件删除,然后在D:\myapp\syslib目录下存放一个Dog.class文件,此时打印结果如下:

Sample:loader1
Dog:sun.misc.Launcher$AppClassLoader@1b84c92
Sample:loader3
Dog:loader3

由此可见,当由loader1加载的Sample类首次主动使用Dog类时,Dog类由系统类加载器加载,如果把D:\myapp\serverlib和D:\myapp\syslib目录下的Dog.class文件都删除,然后在D:\myapp\client目录下存放一个Dog.class文件。

此时文件结构如下图所示:

当Loader1加载Sample类首次主动使用Dog类时,由于loader1及其父类加载器都无法加载Dog类,因此test(loader2)会抛出ClassNotFoundExcption.

这又是因为什么原因呢?

这又牵扯到命名空间的问题。

同一个命名空间内的类时相互可见的。

子加载器的命名空间包含所有父类加载器的命名空间,因此由子加载器加载的类能看见父类加载器加载的类。例如系统类加载器加载的类能看见根类加载器加载的类。由父加载器加载的类不能看见子加载器加载的类。

如果两个加载器之间没有直接或间接的父子关系,那么它们各自加载的类相互不可见。

对于上述问题,loader1可以加载Sample类,而Dog类只能由loader2加载Dog类,loader1是Loader2的父类加载器,父加载器loader1加载的类Sample不能看见子加载器loader2加载的类Dog,所以会抛出异常。

对于上述实例中的main方法,我们不调用test方法,换成如下代码

Class clazz = loader1.loadClass("Sample");
Object obj = clazz.newInstance();
Sample sample = (Sample)obj;
System.out.println(sample.v1);

MyClassLoader类由系统类加载器加载,而Sample类由loader1类加载器加载,所以MyClassLoader类看不见Sample类。在MyClassLoader类的main方法中使用Sample类,会导致NoClassFoundError错误。

当两个不同命名空间内的类相互不可见时,可采用Java反射机制来访问对象实例的属性和方法。

将上述代码修改

Class clazz = loader1.loadClass("Sample");
Object obj = clazz.newInstance();
Field field = clazz.getField("v1");
int v1 = field.getInt(obj);
System.out.println(v1);

此时,可以获取到对象中的v1属性值。利用反射机制,我们可以跨越这种命名空间的限制。

补充:

命名空间:

运行时包:

以上为个人经验,希望能给大家一个参考,也希望大家多多支持小牛知识库。如有错误或未考虑完全的地方,望不吝赐教。

 类似资料:
  • 本文向大家介绍java 类加载与自定义类加载器详解,包括了java 类加载与自定义类加载器详解的使用技巧和注意事项,需要的朋友参考一下 类加载 所有类加载器,都是ClassLoader的子类。 类加载器永远以.class运行的目录为准。 读取classpath根目录下的文件有以下几种方式: 1 在Java项目中可以通过以下方式获取classspath下的文件: 在Tomcat中tomcat又声明了

  • 我试图修改几个类的字节代码,这些类的打包jar文件不在类路径中-它们是在给定URL的运行时由自定义的加载的。我尝试使用和来拦截那些类,但失败了。类加载器是遗留项目的一部分,因此我无法直接对其进行更改。 代理可以很好地处理AppClassLoader“本地”加载的类,但只会忽略那些由自定义类加载器加载的类。 CustomClassLoader: 我的代理中使用的ClassFileTransforme

  • 问题内容: 我一直在尝试建立一个自定义的类加载器,该类加载器将拦截类以打印出哪些类正在加载到应用程序中。类加载器看起来像这样 它只是吐出它加载的所有类的名称。但是,当我尝试运行一些代码时, 通过 它打印出来 这似乎很奇怪。这不是一个简单的类,它取决于程序包中的许多其他类。这些类显然正在加载中,因为d python代码能够执行操作并读取我的文件。但是,由于某些原因,那些类并未由load的类加载器加载

  • 我一直在Java玩简单的自定义类加载器,到目前为止,对于非模块相关的类来说,一切都按照预期工作。但是,我似乎找不到任何方法来使用我的类加载器从模块加载类,即使与模块相关的方法已经被重载。我尝试做的是从模块“HelloModularWorld”加载一个类,并运行它的main。然而,当我指定包所在的目录时,它将“正常”加载,并报告为在未命名模块中。我错过了什么? 类加载器只是从文件系统的其他地方加载类

  • 主要内容:1.类加载过程,2.类加载时机,3.类加载器,4.类加载机制:当程序主动使用某个类时,如果该类还未被加载到内存中,则JVM会通过加载、连接、初始化3个步骤来对该类进行初始化。如果没有意外,JVM将会连续完成3个步骤,所以有时也把这个3个步骤统称为类加载或类初始化。 1.类加载过程 1.1加载 加载指的是将类的class文件读入到内存,并为之创建一个java.lang.Class对象,也就是说,当程序中使用任何类时,系统都会为之建立一个java.lang.Cl

  • 问题内容: 我有一个Java类,它在启动时基于javassist类加载器创建自定义类加载器,然后运行实际的程序类。我收到以下错误: 该问题与以下事实有关:一个对象是由原始类加载器创建的,而另一个是由自定义类创建的。 有没有办法解决此错误? 在此先感谢 Avner 问题答案: 请尝试将-Dlog4j.ignoreTCL =true设置为希望有帮助。关于log4j的类似问题