ClassLoader学习之 自定义classLoader和双亲委派原理

ClassLoader学习之  自定义classLoader和双亲委派原理

1、ClassLoader原理介绍

​ ClassLoader使用的是双亲委托模型来搜索类的,每个ClassLoader实例都有一个父类加载器的引用(不是继承的关系,是一个包含的关系),虚拟机内置的类加载器(Bootstrap ClassLoader)本身没有父类加载器,但可以用作其它ClassLoader实例的的父类加载器。当一个ClassLoader实例需要加载某个类时,它会试图亲自搜索某个类之前,先把这个任务委托给它的父类加载器,这个过程是由上至下依次检查的,

2、为什么要使用双亲委托这种模型呢?

​ 因为这样可以避免重复加载,当父亲已经加载了该类的时候,就没有必要子ClassLoader再加载一次。考虑到安全因素,我们试想一下,如果不使用这种委托模式,那我们就可以随时使用自定义的String来动态替代java核心api中定义的类型,这样会存在非常大的安全隐患,而双亲委托的方式,就可以避免这种情况,因为String已经在启动时就被引导类加载器(Bootstrcp ClassLoader)加载,所以用户自定义的ClassLoader永远也无法加载一个自己写的String,除非你改变JDK中ClassLoader搜索类的默认算法。

3、ClassLoader的体系架构:

类加载器间的关系

我们进一步了解类加载器间的关系(并非指继承关系),主要可以分为以下4点

  • 启动类加载器,由C++实现,没有父类。

  • 拓展类加载器(ExtClassLoader),由Java语言实现,父类加载器为null

  • 系统类加载器(AppClassLoader),由Java语言实现,父类加载器为ExtClassLoader

  • ​自定义类加载器,父类加载器肯定为AppClassLoader。

顶层的类加载器是ClassLoader类,它是一个抽象类,其后所有的类加载器都继承自ClassLoader(不包括启动类加载器),这里我们主要介绍ClassLoader中几个比较重要的方法。

protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
  {
      synchronized (getClassLoadingLock(name)) {
          // 先从缓存查找该class对象,找到就不用重新加载
          Class<?> c = findLoadedClass(name);
          if (c == null) {
              long t0 = System.nanoTime();
              try {
                  if (parent != null) {
                      //如果找不到,则委托给父类加载器去加载
                      c = parent.loadClass(name, false);
                  } else {
                  //如果没有父类,则委托给启动加载器去加载
                      c = findBootstrapClassOrNull(name);
                  }
              } catch (ClassNotFoundException e) {
                  // ClassNotFoundException thrown if class not found
                  // from the non-null parent class loader
              }
​
              if (c == null) {
                  // If still not found, then invoke findClass in order
                  // 如果都没有找到,则通过自定义实现的findClass去查找并加载
                  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;
      }
  }

​ Lancher初始化时首先会创建ExtClassLoader类加载器,然后再创建AppClassLoader并把ExtClassLoader传递给它作为父类加载器,这里还把AppClassLoader默认设置为线程上下文类加载器

public Launcher() {
    // 首先创建拓展类加载器
    ClassLoader extcl;
    try {
        extcl = ExtClassLoader.getExtClassLoader();
    } catch (IOException e) {
        throw new InternalError(
            "Could not create extension class loader");
    }
​
    // Now create the class loader to use to launch the application
    try {
        //再创建AppClassLoader并把extcl作为父加载器传递给AppClassLoader
        loader = AppClassLoader.getAppClassLoader(extcl);
    } catch (IOException e) {
        throw new InternalError(
            "Could not create application class loader");
    }
​
    //设置线程上下文类加载器,稍后分析
    Thread.currentThread().setContextClassLoader(loader);
    //省略其他没必要的代码......
}
}

4、创建自定义类加载器,继承ClassLoader

package com.willow.classloader;
​
import com.sun.xml.internal.messaging.saaj.util.ByteOutputStream;
​
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
​
/**
 * 自定义类加载器
 */
public class FileSystemClassLoader extends ClassLoader {
​
    private String rootDir;  //指定这个类加载器加载的目录  d:/study/
​
    public FileSystemClassLoader(String rootDir) {
        this.rootDir = rootDir;
    }
​
    //编写获取类的字节码并创建class对象的逻辑
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        Class<?> c = findLoadedClass(name);//查找这个类是否加载了
        if (c != null) {
            return c;
        }
        ClassLoader parent = this.getParent(); //获取到父类加载器
        try {
            c = parent.loadClass(name); //委派给父类加载
        }catch (ClassNotFoundException e){
​
        }
​
        if (c != null) {
            return c;
        } else {
            try {
                byte[] classData=getClassData(name);
                if(classData==null){
                    throw new ClassNotFoundException();
                }else{
                    //方法接受一组字节,然后将其具体化为一个Class类型实例,它一般从磁盘上加载一个文件,然后将文件的字节传递给JVM,通过JVM(native 方法)对于Class的定义,将其具体化,实例化为一个Class类型实例。
                    c=defineClass(name,classData,0,classData.length);
                    return c;
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
​
​
        }
​
        return c;
    }
​
    /**
     * 编写读取字节流的方法
     *
     * @param className
     * @return
     * @throws IOException
     */
    private byte[] getClassData(String className) throws IOException {  //com.willow.entity.user   转换为: d:/study/  com/willow/entity/user
        String path = rootDir + "/" + className.replace('.', '/') + ".class";
​
        InputStream inputStream = null;
        ByteOutputStream outputStream = new ByteOutputStream();
        try {
            inputStream = new FileInputStream(path); //读取需要加载的类
            byte[] buffer = new byte[1024];
            int temp = 0;
            while ((temp = inputStream.read(buffer)) != -1) {
                outputStream.write(buffer, 0, temp);
            }
            return outputStream.toByteArray();
​
​
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (inputStream != null) {
                inputStream.close();
            }
            if (outputStream != null) {
                outputStream.close();
            }
        }
​
        return null;
    }
}
​

测试自定义类加载器

public class MyClassLoderTest {
​
    public static void main(String[] args) {
​
        //打印ClassLoader类的层次结构
        ClassLoader classLoader = MyClassLoderTest.class.getClassLoader();    //获得加载ClassLoaderTest.class这个类的类加载器
        while(classLoader != null) {
            System.out.println(classLoader);
            classLoader = classLoader.getParent();    //获得父类加载器的引用
        }
        System.out.println(classLoader);
​
​
        //测试自定义类加载器  ,在d:/study 创建HelloWorld.java ,然后编译为HelloWorld.class 文件
        FileSystemClassLoader loader=new FileSystemClassLoader("d:/study");
        FileSystemClassLoader loader2=new FileSystemClassLoader("d:/study");
        try {
            Class<?> c = loader.loadClass("HelloWorld");
            Class<?> c1 = loader.loadClass("HelloWorld");
            Class<?> c3 = loader2.loadClass("HelloWorld");
​
​
            Class<?> cString = loader2.loadClass("java.lang.String");
            Class<?> cMyClassLoderTest = loader2.loadClass("com.willow.classloader.MyClassLoderTest");
            System.out.println(c.hashCode()+"##:classLoader"+c.getClassLoader());    //自定义加载器加载
            System.out.println(c1.hashCode());
            System.out.println(c3.hashCode());  //同一个类,不同的加载器加载,JVM 认为也是不相同的类
            System.out.println(cString.hashCode()+"##:classLoader"+cString.getClassLoader());  //引导类加载器
            System.out.println(cMyClassLoderTest.hashCode()+"##:classLoader"+cMyClassLoderTest.getClassLoader());  //系统默认加载器
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
​
    }
}

参考文章:https://blog.csdn.net/javazejian/article/details/73413292#

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值