当前位置: 首页 > 后端技术 > Java

ClassLoader隔离的基石是命名空间,我给你们证明一下

时间:2023-04-01 20:31:55 Java

1.背景朋友:在我的知识体系中,ClassLoader的双亲委托机制是顺滑如丝的,但是我看出来这种划分是通过委托类加载的执行来保证的,然后实现类资源的隔离,突然觉得有点陌生和排斥?我:你知道类的命名空间吗?朋友:你是说包裹?我:我说的是ClassLoader中的nameSpace朋友:什么鬼?本文是对ClassLoader中命名空间的直观介绍和验证。笔者个人认为这个知识点非常重要,多次帮助笔者解决了日常工作中遇到的疑难杂症。如果你没有认真研究过ClassLoader,但是你懵懂的认知让你觉得这应该很简单,那么请看下图如果你看不懂,说明你可能没有理解ClassLoader中的一些关键逻辑;如果你不卖焦虑,如果你不感兴趣,就忽略它,知道一个大概的概念,并不会影响你成为一个优秀的程序员。2.Classloader分委托机制的上下文同步。已经了解这部分的读者可以直接跳过,直接进入第三节。ClassLoader是一个抽象类,其子类UrlClassLoader引入了URL资源的概念来约束自己只加载URL覆盖范围内的类文件;ExtClassLoader和AppClassLoader都是UrlClassLoader的子类,它们的资源路径不同,即给定不同的URL有不同的权限,通过委托类加载的执行来保证分而治之的能力,从而实现类资源的隔离.上图是标准的委托机制,可以概括为两个方面:父加载器可以加载父加载器加载:在加载资源之前,先让父类加载器加载。然后父类找到它的父类直到BootStrapClassLoader(它没有父类加载器)。保证级别越高,加载优先级越高。如果父加载器没有加载,我就加载它(findClass);如果我无法加载的子加载器将加载它:如果父加载器加载不成功,加载器将逐步释放。正确的。子类加载器无法加载父类加载器可以加载的类,例如java.lang.String。即使用户自己编造了这个类型的拷贝,启动类加载器也会先加载java.lang.String成功,而应用类加载器是不会加载用户自己编造的。下图描述了SkyWalkingAgent通过自定义类加载器AgentClassLoader加载插件中的类,不会污染宿主应用中的类。3.什么是命名空间?每个类加载器对应一个命名空间,中文称为命名空间(我个人更喜欢中文是命名空间),命名空间由加载器加载的类和所有父类加载器组成。这种介绍很抽象,网上的资料一大堆句子。我会从更细粒度的角度带大家进一步了解它。3.1在同一个命名空间下,只加载一个类,比如AppClassLoader加载器中写的类。不管加载多少次,只要是被AppClassLoader加载过的,它的Class信息的hashcode都是一样的。3.2子加载器可以看到父加载器加载的类。这是一个更简单的例子。核心类库中的类由BootStrapClassLoder加载,如String;AppClassLoader加载的类可以使用String,对吧?3.3父加载器是不可见的。childloader加载的类SPI技术的诞生也是这个原因。什么是SPI:程序运行过程中要用到的类不能通过当前类加载器的自动加载来加载(不是当前类加载器的类资源管辖),如果要使用这个类,必须指定一个可以加载这个类来加载的加载器,如何获取这个加载器是个问题。程序是在线程中执行的,那么从线程的上下文中获取是最合理的,于是线程上下文类加载器诞生了。在这种场景下,加载器不得不使用非自动加载,即通过forName或loadClass。加载类。下面的例子+技术用于验证无法加载的情况。在示例中,有Parent类和Son类。Parent类中有个getSon方法,会用到Son类。编译完成后,将Son类移动到classpath下自定义子加载器的资源目录下,这样就无法加载AppClassLoader。满足我们的运行环境要求:Parent由AppClassLoader加载,Son由自定义的子加载器加载。在调用Parent的getSon方法时,需要newSon(),会触发Son类的加载,但是如果无法加载Son类,则会报错信息为:java.lang.NoClassDefFoundError:com/rock/SonpublicclassParent{privateObjectson;publicObjectgetSon(){返回新的Son();}publicObjectsetSon(Objectson){this.son=son;返回这个.son;}}publicclassSon{}/***父加载器无法加载子加载器加载的类,*父加载器加载Parent*子加载器加载son*NewSon()inParent#getSonmethodObject.//报错,找不到Son的类定义。*/@TestpublicvoidtestParentCanntFindSon(){CustomClassLoader01customClassLoader01=newCustomClassLoader01(ClassLoader.getSystemClassLoader());尝试{ClassclassParent=customClassLoader01.loadClass("com.rock.Parent");System.out.println("classParent:"+classParent.getClassLoader());类classSon=customClassLoader01.loadClass("com.rock.Son");西stem.out.println("classSon:"+classSon.getClassLoader());对象objParent=classParent.newInstance();对象objSon=classSon.newInstance();方法setSon=classParent.getMethod("setSon",Object.class);对象resultSon=setSon.invoke(objParent,objSon);System.out.println(resultSon.getClass());System.out.println(resultSon.getClass().getClassLoader());尝试{方法getSon=classParent.getMethod("getSon");对象调用=getSon.invoke(objParent);System.out.println(调用.getClass().getClassLoader());}catch(Exceptionexp){//java.lang.NoClassDefFoundError:com/rock/Sonexp.printStackTrace();}}catch(Exceptionexp){exp.printStackTrace();}}3.4不同命名空间下的类相互不可见。这个特性也可以通过例子来验证,自定义类加载器new出来了两个实例,每个实例加载一次Man类,通过newInstance方法实例化了两个对象,当对象相互调用setFather赋值时,会报错,即a空间的类不识别b空间的类,即使完全限定名是相同。这种特殊性所导致的坑你踩到过嘛?publicclassMan{privateManfather;publicvoidsetFather(Objectobj){father=(Man)obj;}}@TestpublicvoidtestSifferentNamespaceClass(){CustomClassLoader01customClassLoader01=newCustomClassLoader01(ClassLoader.getSystemClassLoader());CustomClassLoader01customClassLoader02=newCustomClassLoader01(ClassLoader.getSystemClassLoader());尝试{ClassaClass1=customClassLoader01.loadClass("com.rock.Man");System.out.println("class1:"+aClass1.getClassLoader());System.out.println("class1:"+aClass1.);ClassaClass2=customClassLoader02.loadClass("com.rock.Man");System.out.println("class2:"+aClass1.getClassLoader());System.out.println("class2:"+aClass2);对象man1=aClass1.newInstance();对象man2=aClass2.newInstance();方法setFather=aClass1.getMethod("setFather",Object.class);setFather.invoke(man1,man2);}catch(Exceptionexp){exp.printStackTrace();}}class1:com.rock.classLoader.CustomClassLoader01@1f28c152class1:2006034581class2:com.rock.classLoader.CustomClassLoader01@1f28c152class2:488044861...引起:java.lang.ClassCastException:com.rock.Man无法转换为com。rock.Manatcom.rock.Man.setFather(Man.java:6)...还有27个