从上面的结果可以看出,Person是由我们的Test01ClassLoader自定义类加载器所加载的,那么它的父亲加载器是AppClassLoader,显然Dog类是由我们的AppClassLoader所加载的。故此代码正常运行,没有抛出异常,从而得出结论:
1:父加载器所加载的类,不能访问子加载器所加载的类。 2:子加载器所加载的类,可以访问父加载器所加载的类。 双亲委托模型的弊端
我们先看一段我们非常熟悉的数据库连接相关的代码片段。
Class.forName("com.mysql.jdbc.Driver"); Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/RUNOOB","root","123456"); Statement stmt = conn.createStatement(); 案例分析在上述图中的第五步为什么会用线程上下文加载器进行加载呢?
在双亲委托模型的机制下,类的加载是由下而上的。即下层的加载器会委托上层进行加载。有些接口是Java核心库(rt.jar)提供的例如上面的createStatement接口,而Java核心库是由启动类加载器进行加载的。而这些接口的具体实现是来自不同的厂商(Mysql)。而具体的实现都是通过依赖jar包放到我们项目中的classPath下的。Java的启动类加载器/根类加载器是不会加载这些其他来源的jar包。
我们都知道classPath下的jar包是由我们系统类加载器/应用加载器进行加载,根据我们双亲委托的机制父类加载器是看不到子类(系统类加载器)所加载的具体实现。createStatement 这个接口是由根类加载器进行加载的 而具体的实现又加载不了。在双亲委托的机制下,createStatement这个接口就无具体的实现。
我们Java的开发者就通过给当前线程池设置上下文加载器的机制,就可以由设置的上下文加载器来实现对于接口实现类的加载。换句话说父类加载器可以使用当前线程上下文加载器加载父类加载器加载不了的一些接口的实现。完美了解决了由于SPI模型(接口定义在核心库中,而实现由各自的厂商以jar的形式依赖到我们项目中)的接口调用。
下面我提供了一张SPI的流程图。不知道什么是SPI的小伙伴儿,可以看一下这张图:
从上面的例子,我们可以看出,双亲委托模型的弊端。然后我们的jdk给我们提供了一种通过修改线程上下文类加载的方式来打破这种双亲委托的规则。关于修改上下文类加载的话题,我们下个章节再具体的讲解。接下来呢,我们再看看,获取类加载器的几个方法。并且奉上翻译好的java doc文档。方便我们后续学习线程类加载器。
获取类加载器的几个方法Class.getClassLoader()
/** * Returns the class loader for the class(返回加载该类的类加载器). Some implementations may use * null to represent the bootstrap class loader(有一些jvm的实现可能用null来表示我们的启动类加载器比如 hotspot). * This method will return null in such implementations if this class was loaded by the bootstrap class loader. * 若这个方法返回null的话,那么这个类是由我们的启动类加载器加载 * * If this object represents a primitive type or void, null is returned. (原始类型 比如int,long等等的类或者 void类型 那么他们的类加载器是null) * * */ public ClassLoader getClassLoader() { ClassLoader cl = getClassLoader0(); if (cl == null) return null; SecurityManager sm = System.getSecurityManager(); if (sm != null) { ClassLoader.checkClassLoaderPermission(cl, Reflection.getCallerClass()); } return cl; }
1:返回代表加载该class的类加载器
2:有一些虚拟机(比如hotspot) 的启动类加载器是null来表示
3:原始类型 比如int ,long 或者是void类型 ,他们的类加载器是null