乐趣区

关于java:ClassLoader-隔离性的基石是namespace证明给你看

一、背景

敌人:在我常识体系中 ClassLoader 的双亲委派机制是晦涩丝滑的,可是看到 通过委派执行类加载来保障这种分治能力,进而达到了类资源的隔离性 忽然就感觉有点生疏和排挤呢?

我:类的命名空间有理解嘛?

敌人:你是说 package 嘛?

我:我说的是 ClassLoader 中的 nameSpace

敌人:啥玩意儿?

本篇是对 ClassLoader 中的 namespace 做个直观的介绍和验证。这个知识点笔者集体认为很重要,屡次帮忙笔者解决日常工作中遇到的疑难杂症,如果你尚未认真钻研过 ClassLoader,但糊涂的认知让你感觉这个应该很简略,那请看下图,若看不懂则表明你可能并不理解ClassLoader 中得一些要害逻辑;不贩卖焦虑,不感兴趣则疏忽,晓得个大略即可,不会它并不影响你做一个优良的程序员。

二、Classloader 的分治和委派机制

上下文同步,已理解这部分的读者敌人可间接跳过,进过第三大节。

ClassLoader 是个抽象类,其子类 UrlClassLoader 引入 URL 资源概念,以此形式来束缚本人只加载 URL 覆盖范围内的类文件;ExtClassLoaderAppClassLoader 都是 UrlClassLoader 的子类,各自分管的资源门路不同,即给定的 URL 不同则管辖范畴不同,并通过委派执行类加载来保障这种分治能力,进而达到了类资源的隔离性。

上图是规范的委派机制,总结为 2 个方面:

  1. 父加载器能加载 父加载器来加载:

    • 本人在加载资源之前,先让父类加载器去加载。父类再找其父类,直到BootStrapClassLoader(它没有父类加载器)。
    • 保障了等级越高,加载的优先权越高
  2. 父加载器不加载 我就来加载(findClass);我加载不了的子加载器来加载:

    • 若父类加载器没有加载胜利,才逐级下放这个加载权。
    • 子类加载器不能加载父类加载器能加载的类,如 java.lang.String,即便用户本人假造一份这个类型,启动类加载器优先将 java.lang.String 加载胜利后,利用类加载器就不会再加载用户本人假造的。

下图形容了 SkyWalking Agent 通过自定义的类加载器AgentClassLoader 加载插件中的类,不会对宿主利用中的类产生净化。

三、namespace是什么?

每个类加载器都对应一个namespace,汉化叫命名空间(我集体其实更喜爱汉化为名字空间),命名空间由该加载器及所有父类加载器所加载的类组成。这样的介绍很形象,网络材料中也多是这么几句话,我会从更多的细粒度视角带您进一步理解它。

3.1 同一个命名空间中,类只加载一份

比方 AppClassLoader 加载程序中编写的类。无论加载多少次,只有是被 AppClassLoader 加载的,其 Class 信息的 hashcode 都是雷同的。

3.2 子加载器可见父加载器加载的类

这个更容易举例,外围类库的类由 BootStrapClassLoder 加载的,比方String;由AppClassLoader 所加载的类,都能应用 String 对吧?

3.3 父加载器不可见子加载器所加载的类

SPI技术的诞生也是这个起因,什么是 SPI:程序运行过程中要用到的类,通过 以后类加载器 主动加载 ,加载不到(不在以后类加载器的类资源管辖范畴),如果要应用这个类,必须指定一个可能加载这个类的加载器去加载,而怎么获取这个加载器是个问题。
程序都是在线程中执行,那么从线程的上下文中去拿最正当,所以就诞生了线程上下文类加载器,这中场景下加载器就得采纳 非主动加载,即通过 forName 或者 loadClass 的形式去加载类。

下边通过示例 + 科技来验证加载不到的状况

