
文章插图
AppClassLoader 在加载一个未知的类名时,它并不是立即去搜寻 Classpath,它会首先将这个类名称交给 ExtensionClassLoader 来加载,如果 ExtensionClassLoader 可以加载,那么 AppClassLoader 就不用麻烦了 。否则它就会搜索 Classpath。
而 ExtensionClassLoader 在加载一个未知的类名时,它也并不是立即搜寻 ext 路径,它会首先将类名称交给 BootstrapClassLoader 来加载,如果 BootstrapClassLoader 可以加载,那么 ExtensionClassLoader 也就不用麻烦了 。否则它就会搜索 ext 路径下的 jar 包 。
这三个 ClassLoader 之间形成了级联的父子关系,每个 ClassLoader 都很懒,尽量把工作交给父亲做,父亲干不了了自己才会干 。每个 ClassLoader 对象内部都会有一个 parent 属性指向它的父加载器 。
class ClassLoader { ... private final ClassLoader parent; ...}值得注意的是图中的 ExtensionClassLoader 的 parent 指针画了虚线,这是因为它的 parent 的值是 null,当 parent 字段是 null 时就表示它的父加载器是「根加载器」 。如果某个 Class 对象的 classLoader 属性值是 null,那么就表示这个类也是「根加载器」加载的 。注意这里的 parent 不是 super 不是父类,只是 ClassLoader 内部的字段 。Class.forName当我们在使用 jdbc 驱动时,经常会使用 Class.forName 方法来动态加载驱动类 。
Class.forName("com.MySQL.cj.jdbc.Driver");其原理是 mysql 驱动的 Driver 类里有一个静态代码块,它会在 Driver 类被加载的时候执行 。这个静态代码块会将 mysql 驱动实例注册到全局的 jdbc 驱动管理器里 。class Driver { static { try { java.sql.DriverManager.registerDriver(new Driver()); } catch (SQLException E) { throw new RuntimeException("Can't register driver!"); } } ...}forName 方法同样也是使用调用者 Class 对象的 ClassLoader 来加载目标类 。不过 forName 还提供了多参数版本,可以指定使用哪个 ClassLoader 来加载Class<?> forName(String name, boolean initialize, ClassLoader cl)通过这种形式的 forName 方法可以突破内置加载器的限制,通过使用自定类加载器允许我们自由加载其它任意来源的类库 。根据 ClassLoader 的传递性,目标类库传递引用到的其它类库也将会使用自定义加载器加载 。自定义加载器ClassLoader 里面有三个重要的方法 loadClass()、findClass() 和 defineClass() 。
loadClass() 方法是加载目标类的入口,它首先会查找当前 ClassLoader 以及它的双亲里面是否已经加载了目标类,如果没有找到就会让双亲尝试加载,如果双亲都加载不了,就会调用 findClass() 让自定义加载器自己来加载目标类 。ClassLoader 的 findClass() 方法是需要子类来覆盖的,不同的加载器将使用不同的逻辑来获取目标类的字节码 。拿到这个字节码之后再调用 defineClass() 方法将字节码转换成 Class 对象 。下面我使用伪代码表示一下基本过程
class ClassLoader { // 加载入口,定义了双亲委派规则 Class loadClass(String name) { // 是否已经加载了 Class t = this.findFromLoaded(name); if(t == null) { // 交给双亲 t = this.parent.loadClass(name) } if(t == null) { // 双亲都不行,只能靠自己了 t = this.findClass(name); } return t; }// 交给子类自己去实现 Class findClass(String name) { throw ClassNotFoundException(); }// 组装Class对象 Class defineClass(byte[] code, String name) { return buildClassFromCode(code, name); }}class CustomClassLoader extends ClassLoader { Class findClass(String name) { // 寻找字节码 byte[] code = findCodeFromSomewhere(name); // 组装Class对象 return this.defineClass(code, name); }}自定义类加载器不易破坏双亲委派规则,不要轻易覆盖 loadClass 方法 。否则可能会导致自定义加载器无法加载内置的核心类库 。在使用自定义加载器时,要明确好它的父加载器是谁,将父加载器通过子类的构造器传入 。如果父类加载器是 null,那就表示父加载器是「根加载器」 。// ClassLoader 构造器protected ClassLoader(String name, ClassLoader parent);双亲委派规则可能会变成三亲委派,四亲委派,取决于你使用的父加载器是谁,它会一直递归委派到根加载器 。Class.forName vs ClassLoader.loadClass
这两个方法都可以用来加载目标类,它们之间有一个小小的区别,那就是 Class.forName() 方法可以获取原生类型的 Class,而 ClassLoader.loadClass() 则会报错 。
Class<?> x = Class.forName("[I");System.out.println(x);x = ClassLoader.getSystemClassLoader().loadClass("[I");System.out.println(x);---------------------class [IException in thread "main" java.lang.ClassNotFoundException: [I...
推荐阅读
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- MySQL无锁化WAL系统那些事儿
- 关于ICP许可证
- 十大最有用的Linux命令
- linux性能监控:CPU监控命令之dstat命令
- 穿衣搭配|30岁职场人的职业选择
- |职场中一定要远离这几类人
- 温州缘定今生婚庆策划公司介绍
- 什么是走入式衣柜 走入式衣柜的优缺点
- 特立独行的80后婚纱照
- 羽衣甘蓝食用方法 羽衣甘蓝功效
