JVM-线程上下文类加载器

SPI是什么

Java提供了很多SPI,允许第三方为这些接口提供实现,最常见的SPI实现有JDBC、JNDI等等,根据类加载器的双亲委派模型,加载ServiceLoader的 BootstrapClassLoader 是不能加载SPI的实现类的,因为SPI的实现类是由 AppClassLoader 加载的,而 BootstrapClassLoader 是不能委派 AppClassLoader 来加载类的,那该怎么办呢?

SPI约定为:当服务的提供者提供了服务接口的一种实现之后,在jar包的META-INF/services/目录里同时创建一个以服务接口命名的文件。该文件里就是实现该服务接口的具体实现类。而当外部程序装配这个模块的时候,就能通过该jar包META-INF/services/里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。
SPI就是

在resources目录下,新建META-INF/services目录,然后新建文件(文件名=接口包名.接口名)。内容就是实现类的包名.类名
image.png
image.png

SPI的接口是Java核心库的一部分,按照双亲委派模式和类加载器搜索路径而言:它是由启动类加载器来加载的;但是SPI的实现类在我们应用引入之后在应用Classpath下,BootstrapClassLoader不认识它加载不了,只能由系统类加载器来加载的。原因在于启动类加载器是无法找到 SPI 的实现类的(因为它只加载 Java 的核心库),按照双亲委派模型,启动类加载器又无法委派系统类加载器去加载类。也就是说,类加载器的双亲委派模式无法解决这个问题
这时候线程上下文类加载器排上了用场。线程上下文类加载器破坏了“双亲委派模型”,可以在执行线程中抛弃双亲委派加载链模式,使程序可以逆向使用类加载器。

线程上下文类加载器(TCCL)

可使用ServiceLoader的load方法获取到TCCL

private static final String PREFIX = "META-INF/services/";

public static <S> ServiceLoader<S> load(Class<S> service) {    
    // 获取当前调用线程的类加载器
    ClassLoader cl = Thread.currentThread().getContextClassLoader();    
    return ServiceLoader.load(service, cl);
}

Java.lang.Thread中的方法 getContextClassLoader()和 setContextClassLoader(ClassLoader cl)用来获取和设置线程的上下文类加载器。如果没有通过setContextClassLoader(ClassLoader cl)方法进行设置的话,线程将继承其父线程的上下文类加载器。Java 应用运行的线程上下文类加载器初始是AppClassLoader,在线程中运行的代码可以通过此类加载器来加载类和资源。

ServiceLoader<Lxw> loader = ServiceLoader.load(Lxw.class);
Iterator<Lxw> iterator = loader.iterator();
while (iterator.hasNext()) {
    Lxw l = iterator.next();
    l.say();
}

应用程序启动会走这个获取到系统类加载器,是应用程序类加载器,然后执行第一行进入load放方法
在这里插入图片描述

获取到线程上下文类加载器
,然后就是load了,new了一个serviceLoader
在这里插入图片描述

可以看到new了一个类出来
在这里插入图片描述

在这里插入图片描述

总结

  • 直白一点说就是,我(JDK)提供了一种帮你(第三方实现者)加载服务(如数据库驱动、日志库)的便捷方式,只要你遵循约定(把类名写在/META-INF里),那当我启动时我会去扫描所有jar包里符合约定的类名,再调用forName加载,但我的ClassLoader是没法加载的,那就把它加载到当前执行线程的TCCL里,后续你想怎么操作(驱动实现类的static代码块)就是你的事了。

  • 线程上下文类加载器(它并不是一个真正的类加载器,而是通过当前线程拿到我们想要的类加载器->应用运行时它被放在了线程中,所以不管当前程序处于何处BootstrapClassLoader或ExtClassLoader等,在任何需要的时候都可以拿出去使用)。
    线程上下文类加载器打破了双亲委派机制,实现逆向调用类加载器来加载当前线程中类加载器加载不到的类

  • 当高层提供了统一接口让低层去实现,同时又要是在高层加载(或实例化)低层的类时,比如上面spi的调用者ServiceLoader所在的BootstrapClassloader无法加载的时候,必须通过线程上下文类加载器来帮助高层的ClassLoader找到并加载该类。

  • 类/接口是有命名空间之分的,不同的类加载器是不同的命名空间。一个类是由A类加载器加载的,那么这个类的依赖类也会由这个A类加载器加载,但是如果所依赖的类不在A类加载器加载的范围内,那么就会找不到这个类。可以使用线程上下文类加载器进行加载使用。这种操作就是破坏了双亲委派模式

参考

  • https://blog.csdn.net/yangcheng33/article/details/52631940
  • https://anthonyzero.github.io/2019/10/01/JVM-%E7%BA%BF%E7%A8%8B%E4%B8%8A%E4%B8%8B%E6%96%87%E7%B1%BB%E5%8A%A0%E8%BD%BD%E5%99%A8/
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值