【基础篇】七、线程上下文类加载器打破双亲委派机制

文章目录

  • 1、SPI机制
  • 2、JDBC案例之SPI机制
  • 3、打破双亲委派机制:线程上下文类加载器
  • 4、打破双亲委派机制:osgi模块化
  • 5、JDK9之后的类加载器
  • 6、小总结

1、SPI机制

SPI,Service Provider Interface,是JDK内置的一种服务提供发现机制。大致流程:

  • 相关组织定义好接口标准并对外提供
  • 第三方去针对接口写实现类,并将信息写在META-INF/services/${interface_name},文件内容是实现类全类名
  • 用户使用JDK提供的ServiceLoader加载(jar里的)实现类

写个小Demo直观感受下SPI,定义对外接口:

public interface SpiInterface {

    void helloSpi();

}

其他类写实现类(这里接口和实现类放同一个jar中了,一般来说写接口和写实现的是两种角色):

public class SpiClass1 implements SpiInterface {

    @Override
    public void helloSpi() {
        System.out.println("hello SpiClass1...");
    }
}
public class SpiClass2 implements SpiInterface {

    @Override
    public void helloSpi() {
        System.out.println("hello SpiClass2...");
    }
}

写文件:

在这里插入图片描述

//内容
com.plat.impl.SpiClass1
com.plat.impl.SpiClass2

测试模块中引入上面的这个jar:

<dependency>
   <groupId>com.plat</groupId>
   <artifactId>demo</artifactId>
   <version>0.0.1</version>
</dependency>

尝试加载:

public class SpiTest {

    public static void main(String[] args) {
        ServiceLoader<SpiInterface> serviceLoader = ServiceLoader.load(SpiInterface.class);
        for (SpiInterface spiClass : serviceLoader) {
            System.out.println(spiClass);
            spiClass.helloSpi();
        }
    }
}

成功获取到接口的两个实现类:

【基础篇】七、线程上下文类加载器打破双亲委派机制_第1张图片

2、JDBC案例之SPI机制

JDBC使用DriverManager类来完成不同数据库厂商的驱动注册:

/获取连接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/testDB","root","95279527");

DriverManager位于JDK的rt.jar包,由启动类加载器去加载,而引入的第三方jar包归应用程序类加载器管。DriverManager又如何知道jar中要加载哪个驱动类?看下DriverManager类的源码,其中有一段静态代码块:

【基础篇】七、线程上下文类加载器打破双亲委派机制_第2张图片

往下跟进loadInitialDrivers方法:

【基础篇】七、线程上下文类加载器打破双亲委派机制_第3张图片

断点看下值,成功拿到MySQL驱动的类名:

【基础篇】七、线程上下文类加载器打破双亲委派机制_第4张图片

即SPI机制去加载这个接口的实现类,然后用迭代器去遍历所有jar包种满足条件的类名。而MySQL驱动的jar中已按SPI流程写好了实现类和名为java.sql.Driver的文件:

【基础篇】七、线程上下文类加载器打破双亲委派机制_第5张图片

到此,整个JDBC的流程为:

【基础篇】七、线程上下文类加载器打破双亲委派机制_第6张图片

拿到jar包中驱动类的类名了,但这个类得靠应用程序类加载器去加载,那应用程序类加载器从哪儿来? ⇒ SPI过程能拿到应用程序类加载器,是因为SPI使用了线程上下文中保存的类加载器:

【基础篇】七、线程上下文类加载器打破双亲委派机制_第7张图片

而线程上下文类加载器通常为应用程序类加载器(一个线程创建完之后,JVM底层会将应用程序类加载器放到线程上下文中):

//获取线程上下文类加载器
ClassLoader c = Thread.currentThread.getContextClassLoader();
//设置线程上下文的类加载器为自定义类加载器
Thread.currentThread().setContextClassLoader(new BreakClassLoader1());
//com.plat.broken.BreakClassLoader1@6537cf78
System.out.println(Thread.currentThread().getContextClassLoader());

ps:对于MySQL的驱动类,其加载过程中会自动注册驱动:

【基础篇】七、线程上下文类加载器打破双亲委派机制_第8张图片

3、打破双亲委派机制:线程上下文类加载器

JDBC案例中,启动类加载器要委托它的下级应用程序类加载器去加载一个类,与双亲委派机制的自下而上委派不符合,因此才说打破了双亲委派机制,但其实这两块都各自满足双亲委派机制,单看DriverManager,其就应该由启动类加载器干,但看Jar中的MySQL驱动类,其也本该由应用程序类加载器干。

【基础篇】七、线程上下文类加载器打破双亲委派机制_第9张图片

4、打破双亲委派机制:osgi模块化

历史上,OSGi模块化框架。它存在同级之间的类加载器的委托加载:
【基础篇】七、线程上下文类加载器打破双亲委派机制_第10张图片

5、JDK9之后的类加载器

JDK8及之前的版本中,扩展类加载器和应用程序类加载器的源码位于rt.jar包中的sun.misc.Launcher.java。
【基础篇】七、线程上下文类加载器打破双亲委派机制_第11张图片
这两个类加载器都继承自URLClassLoader,URLClassLoader是根据某一个特定的目录去找到jar包以及jar包中的字节码文件,所以,JDK8及以前,是按照jar包的位置去加载字节码文件的。JDK9引入了module的概念,之前的包放入到了jmods目录下的jmod文件中,对应的,类加载器也由原来的从jar包中加载改为了去jmod文件中加载:

【基础篇】七、线程上下文类加载器打破双亲委派机制_第12张图片

类加载器的变化:

  • 启动类加载器使用Java编写,位于jdk.internal.loader.ClassLoaders类中,但java代码中获取启动类加载器对象依然为null
    【基础篇】七、线程上下文类加载器打破双亲委派机制_第13张图片

  • 扩展类加载器被替换成了平台类加载器(Platform Class Loader),平台类加载器遵循模块化方式加载字节码文件,之前扩展类加载器继承自URLClassLoader,现在继承自BuiltinClassLoader
    【基础篇】七、线程上下文类加载器打破双亲委派机制_第14张图片

6、小总结

【基础篇】七、线程上下文类加载器打破双亲委派机制_第15张图片
【基础篇】七、线程上下文类加载器打破双亲委派机制_第16张图片
【基础篇】七、线程上下文类加载器打破双亲委派机制_第17张图片
【基础篇】七、线程上下文类加载器打破双亲委派机制_第18张图片

你可能感兴趣的:(JVM,jvm,java)