学习类加载器机制

import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;

/**
 类加载器
一 类加载器机制
    1 所有的类都有类加载器加载
    2 同一个类的标准
        java: 包名 + 类名(全限定名)
        JVM: 类加载器 + 包名 + 类名;
    3 类加载器结构层次,如下
        根类加载器   Bootstrap Classloader 负责加载Java的核心类.
                    在sun的JVM中.执行java.exe时使用-Xbootclasspath 或-D指定sun.boot.class.path系统属性值,即可指定加载附加的类
        拓展类加载器 Extension ClassLoader
        系统类加载器 System ClassLoader
        用户类加载器 通过继承ClassLoader来实现.
        -注意: 类加载器之间的父子关系并不是类继承上的父子关系.而是类加载器实例之间的关系.
    4 JVM类加载机制如下3类
        -全盘负责  该类加载器负责加载该Class,及其该Class所依赖的其他Class.除非显式的指定另一个类加载器.
        -父类委托  即先让父类加载器试图加载该Class,只有在父类加载器无法加载该Class时才尝试从自己的类路径中加载该Class.
        -缓存机制  保证所有加载过的Class都会被缓存.当程序需要某个Class时,类加载器先从缓存区中搜寻该Class
                    不存在  系统才执行该类的类加载阶段即将该类的二进制数据读取到内存中得到想要的Class对象,并该Class其存入缓存区.
    5 类加载器加载Class(其实就是当前类加载器loadClass()方法地全部内容)
        -总结步骤如下3步(涉及缓存机制,父类委托,全盘负责等JVM类加载机制)
        1 从缓存区获取Class,如果不存在.
        2 请求父类加载器从它的类加载路径中寻找class文件,找到了加载该Class,存入缓存区,并返回该Class
            (如果父类加载器不存在,则请求根类加载器再次执行第2步)
        3 如果找不到class文件,则抛出ClassNotFoundException异常
  注意: 第2步中涉及父类委托类加载器机制,加载Class对象的类加载器呈现的的优先级结果就是:
    根类加载器,拓展类加载器,系统类加载器,自定义类加载器(父类加载器的类加载路径中找不到该类的class文件,则通过子类加载器去完成同样的加载过程)
    (父类委托机制其实就是类加载器的实例之间的关系相当于类的继承关系)
        4 具体步骤如下(增加了具体执行的方法)
        findLoadedClass()//缓存机制
        存在: 返回该Class
        不存在 (父类委托)
            父类加载器
                存在 : loadClass()
                        findClass()从当前类加载器的类加载路径中寻找
                            找到并加载该Class(defineClass()),存入缓存区,并返回该Class.
                            找不到抛出ClassNotFoundException异常
                不存在
                    请求根类加载器: loadClass()
                        findClass()从当前类加载器的类加载路径中寻找
                             找到并加载该Class(defineClass()),存入缓存区,并返回该Class.
                             找不到抛出ClassNotFoundException异常
二 自定义类加载器的定义和使用
    (I) 开发自定义的类加载器步骤
      1 通过拓展ClassLoader子类( JVM中除了根类加载器之外所有的类加载器都是ClassLoader及其子类的实例)
      2 重写ClassLoader所包含的2个关键方法.
    (II) 可以重写ClassLoader类有如下2个关键方法.
         protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
            该方法为ClassLoader的入口点.根据名称加载类.系统就是调用ClassLoader的该方法获取指定类的Class的
         protected Class<?> findClass(String name) throws ClassNotFoundException 根据名称查找类
        提示:
        1 推荐重写findClass()方法(也可以重写两个方法)
        2 不建议重写loadClass()方法,该方法执行步骤如下(重写该方法实现逻辑复杂)
            (1) findLoadedClass() 从缓冲区检查是否已经加载过该Class,如果已经加载,直接返回(缓存机制)
            (2) 委托父类上调用loadClass(),如果父类加载器为null,则使用根类加载器.(父类委托机制)
            (3) 调用 findClass()根据名称查找类(即在当前类加载器的类加载路径中查找目标类的class二进制文件)
            findLoadedClass()->loadClass()->findClass()
    (III) ClassLoader的其他方法
        1 ClassLoader的另一个核心方法 defineClass() 将查找到的class文件读入内存并转成Class对象的关键方法.
             protected final Class<?> defineClass(String name, byte[] b, int off, int len) throws ClassFormatError
           (1)这个方法负责将指定类的class字节码文件读入字节数组byte[] b中,并且把它转为Class对象,该字节码文件可以来源于网络,文件等.
           (2) defineClass()方法管理许多复杂的实现.负责将字节码解析成运行时数据结构,并校验有效性等.(该方法为final,无法重写)
        2 ClassLoader还包含一些普通方法
protected findSystemClass(String name) 从本地文件系统装入class文件.如果存在,内部通过defineClass()将原始字节转成Class对象.
        static getSystemClassLoader() 获取系统类加载器
        static getPlatformClassLoader() 获取拓展类加载器
        ClassLoader getParent()         获取父类加载器
 protected final void resolveClass(Class<?> c) (执行类的链接阶段)链接指定的类,类加载器可以使用该方法来来链接已经加载的Class
        findLoadedClass()

三 URLClassLoader(指定多个class二进制文件加载来源,即指定多个类加载路径)
    1 构造器
        URLClassLoader(URL[] urls) 可接受多个URL作为类加载路径,默认父类加载器
        URLClassLoader(URL[] urls, ClassLoader parent) 指定父类加载器
    2 用法
        一旦得到了URLClassLoader类加载器,则可以调用该类地loadClass加载指定Class,此时无需通过CLASSPATH环境变量加载该Class了。
 要求:
    1 正确的类加载路径,一定要注意要加斜杠,所有表示目录的都要加斜杠,不加则表示这是文件.(e:/不等于c:,它们在显示上没有差别,具有迷惑性))
    2 正确全限定名,因为系统要在类加载路径下寻找这个类(包名=路径名,类名=class文件名)
    3 public修饰(不可以加载省略修饰符的类,因为它们的访问权限在它的包内,需要在包外部访问要用public修饰)
踩坑:
    1 URLClassLoader总是优先加载当前类加载路径下的Class,其次就是URLClassLoader地URL数组中元素顺序。(父类委托机制)
    2 如果通过非默认的类加载器(比如URLClassLoader)加载当前项目中的Class,则该类必须显式地提供构造器。(否则报错)
    3 通过new File("e:/").toURI().toURL();//默认file协议,一定要注意要加斜杠,表示这是一个目录,不加则表示这是文件.
    4 永远记住路径字符串末尾加斜杠表示目录,不加斜杠表示文件,(实际在获取文件时极其容易丢掉斜杠)
    5 url路径 = 协议名+冒号+路径(注意加冒号就可以,正确比如file:e:/,错误比如file:/e:,file:\\e)
 */
public class 类加载器 {
    public static void main(String[] args) throws  Exception  {
        var url1 = new File("e:/").toURI().toURL();//默认file协议,一定要注意要加斜杠,表示这是一个目录,不加则表示这是文件.
        URL url2 = new URL("file:C:\\Users\\CAI\\Desktop\\RUSH\\");//也要加斜杠
        var urlClassLoader = new URLClassLoader(new URL[]{url1,url2});//优先从url1中寻找指定类
        Class<?> clazz = urlClassLoader.loadClass("com.china.school.reflect.Template");//(全限定名要正确,包名=路径名,类名=class文件名)
        var template = clazz.getConstructor().newInstance();
        System.out.println(template);//输出的是对象来源于url1

    }

}

运行结果如下:

我是实例代码块,类加载路径的url对象为: url1
com.china.school.reflect.Template@19dfb72a

posted @ 2022-05-19 12:32  -和时间赛跑-  阅读(39)  评论(0编辑  收藏  举报