JAVA中URLClassLoader ClassLoader的进阶版

阅读一下文章,你能学习到一种新的,更加低耦合的开发方式。ClassLoader的使用方式进阶版URLClassLoader。在正式开始之前,先简单回顾一下反射和双亲委派机制

反射

在讲反射之前,我们先回顾一下Class的信息,当然这是我个人知识体系上面的回顾,可能存在误区,希望大伙指明错误,谢谢

在我们编写完程序保存后,生成了.java文件,通过javac指令把.java文件通过JIT编译器编译成.class文件。在通过java指令运行.class。jvm虚拟机就会把class的信息保存在方法区当中。这是全有对象的认祖归宗的最重要的信息,在我们new创建对象的执行过程当中,一个从无到有的过程当中,对象首先存在数据就是对象头当中的数据。

java的对象头由以下三部分组成:

  1. Mark Word
  2. 指向类的指针
  3. 数组长度(只有数组对象才有);

知道了这些基础信息,让我们来看看获取Class信息的3种方式,你就知道能大致明白对应的方式

public class ReflectTest {
 public static void main(String[] args) throws Exception {
        //不会真正加载到jvm当中
        Class<CCN> ccnClass = CCN.class;
        //会真正加载到jvm当中
        Class<?> aClass = Class.forName("com.ccn.reflect.CCN");
        //会真正加载到jvm当中
        CCN ccn = new CCN();
        Class<? extends CCN> aClass1 = ccn.getClass();
    }
}

我们能想象出这三种方法分别获取的方式 :Xxx.class。直接通过文件获取class信息,obj.getClass()通过对象获取class信息。重点介绍Class.forName(“全限定类名”)。

Class.forName(“全限定类名”)。

在这里插入图片描述
再点进去
在这里插入图片描述
我们发现是会用当前执行代码的全限定类名去加载我们的class.forName()对象。为什么呢?其实不然我们知道,main方法是我们程序的入口函数。它是必然保证已经加载到jvm当中,也必然保证拥有的classLoader对象。用它来帮助我们的将类加载到jvm当中。当然不仅main方法,只要调用者,也必然保证他们已经初始化完成,同样也有如上的效果。

如果classLoader找不到该类就会报ClassNotFoundException错误
在这里插入图片描述

双亲委派机制

java自带使用三种加载器把.class文件加载到jvm当用运动。如下图。当然开发者们也能自行扩展。
在这里插入图片描述

  • Bootstrap classLoader:主要负责加载核心的类库(java.lang.*等),构造ExtClassLoader和APPClassLoader。
  • ExtClassLoader:主要负责加载jre/lib/ext目录下的一些扩展的jar。
  • AppClassLoader:主要负责加载应用程序的主函数类
  public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
    }

    protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
            // 首先,检查是否已经被类加载器加载过
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                try {
                    // 存在父加载器,交由父加载器(递归调用)
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        // 直到最上面的Bootstrap类加载器
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }
 
                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    c = findClass(name);
                }
            }
            return c;
    }

