美文网首页Java学习笔记我爱编程
Java的类加载器ClassLoader

Java的类加载器ClassLoader

作者: 纳米君 | 来源:发表于2018-05-16 23:44 被阅读56次

    Java类加载方式采取树形结构的双亲委托机制。如下图:

    树形结构.png

    Bootstrap:加载rt.jar中所有的类,C/C++编写,Java中不存在该类
    ExtClassLoader:加载ext目录下所有的扩展类
    AppClassLoader:加载应用类,一般为我们编写的字节码

    类加载器的规则:


    image.png

    如上图,加载类的时候,先看自身的ClassLoader是否已经加载过该类,加载过就直接获取;如果未加载过,则看父加载器是否已经加载过该类......以此类推。
    另外需要注意,被当前类引用的类的加载应该由加载当前类的ClassLoader或者父加载器加载,否则会抛异常。

    比如:如果一个类是用AppClassLoader加载的,我们把该类转移到ext目录下,该类继承的类本来也是AppClassLoader加载,由于我们把该类交由给ExtClassLoader加载了,按照加载规则,ExtClassLoader未加载到继承的类,抛异常ClassNotFoundException。

    看代码示例:

    public class ClassLoaderTest {
    
        public static void main(String[] args) {
            ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
            System.out.println(classLoader.getClass().getName());
            System.out.println(int.class.getClassLoader());
            System.out.println(String.class.getClassLoader());
            System.out.println(System.class.getClassLoader());
    
            while (classLoader != null){
                System.out.println(classLoader.getClass().getName());
                classLoader = classLoader.getParent();
            }
        }
    }
    

    打印结果:

    sun.misc.Launcher$AppClassLoader
    null
    null
    null
    sun.misc.Launcher$AppClassLoader
    sun.misc.Launcher$ExtClassLoader
    

    需要注意的是,类的加载器的父亲并不是其真正的父类,看JDK源码得知,AppClassLoader和ExtClassLoader都属于Launcher类的静态内部类,都继承于URLClassLoader,继承关系如下图:


    image.png image.png

    源代码简略如下:

    public class Launcher {
    
        static class AppClassLoader extends URLClassLoader {
            ...
        }
    
        static class ExtClassLoader extends URLClassLoader {
            ...
        }
    
    }
    

    那么如何自定义一个类加载器呢?
    很简单,只需要继承ClassLoader类,并复写findClass()方法即可。
    注意:自定义类加载器的默认父加载器是AppClassLoader。
    代码如下:

    public class MyClassLoader extends ClassLoader {
    
        private String path;
    
        public MyClassLoader(String path) {
            this.path = path;
        }
    
        /**
         * @param name 类的全路径(包名+类名),比如cn.tl.domain.Employee
         * @return
         * @throws ClassNotFoundException
         */
        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            System.out.println("MyClassLoader findClass execute...");
    
            String filePath = path + name.replace('.', File.separatorChar) + ".class";
            try (
                    FileInputStream fis = new FileInputStream(filePath);
                    ByteArrayOutputStream bos = new ByteArrayOutputStream()
            ) {
                int len;
                while ((len = fis.read()) != -1) {
                    bos.write(len);
                }
                byte[] bytes = bos.toByteArray();
                return defineClass(name, bytes, 0, bytes.length);
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
    
            return super.findClass(name);
        }
    }
    

    测试类:

    public class MyClassLoaderTest {
    
        public static void main(String[] args) throws Exception{
            Class<?> aClass = new MyClassLoader("F:\\").loadClass("cn.tl.domain.Employee");
            System.out.println(aClass.getClassLoader().getClass().getName());
            Object o = aClass.newInstance();
            System.out.println(o);
        }
    }
    

    打印结果:

    sun.misc.Launcher$AppClassLoader
    Employee [name=null, age=0]
    

    有人可能会发现并没有打印MyClassLoader findClass execute...,类加载器也是AppClassLoader,但是类加载到了,我们自定义的加载器根本没有起作用。请删除工作空间编译后的class文件,否则AppClassLoader加载到该类,就终止加载流程了,根本就没MyClassLoader的事了。
    删除工作空间的class文件后的打印结果:

    MyClassLoader findClass execute...
    cn.tl.classloader.MyClassLoader
    Employee [name=null, age=0]
    

    结果完美。

    以上。

    相关文章

      网友评论

      本文标题:Java的类加载器ClassLoader

      本文链接:https://www.haomeiwen.com/subject/vduddftx.html