如何写出自己的类加载器

通过源码,我们可以看到类加载器最重要的就两个方法,一个是loadClass,另一个是findClass。loadClass实现了双亲委派机制。findClass实现了查找类,但是应用类加载器和扩展类加载器都没有实现这个功能,而是交给URLClassLoader实现。

在下面的例子里面,我们重写一下findClass的过程

package com.test.test;

import java.io.FileInputStream;

public class MyClassLoader extends ClassLoader{
    //读取文件
    private byte[] loadByte(String name) throws Exception {
        name = name.replaceAll("\\.", "/");
        FileInputStream fis = new FileInputStream( "D:/test/" + name + ".class");
        int len = fis.available();
        byte[] data = new byte[len];
        fis.read(data);
        fis.close();
        return data;
    }

    @Override
    protected Class findClass(String name) throws ClassNotFoundException {
        try {
            //读取字节数组
            byte[] bytes = loadByte(name);
            //定义类
            return defineClass(name,bytes,0,bytes.length);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public static void main(String[] args) throws ClassNotFoundException {
        MyClassLoader myClassLoader = new MyClassLoader();
        Class aClass = myClassLoader.loadClass("com.test.test.User");
        System.out.println(aClass.getClassLoader());

        Class bClass = myClassLoader.loadClass("com.test.test.User1");
        System.out.println(bClass.getClassLoader());
    }
}

这是我的文件结构

image-20210522160222860

然后我在D盘放了User1和User两个类。

执行结果
"C:\Program Files\Java\jdk1.8.0_161\bin\java.exe" "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2020.2.3\lib\idea_rt.jar=9658:C:\Program Files\JetBrains\IntelliJ IDEA 2020.2.3\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_161\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\rt.jar;F:\workspace\test\target\classes" com.test.test.MyClassLoader
sun.misc.Launcher$AppClassLoader@18b4aac2
com.test.test.MyClassLoader@1540e19d
分析结果

User类的类加载器是应用加载器。这是因为采用的是双亲委派机制,执行路线MyClassLoader->AppClassLoader->ExtClassLoader->BootstrapLoader->ExtClassLoader->AppClassLoader。

User1类的类加载器是MyClassLoader。这是因为父加载器都没有,才轮到自己执行。

如何打破双亲委派机制

其实也不复杂,只需要重写一下loadClass里面的代码就可以实现。

如果只是删除parent.loadClass是不够的。这是因为所有的类的父类都是Object。删除掉了parent.loadClass这意味着扩展类加载器和引导类加载器都不会进行加载。但是加载User和User1的前提,必须是先加载Object.class。所以还需要追加代码,完整代码如下:

 @Override
    protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class c = findLoadedClass(name);
            long t1=0;
            if (c == null) {
                long t0 = System.nanoTime();
               //删除双亲委派这一部分代码就可以了
                //追加这部分代码是为了解决java.io.FileNotFoundException: D:\test\java\lang\Object.class (系统找不到指定的路径。)
                //如果不是指定类那么就走传统的双亲委派
                if (!"com.test.test.User".equals(name)){
                    c=super.loadClass(name,resolve);
                }else {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                   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;
        }
    }

错误:

java.io.FileNotFoundException: D:\test\java\lang\Object.class (系统找不到指定的路径。)
    at java.io.FileInputStream.open0(Native Method)
    at java.io.FileInputStream.open(FileInputStream.java:195)
    at java.io.FileInputStream.(FileInputStream.java:138)
    at java.io.FileInputStream.(FileInputStream.java:93)
    at com.test.test.MyClassLoader.loadByte(MyClassLoader.java:9)
    at com.test.test.MyClassLoader.findClass(MyClassLoader.java:21)
    at com.test.test.MyClassLoader.loadClass(MyClassLoader.java:42)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    at java.lang.ClassLoader.defineClass1(Native Method)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:763)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:642)
    at com.test.test.MyClassLoader.findClass(MyClassLoader.java:22)
    at com.test.test.MyClassLoader.loadClass(MyClassLoader.java:42)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    at com.test.test.MyClassLoader.main(MyClassLoader.java:58)
Exception in thread "main" java.lang.NoClassDefFoundError: java/lang/Object
    at java.lang.ClassLoader.defineClass1(Native Method)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:763)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:642)
    at com.test.test.MyClassLoader.findClass(MyClassLoader.java:22)
    at com.test.test.MyClassLoader.loadClass(MyClassLoader.java:42)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    at com.test.test.MyClassLoader.main(MyClassLoader.java:58)

Process finished with exit code 1

你可能感兴趣的:(如何写出自己的类加载器)