类加载器及双亲委派原则验证

6 篇文章 0 订阅
2 篇文章 0 订阅

一、代码准备

自定义类加载器,重写findClass方法:

package com.test.source.jvm.classloader;

import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;

public class MyClassLoader extends ClassLoader {
    private String byteCodePath;

    public MyClassLoader(String byteCodePath) {
        this.byteCodePath = byteCodePath;
    }

    public MyClassLoader(ClassLoader parent, String byteCodePath) {
        super(parent);
        this.byteCodePath = byteCodePath;
    }

    @Override
    protected Class<?> findClass(String className) throws ClassNotFoundException {
        BufferedInputStream bis = null;
        ByteArrayOutputStream bos = null;

        try {
            String fileName = byteCodePath + className.replace(".","/") + ".class";
            bis = new BufferedInputStream(new FileInputStream(fileName));
            bos = new ByteArrayOutputStream();

            int len;
            byte[] data = new byte[1024];
            while ((len = bis.read(data)) != -1) {
                bos.write(data, 0, len);
            }

            byte[] byteCodes = bos.toByteArray();
            Class clazz = defineClass(null, byteCodes, 0, byteCodes.length);
            return clazz;
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (bis != null)
                    bis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if (bos != null)
                    bos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
        return null;
    }
}

准备测试类:

package com.test.source.jvm.classloader;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class ClassLoadeTest {
    public static void main(String[] args) {
        MyClassLoader loader = new MyClassLoader(ClassLoader.getSystemClassLoader(), "D:\\test\\MySpring\\src\\main\\java\\");

        try {
            Class<?> TestClazz = loader.findClass("com.test.source.jvm.classloader.Test");
            System.out.println("loader的父类加载器" + loader.getParent());
            System.out.println("当前线程上下文类加载器:" + Thread.currentThread().getContextClassLoader());
            Thread.currentThread().setContextClassLoader(loader);
            System.out.println("修改后线程上下文类加载器:" + Thread.currentThread().getContextClassLoader());

            System.out.println("Test3的类加载器:" + Test3.class.getClassLoader());

            Object object = TestClazz.newInstance();
            Method method = TestClazz.getMethod("say");
            method.invoke(object);

            System.out.println("主方法中Test的类加载器:" + Test.class.getClassLoader());

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }
}

待测试的对象:

package com.test.source.jvm.classloader;

public class Test{
    public void say(){
        System.out.println("Test ContextClassLoader :"+Thread.currentThread().getContextClassLoader());
        System.out.println("Test:"+Test.class.getClassLoader());
        System.out.println("Test2:"+Test2.class.getClassLoader());
    }
}

package com.test.source.jvm.classloader;

public class Test2{
    public Test2() {
        System.out.println("test2!!!!!");
    }

    public void say(){
        System.out.println("Hello Test !!");
    }
}

package com.test.source.jvm.classloader;

public class Test3 {
    public Test3() {
        System.out.println("test3!!!!!");
    }

    public void say(){
        System.out.println("Hello Test3 !!");
    }
}

待测试对象之间的关系:Test 类中使用到了Test2,Test3类是独立的类,没有引用其他类。

二、测试验证

1、要设置自定义加载器的父类加载器为应用类加载器 AppClassLoader

2、类加载器的调用方法使用 loadClass(我们自定义了findClass,但是如果要保证双亲委派原则,就使用loadClass方法)
在这里插入图片描述
3、编译三个待测试对象
在这里插入图片描述
在这里插入图片描述
4、对整个项目进行编译
在这里插入图片描述
此时三个测试类Test.java、Test2.java、Test3.java就各有两个class文件,一处与他们的java文件放在一起,一处放在classpath路径下。

5、运行 ClassLoadeTest 主方法,注意打印出来的日志
在这里插入图片描述
由于自定义的类加载器MyClassLoader的父类加载器是系统类加载器AppClassLoader,系统类加载器加载的是ClassPath路径下的类文件。
自定义类加载器加载Test时会向上委托给父类加载器(系统类加载器),系统类加载器会继续向上委托,但是引导类加载器和扩展类加载器肯定无法加载到这个类,所以最后系统类加载器自己去classpath中尝试加载,结果真的找到了这三个class,于是系统类加载器进行加载,可以看到Test和Test2的加载器为系统类加载器。
在这里插入图片描述
6、删掉classpath路径下的Test.class、Test2.class
在这里插入图片描述
在这里插入图片描述
执行main方法后报错,报错原因是系统类加载器找不到Test.class。
根据自定义类加载器加载的结果可以看出,父类加载器(系统类加载器)无法加载时,自定义加载器将会自己加载。

7、重新编译项目,在classpath下生成新的class文件,修改自定义类的父类加载器为扩展类加载器
在这里插入图片描述
在这里插入图片描述
执行main方法之后,查看运行结果。
更改自定义类加载器的父类加载器为扩展类加载器,自定义类加载器加载Test时会向上委托,但是扩展类加载器和引导类加载器均无法加载,所以最终自定义类加载器自己加载。通过日志可以看到系统类加载器 AppClassLoader 也加载了Test.class,由此也验证了不同类加载器可以加载同一个class,两个类互相隔离。

8、根据以上的测试结果可以看到,Test和Test2的类加载器始终一致,这是因为一个类在引用另一个类时,如果这个类没有加载,他会使用加载自己的那个类加载器去加载依赖的类。这一点就引出了SPI和线程上下文类加载器。

9、针对上一条测试的再测试
修改自定义类加载器的父类加载器为系统类加载器,修改加载器的加载方式为 findClass
在这里插入图片描述
查看执行结果:
在这里插入图片描述
可以看到Test的类加载器是自定义类加载器,而Test2的类加载器是系统类加载器,为什么呢?
因为直接调用了findClass,没有遵循双亲委派,Test被自定义加载器直接加载了。但是Test依赖了Test2,自定义加载器开始尝试加载Test2,这时候系统调用的肯定是loadClass方法了,说明系统在这种情况下默认遵循了双亲委派,所以向上委托给了 AppClassLoader,AppClassLoader在classpath下找到了Test2.class,所以就加载了;如果把类路径下的Test2.class删掉,那自然是自定义类加载器自己加载了。

10、将上面的代码进行改动,将父类加载器改为扩展类加载器

系统在加载Test2.class是向上委托,但是父类加载器加载不了,自然是自定义加载器自己加载了。
在这里插入图片描述
11、线程上下文类加载器

上面提到类A依赖类B时,会使用加载类A 的加载器去加载类B,可以通过线程上下文类加载器进行改变吗?
加上两处输出代码:
在这里插入图片描述
根据结果可以看到,修改线程上下文类加载器无法改变当前类的类加载(当然无法改变已经加载的类的类加载器),也无法影响之后加载类的类加载器(Test3和主方法中的Test2的加载器依然是系统类加载器,并没有因为修改上下文类加载而改变)

12、线程上下文类加载器的使用

在Test3中加入静态代码块:
在这里插入图片描述
手动编译:
在这里插入图片描述
删除类路径下的Test3.class
在这里插入图片描述
没有设置线程上下文类加载器时,该加载器默认为系统类加载器,由于classpath路径下找不到Test3.class,所以会加载失败:
在这里插入图片描述
设置为自定义类加载器后,就能正常加载:
在这里插入图片描述
SPI中,父加载器无法加载类,而使用线程上线文类加载器进行加载,应该也是这种套路吧。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值