示例中有 Parent 类,Son类。Parent类中有个 getSon 办法中,会应用到 Son 类。编译后,在 classpath 下,把 Son 类移到自定义子加载器的资源目录中,让 AppClassLoader 无奈加载。达到咱们的运行环境要求:

  1. ParentAppClassLoader 加载
  2. Son由自定义的子加载器加载.
  3. 调用 ParentgetSon办法的时候要 new Son(),这样会触发Son 类的加载,然而加载不到 Son 类,报错信息为:java.lang.NoClassDefFoundError: com/rock/Son
public class Parent {

    private Object son;

    public Object getSon(){return new Son();
    }
    public Object setSon(Object son){
        this.son = son;
        return this.son;
    }
}  
public class Son {}
/**
     * 父加载器加载不了, 子加载器所加载的类,* 父加载器加载 Parent
     * 子加载器加载 son
     * Parent#getSon 办法里 new Son()对象.// 报错, 找不到 Son 的类定义.
     */

    @Test
    public void testParentCanntFindSon(){CustomClassLoader01 customClassLoader01 = new CustomClassLoader01(ClassLoader.getSystemClassLoader());
        try {Class<?> classParent = customClassLoader01.loadClass("com.rock.Parent");
            System.out.println("classParent:" + classParent.getClassLoader());
            Class<?> classSon = customClassLoader01.loadClass("com.rock.Son");
            System.out.println("classSon:" + classSon.getClassLoader());
            Object objParent = classParent.newInstance();

            Object objSon = classSon.newInstance();

            Method setSon = classParent.getMethod("setSon",Object.class);
            Object resultSon = setSon.invoke(objParent, objSon);
            System.out.println(resultSon.getClass());
            System.out.println(resultSon.getClass().getClassLoader());

            try {Method getSon = classParent.getMethod("getSon");
                Object invoke = getSon.invoke(objParent);
                System.out.println(invoke.getClass().getClassLoader());
            }catch (Exception exp){
                //java.lang.NoClassDefFoundError: com/rock/Son
                exp.printStackTrace();}
        }catch (Exception exp){exp.printStackTrace();
        }
    }

3.4 不同命名空间的类相互不可见

这个特色也通过实例来验证一下,自定义类加载器 new 进去两个实例,每个实例各自加载一次 Man 类,通过 newInstance 形式实例化出两个对象,对象之间相互调用 setFather 赋值就会报错。即 a 空间的不辨认 b 空间的类,即便全限定名雷同也不辨认。这种个性所导致的坑你踩到过嘛?

public class Man {
    private Man father;
    public void setFather(Object obj){father = (Man)obj;
    }
}
 @Test
    public void testSifferentNamespaceClass(){CustomClassLoader01 customClassLoader01 = new CustomClassLoader01(ClassLoader.getSystemClassLoader());
        CustomClassLoader01 customClassLoader02 = new CustomClassLoader01(ClassLoader.getSystemClassLoader());
        try {Class<?> aClass1 = customClassLoader01.loadClass("com.rock.Man");
            System.out.println("class1 :" + aClass1.getClassLoader());
            System.out.println("class1 :" + aClass1.);

            Class<?> aClass2 = customClassLoader02.loadClass("com.rock.Man");
            System.out.println("class2 :" + aClass1.getClassLoader());
            System.out.println("class2 :" + aClass2);
            Object man1 = aClass1.newInstance();
            Object man2 = aClass2.newInstance();
            Method setFather = aClass1.getMethod("setFather", Object.class);
            setFather.invoke(man1,man2);

        }catch (Exception exp){exp.printStackTrace();
        }
    }
class1 : com.rock.classLoader.CustomClassLoader01@1f28c152
class1 : 2006034581
class2 : com.rock.classLoader.CustomClassLoader01@1f28c152
class2 : 488044861
...
Caused by: java.lang.ClassCastException: com.rock.Man cannot be cast to com.rock.Man
    at com.rock.Man.setFather(Man.java:6)
    ... 27 more
退出移动版