SPI,Service Provider Interface,是JDK内置的一种服务提供发现机制。大致流程:
META-INF/services/${interface_name}
,文件内容是实现类全类名写个小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();
}
}
}
成功获取到接口的两个实现类:
JDBC使用DriverManager类来完成不同数据库厂商的驱动注册:
/获取连接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/testDB","root","95279527");
DriverManager位于JDK的rt.jar包,由启动类加载器去加载,而引入的第三方jar包归应用程序类加载器管。DriverManager又如何知道jar中要加载哪个驱动类?看下DriverManager类的源码,其中有一段静态代码块:
往下跟进loadInitialDrivers方法:
断点看下值,成功拿到MySQL驱动的类名:
即SPI机制去加载这个接口的实现类,然后用迭代器去遍历所有jar包种满足条件的类名。而MySQL驱动的jar中已按SPI流程写好了实现类和名为java.sql.Driver的文件:
到此,整个JDBC的流程为:
拿到jar包中驱动类的类名了,但这个类得靠应用程序类加载器去加载,那应用程序类加载器从哪儿来? ⇒ SPI过程能拿到应用程序类加载器,是因为SPI使用了线程上下文中保存的类加载器:
而线程上下文类加载器通常为应用程序类加载器(一个线程创建完之后,JVM底层会将应用程序类加载器放到线程上下文中):
//获取线程上下文类加载器
ClassLoader c = Thread.currentThread.getContextClassLoader();
//设置线程上下文的类加载器为自定义类加载器
Thread.currentThread().setContextClassLoader(new BreakClassLoader1());
//com.plat.broken.BreakClassLoader1@6537cf78
System.out.println(Thread.currentThread().getContextClassLoader());
ps:对于MySQL的驱动类,其加载过程中会自动注册驱动:
JDBC案例中,启动类加载器要委托它的下级应用程序类加载器去加载一个类,与双亲委派机制的自下而上委派不符合,因此才说打破了双亲委派机制,但其实这两块都各自满足双亲委派机制,单看DriverManager,其就应该由启动类加载器干,但看Jar中的MySQL驱动类,其也本该由应用程序类加载器干。
历史上,OSGi模块化框架。它存在同级之间的类加载器的委托加载:
JDK8及之前的版本中,扩展类加载器和应用程序类加载器的源码位于rt.jar包中的sun.misc.Launcher.java。
这两个类加载器都继承自URLClassLoader,URLClassLoader是根据某一个特定的目录去找到jar包以及jar包中的字节码文件,所以,JDK8及以前,是按照jar包的位置去加载字节码文件的。JDK9引入了module的概念,之前的包放入到了jmods目录下的jmod文件中,对应的,类加载器也由原来的从jar包中加载改为了去jmod文件中加载:
类加载器的变化:
启动类加载器使用Java编写,位于jdk.internal.loader.ClassLoaders类中,但java代码中获取启动类加载器对象依然为null
扩展类加载器被替换成了平台类加载器(Platform Class Loader),平台类加载器遵循模块化方式加载字节码文件,之前扩展类加载器继承自URLClassLoader,现在继承自BuiltinClassLoader