执行过程如下图(借用于作者:IT烂笔头
在这里插入图片描述

我们言归正传理解Class.forName();

通过dubug我们惊奇的发现。它是会跑到ClassLoader.loadClass当中的。精彩的事情马上开始,开始前先梳理一下执行。
Class.forName() -> native代码(C++代码) -> ClassLoader.loadClass();

分析ClassLoader.loadClass(“com.ccn.leetcode.CCN”)

在这里插入图片描述
当第一次执行ClassLoader.loadClass(“com.ccn.leetcode.CCN”),发现存在parent类加载器(Ext 扩展类加载器),基于双亲委派机制规则,递归调用,尝试使用parent类加载器加载“com.ccn.leetcode.CCN”;
在这里插入图片描述
(递归调用)当第二次执行ClassLoader.loadClass(“com.ccn.leetcode.CCN”)。发现parent类加载器为空,尝试使用启动类加载器加载(由此我们得知,扩展类加载器是没有父类加载器的

接下来可能有点绕,大伙赶紧上车,做好准备。

c = findBootstrapClassOrNull(name);
发现 c == null成立 尚未加载成功。
在这里插入图片描述

第一次调用findClass(尝试使用当前类加载器加载对象(当前的类加载器是什么???【1】))然后findClass就会调用URLClassLoader.findClass()尝试加载对象(我们的主角终于出现了!!!)
【1】:Ext 扩展类加载器

在这里插入图片描述
关于源码中AccessController.doPrivileged。查看

在这里插入图片描述
Ext 扩展类加载器尝试加载但是失败了。所以抛出ClassNotFoundException异常,但是我们要知道,这是第二次调用loadClass的第一次findClass操作。在我们使用扩展类加载器之前,我们是首先也有一个类加载器这个类加载器是什么???【2】
在这里插入图片描述
【2】 : APP 应用程序类加载器
当我第一次调用ClassLoad.loadClass。使用的是Applicaton ClassLoader的时候。这个方法栈帧捕获到了来自非空 parent加载器的ClassNotFoundException异常。所以尝试自己执行findCLass。
在这里插入图片描述
执行成功!!!
到此整一个加载顺叙,我们就梳理完成了,是不是有种头皮拔凉拔凉的感觉。不要紧张。剃光就好了。接下来让我们讲解这篇文章的主题URLClassLoader

URLClassLoader

开始之前。我们先讲一下什么是全限定类名,什么是ClassPath路径。这个问题之前一直在我脑海中傻傻分不清,原因是我们把他们分开记了,通过下面分析,我希望大家把两者合在一起记住。
在我们使用Class.forName("")时候,一定要使用权限定类名,那什么是全限定类名。从内容上理解,他们就package名 + 类名
在这里插入图片描述
com.ccn.reflect + .CCN 就是这个当前类的全限定类名。
那什么是ClassPath路径呢?以前的知识什么说src下路径,打包成target后,又不知道去哪里了,jar包,war包一样。傻傻的分不清。

ClassPath路径

在这里插入图片描述
URLClassLoader.findClass通过path,调用URLClassPath找到了我们的CCN.class所在的地方,什么我们在使用电脑找文件,点击文件一步步找下去的想法, URLClassPath + path = CCN.class文件所在的位置。到这里我们应该可以理解 ClassPath 路径了吧。不管打包成什么样子,jvm加载.class的方式(除非你破坏它)是不会改变的,是通过地址找到.class文件io操作把他加载。 path是已知道,全限定类名。那么我们在我们的项目下如果找到文件,也就是jvm如何找到文件。ClassPath就是我们找到文件的路径 *减去 全限定类名转换成文件路径

终于终于开始了

我觉得全限定类名不好,也耦合了,我不希望这样做那应该怎么做?

package com.ccn.leetcode;

public class Person {
    public void say() {
        System.out.println("Person");
    }
}
package com.ccn.leetcode;

public class CCN  extends Person {
    @Override
    public void say() {
        System.out.println("CCN");
    }
}
  public  static void main(String[] args) throws Exception {
            URL[] urls = new URL[1];
            URLStreamHandler streamHandler = null;
            File classPath = new File("E:\\IdeaProjects\\SpringMVC\\offer\\src\\com\\ccn\\leetcode");
            String repository = (new URL("file", null, classPath.getCanonicalPath() + File.separator)).toString() ;
            System.out.println(repository);
            urls[0] = new URL(null, repository, streamHandler);
            ClassLoader loader = new URLClassLoader(urls);
            Class<?> aClass = loader.loadClass("CCN");
            Person o =(Person) aClass.newInstance();
            o.say();
        }

URLClassLoder类是ClassLoader类的一个直接子类。他有很多个构造函数,在此我们使用最简单的 URLClassLoader(URL[ ] urls);
urls是一个java.net.URL对象数组,**当裁人一个类时每个URL对象指明的类加载器到哪里查找类(本地/网络)**若一个URL以“/”结尾,则表明它指向一个目标(上述代码就是利用这点 +File.separator)否则,URL默认指向一个JAR文件。

由于之前路径代码path在这里插入图片描述
找得是.class文件。所以我们需要自己手动编译一下。(当然我当初后者驱动前者,尝试了很久)
在这里插入图片描述
回到运行主函数。如无意外出错了
在这里插入图片描述

NoClassDefFoundError是在运行时JVM加载不到类或者找不到类。它跟ClassNotfoundException很像。但ClassNotfoundException表示在编译时JVM加载不到类或者找不到类导致的; Class.forName()全限定类名写错导致的错误。

经过我不懈的尝试(其实是看深入剖析Tomcat的代码)找出问题

在这里插入图片描述

在这里插入图片描述
因为我们把包名去掉了,idea出错先他他注销,在运行代码。。
重新编译!!见证奇迹的时候到了
在这里插入图片描述

到此我们整个学习过程就完全结束了!这功能到底有什么用呢?

总结:首先我们看到了面向接口编程的重要性,这种降低耦合度的方式,是我目前学到最强的了(我觉得有点像SPI),我们完全就可以通过IO编程,对编写更改文本的方式来改变执行的代码。学到这里其实我们知道URLClassLoader 其实不是ClassLoader进阶版,然后还是他的子类,我们平时使用的时候也是在用URLClassLoader原本封装好的方法,所以才可以直接用全限定内名来加载代码。

为什么不能变为原来的子类???

我们把包路径删掉了,它对应的就是唯一一个没有包路径的CCN。那么它对应就是直接放在SRC下的CCN。
在这里插入图片描述
我想应该没有人这样编写程序吧,所以最好面向接口编程。突然想到,因为我在load.class用得是"CCN"”所以有跟有package的CCN就不是同一个玩意,所以之前才报NoClassDefFoundError的错误。因为在项目当中包名+类名 == 唯一ID。我没有尝试,但是我认为如果load.class传进去全限定类名的话(又回到原点),代码是不需要改直接编译后就可以运行,也可以直接向下转换成功。我没尝试交给大伙试一下了。所以还是面向接口编程最好。更低耦合

谢谢大家观看!

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值