自定义 ClassLoader 绕过双亲委派的实验

 1.对于任意一个类,由加载它的ClassLoader和它本身决定了在java虚拟机中的唯一性。
也就是说比较2个类,只有它们都是由同一个ClassLoader加载,那么比较才有意义。否则,即使是同一个类文件,如果加载它们的ClassLoader不同,那么这2个类必定不相等。

2.类加载的双亲委派机制是可以破坏的,通过改变CallClassLoader和ContextClassLoader。
被当前类引用的类的加载也是由加载当前类的ClassLoader加载,子线程的ContextClassLoader是由父线程的ContextClassLoader派生出来。

3.扩展ClassLoader一般应该建议重写findClass(String)方法。
原因是自定义的ClassLoader只专注于加载自己的Class,在加载自己的Class的过程中,又会先加载父类Object,Object类是由启动类加载器加载的,默认可以直接由loadClass(String)执行,这么做可以用ClassLoader来选择性加载某一些类做隔离。

package com.mashibing.jvm.c2_classloader;

import java.io.InputStream;
import java.lang.reflect.Method;

public class MySelfClassLoader extends ClassLoader {
    public void say() {
        System.out.println("hello world");
    }

    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        try {
            String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
            InputStream is = getClass().getResourceAsStream(fileName);
            if (is == null) {
                return super.loadClass(name);
            }
            byte[] b = new byte[is.available()];
            is.read(b);
            return defineClass(name, b, 0, b.length);
        }
        catch (Exception e) {
            throw new ClassNotFoundException(name);
        }
    }
    public static void main(String[] args) throws Exception {
        // 打印java虚拟机的ClassLoader
        System.out.println(Thread.currentThread().getContextClassLoader());
        System.out.println(Thread.currentThread().getContextClassLoader().getParent());
        System.out.println(Thread.currentThread().getContextClassLoader().getParent().getParent());
        // 自定义ClassLoader
        ClassLoader myLoader = new MySelfClassLoader();
        Class<?> clazz = myLoader.loadClass("com.mashibing.jvm.c2_classloader.MySelfClassLoader");
       // Class<?> clazz = myLoader.loadClass("com.mashibing.jvm.Hello");
//加载别的类显示的 ClassLoader就不是MySelfClassLoader,是我电脑环境的问题,公司电脑就正常,真tm绝了,
//都是jdk1.8,win10,ide2018 ,就除了是电脑牌子不一样,别的不知道是啥了,有小伙伴遇到过这样的问题吗

        Object obj = clazz.newInstance();
        // 打印自定义ClassLoader加载的Class对象
        System.out.println(obj.getClass());
        // 打印被加载的Class对象是由哪个ClassLoader加载的
        System.out.println(obj.getClass().getClassLoader());
        /*
         * 对于任意一个类,由加载它的ClassLoader和它本身决定了在jvm虚拟机中的唯一性。
         * 也就是说比较2个类,只有它们都是由同一个ClassLoader加载,那么比较才有意义。
         * 否则,即使是同一个类文件,只要加载它们的ClassLoader不同,那么这2个类必定不相等。
         */
        System.out.println(obj instanceof com.mashibing.jvm.c2_classloader.MySelfClassLoader);
        //System.out.println(obj instanceof com.mashibing.jvm.Hello);

        // 由自定义ClassLoader加载后,在程序里运行。
        Method method = clazz.getDeclaredMethod("say", new Class<?>[] {});
        method.invoke(obj, new Object[] {});
        // 获取当前上下文的ClassLoader
        System.out.println(Thread.currentThread().getContextClassLoader());
        // 改变上下文的ClassLoader
        Thread.currentThread().setContextClassLoader(myLoader);
        // 获取当前上下文的ClassLoader
        System.out.println(Thread.currentThread().getContextClassLoader());
        // 改变当前上下文的ClassLoader可以改变在当前线程派生出的子线程的上下文ClassLoader
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                Thread t2 = Thread.currentThread();
                System.out.println(t2.getName() + ":" + t2.getContextClassLoader());
            }
        });
        t.start();
        Thread.sleep(3000);
        // 获取CallClassLoader
        Class<?> clz = Class.forName("com.mashibing.jvm.c2_classloader.MySelfClassLoader");
        System.out.println(clz);
        System.out.println(clz.getClassLoader());
        System.out.println(clz.getClass());
        System.out.println(clz.getClass().getClassLoader());
    }
}

执行结果:
sun.misc.Launcher$AppClassLoader@addbf1 ==> 当前上下文类加载器是应用类加载器
sun.misc.Launcher$ExtClassLoader@42e816 ==> 应用类加载器的父类加载器是扩展类加载器
null ==> 扩展类加载器的父类加载器是启动类加载器,因为启动类加载器是C++编写,java里获取不到
class com.zoo.classloader.ClassLoaderTest ==> 自定义类加载器加载进行加载
com.zoo.classloader.MyClassLoader@1fb8ee3 ==> 通过Class对象获取它的类加载器
false ==> 同一个Class,不同的类加载器加载,那么Class不相等
hello world ==> 被自定义类加载器加载的Class在程序中执行
sun.misc.Launcher$AppClassLoader@addbf1 ==> 获取当前上下文类加载器
com.zoo.classloader.MyClassLoader@1fb8ee3 ==> 设置上下文类加载器
Thread-0:com.zoo.classloader.MyClassLoader@1fb8ee3 ==> 派生出来的子线程的类加载器
class com.zoo.classloader.ClassLoaderTest ==> 默认加载类
sun.misc.Launcher$AppClassLoader@addbf1 ==> 默认加载用的是应用类加载器
class java.lang.Class ==> 获取Class
null ==> 获取类加载器,由于是启动类加载器,所以为null,java基础类是由启动类加载器加载。

为啥要打破双亲委派?

打破双亲委派,重新ClassLoader。
1、部署在同一个服务器上的两个Web应用程序所使用的Java类库可以实现相互隔离。这是最基本的要求,两个不同的应用程序可能会依赖同一个第三方类库的不同版本,不能要求一个类库在一个服务器中只有一份,服务器应当保证两个应用程序的类库可以互相使用

2、部署在同一个服务器上的两个Web应用程序所使用的Java类库可以相互共享。这个需求也很常见,比如相同的Spring类库10个应用程序在用不可能分别存放在各个应用程序的隔离目录中

3、支持热替换,我们知道JSP文件最终要编译成.class文件才能由虚拟机执行,但JSP文件由于其纯文本存储特性,运行时修改的概率远远大于第三方类库或自身.class文件,而且JSP这种网页应用也把修改后无须重启作为一个很大的优势看待

由于存在上述问题,因此Java提供给用户使用的ClassLoader就无法满足需求了。Tomcat服务器就有自己的ClassLoader架构,当然,还是以双亲委派模型为基础的

转自:http://lifestack.cn/archives/256.html

猜你喜欢

转载自blog.csdn.net/zhaofuqiangmycomm/article/details/113827335