类加载器子系统与SPI机制

类加载器

加载过程

  1. 检测此Class是否载入过,即在缓冲区中是否有此Class,如果有直接进入第8步,否则进入第2步。

  2. 如果没有父类加载器,则要么Parent是根类加载器,要么本身就是根类加载器,则跳到第4步,如果父类加载器存在,则进入第3步。

  3. 请求使用父类加载器去载入目标类,如果载入成功则跳至第8步,否则接着执行第5步。

  4. 请求使用根类加载器去载入目标类,如果载入成功则跳至第8步,否则跳至第7步。

  5. 当前类加载器尝试寻找Class文件,如果找到则执行第6步,如果找不到则执行第7步。

  6. 从文件中载入Class,成功后跳至第8步。

  7. 抛出ClassNotFountException异常。

  8. 返回对应的java.lang.Class对象。

在Java中,一个类用其全限定类名(包括包名和类名)作为标识;但在JVM中,一个类用其全限定类名和其类加载器作为其唯一标识。

分类

C++编写
启动类加载器-Bootstrap ClassLoader:

  • 没有实体,JVM将C++处理类加载的一套逻辑定义为启动类加载器;由C++编写的,通过Java程序去查看显示的是null
  • 通过启动类加载器加载类sun.launcher.LauncherHelper,执行该类的方法checkAndLoadMain,加载main函数所在的类,启动扩展类加载器、应用类加载器也是在这个时候完成的
  • 可以通过-Xbootclasspath指定
  • 用来加载 Java 的核心库
  • 负责加载$JAVA_HOME中jre/lib/rt.jar里所有的class

java编写,继承自java.lang.ClassLoader
扩展类加载器-Extension ClassLoader:

  • 可以通过java.ext.dirs指定

  • 用来加载 Java 的扩展库

  • 负责加载JRE的扩展目录,lib/ext
    应用类加载器-Application ClassLoader:

  • 可以通过java.class.path指定

  • 根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。

自定义类加载器-User ClassLoader

双亲委派

双亲委派机制
原理:

双亲委派机制,其工作原理的是,如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式,即每个儿子都很懒,每次有活就丢给父亲去干,直到父亲说这件事我也干不了时,儿子自己才想办法去完成。

	try {
    if (this.parent != null) {
        c = this.parent.loadClass(name, false);
    } else {
        c = this.findBootstrapClassOrNull(name);
    }
} catch (ClassNotFoundException var10) {
                }

优势

  • 避免重复加载
    Java类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层级关可以避免类的重复加载,当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次。
  • 避免核心类篡改
    假设通过网络传递一个名为java.lang.Integer的类,通过双亲委托模式传递到启动类加载器,而启动类加载器在核心Java API发现这个名字的类,发现该类已被加载,并不会重新加载网络传递的过来的java.lang.Integer,而直接返回已加载过的Integer.class,这样便可以防止核心API库被随意篡改。

打破双亲委派

1. 自定义类加载器,重写loadClass方法

  • loadClass:双亲委派机制就是通过该方法实现的。默认过程-先判断该类是否被当前层的类加载过,如果没有就将该类委托给父类加载器。如果最顶端父类无法加载则向下传递。
    重写方法后可以自己定义是用什么加载器,也可以自定义委派机制。
  • findclass
    这只是一个空方法,返回内容为class,方法其中没有任何内容,只抛出了个异常,说明这个方法需要开发者自己去实现。
    如果自定义的方法不想违背双亲委派模型,则只需要重写findclass方法即可,如果想违背双亲委派模型,则还需要重写loadclass方法。
if (name.startsWith("com.luban")) { 
// com.luban包下的所有类不委派
    c = findClass(name);
} else {
    c = this.getParent().loadClass(name);
}

线程上下文类加载器破坏双亲委派机制
SPI

  • 概念
    约定在 Classpath 下的 META-INF/services/ 目录里创建一个以服务接口命名的文件,然后文件里面记录的是此 jar 包提供的具体实现类的全限定名。这样当我们引用了某个 jar 包的时候就可以去找这个 jar 包的 META-INF/services/ 目录,再根据接口名找到文件,然后读取文件里面的内容去进行实现类的加载与实例化。
  • 缺点
    Java SPI 在查找扩展实现类的时候遍历 SPI 的配置文件并且将实现类全部实例化,假设一个实现类初始化过程比较消耗资源且耗时,但是你的代码里面又用不上它,这就产生了资源的浪费。
  • 实现
  1. 当服务提供者提供了接口的一种具体实现后,在 jar 包的 META-INF/services 目录下创建一个以 “接口全限定名” 命名的文件,内容为实现类的全限定名;
  2. 接口实现类所在的 jar 包放在主程序的 classpath 中;
  3. 主程序通过 java.util.ServiceLoder 动态装载实现模块,它通过扫描 META-INF/services 目录下的配置文件找到实现类的全限定名,把类加载到 JVM ;
  4. SPI 的实现类必须携带一个不带参数的构造方法;
  • 线程上下文类加载器引入原因
    Java 提供了很多服务提供者接口(SPI),允许第三方为这些接口提供实现。常见的 SPI 有 JDBC、JCE、JNDI、JAXP 和 JBI 等。这些 SPI 的接口由 Java 核心库来提供,
    SPI 实现的 Java 类一般是由系统类加载器来加载的。启动类加载器是无法找到 SPI 的实现类的,因为它只加载 Java 的核心库。它也不能代理给系统类加载器,因为它是系统类加载器的祖先类加载器。也就是说,类加载器的代理模式无法解决这个问题。
    优点
    使用线程上下文类加载器,可以在执行线程中抛弃双亲委派加载链模式,使用线程上下文里的类加载器加载类。典型的例子有:通过线程上下文来加载第三方库jndi实现,而不依赖于双亲委派。
    操作方法
    ServideLoader—
    获取
    Thread.currentThread().getContextClassLoader()
    设置
    Thread.currentThread().setContextClassLoader(new Classloader_4());
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值