SPI

0x01 What Is SPI

Service Provider Interface:是JDK内置的一种服务提供发现机制,是Java提供的一套用来被第三方实现或扩展的接口,它可以用来启动框架扩展和替换组件。SPI是为这些被扩展的API寻找服务实现。不同厂商可以针对同一接口做出不同的实现。

  • API:实现方制定接口并完成对接口的实现,调用方仅仅依赖接口调用,无权选择不同实现。API被应用开发人员使用

  • SPI:应用方制定接口规范,提供给外部来实现,调用方在调用时则选择自己需要的外部实现。SPI被框架扩展人员使用

java.sql.Driver接口

image-20230123013436497

0x02 Best Practice

接口:

实现类:

在classpath下面创建目录META-INF/services/,创建一个名字为上面接口全限定名的文件

image-20230123012616159

测试:

0x03 SPI + JDBC

JDBC连接数据库经常是下面的写法,首先是注册驱动,告诉程序使用哪种数据库

驱动jar包下的META-INF/services文件夹中的java.sql.Driver文件(文件名为SPI接口)中已经把Driver类路径记录下来了

若程序中没有注册驱动,会先读取这个文件,自动注册驱动。

这里就用到了SPI技术,它通过在ClassPath路径下的META-INF/services文件夹查找文件,实现类自动加载

image-20230123013953375

Java类加载过程中,有一步是初始化(Initialization),初始化阶段会执行被加载类的Static Blocks。

image-20230123014138191

注册驱动时执行了Class.forName("com.mysql.cj.jdbc.Driver")Class.forName()默认会进行类的初始化

com.mysql.cj.jdbc.Driver的静态代码中调用了DriverManagerregisterDriver静态方法

因此JVM会尝试去加载DriverManager类,进而执行DriverManager的静态代码,调用类中的loadInitialDrivers方法

注意到ServiceLoader.load(Driver.class)进行了类加载

image-20230123014749261
image-20230123014758193
image-20230123014806266

最后得到的driversIterator就是LazyIterator

懒迭代器,顾名思义,需要用的时候才迭代

hasNext => hasNextService

image-20230123122535820

ServiceLoader<S>类有常量属性PREFIX = "META-INF/services/"

service.getName()获取接口全类名,拼接得到SPI文件名,读取得到实现类的类名

调用其next方法 => nextService方法

  • Thread.currentThread().getContextClassLoader();获取类加载器

  • 扩展的实现类通过c = Class.forName(cn, false, loader);获取

Class.forName()默认使用调用者的ClassLoader,我们是在DriverManager类里调用ServiceLoader的,所以调用类也就是DriverManager,它的加载器是Bootstrap ClassLoader。我们知道Bootstrap ClassLoader加载rt.jar包下的所有类,要用Bootstrap ClassLoader去加载用户自定义的类是违背双亲委派的,所以使用Thread.currentThread().getContextClassLoader去指定AppClassLoaderServiceLoader.load传入的)

查看ClassPath下有那些JDBC Driver

image-20230123015151674

0x04 JDBC Driver后门

现在我们尝试自己编写一个后门驱动jar包,让用户引入后门jar包,在建立JDBC连接时,触发执行命令

jar中的META-INF下需要有services文件夹

services文件夹下为java.sql.Driver,内容为com.mysql.fake.jdbc.ShellDriver

这是为了让SPI找到我们要加载的类ShellDriver

image-20230123015541428

执行JDBC连接数据库操作,成功弹出计算器

注意:Class.forName(className, initia, currentLoader)

第二个参数指定是否初始化,nextService中第二个参数为false。因此不会执行待加载类的静态代码。实际上是后面对加载的Class进行newInstance时才执行了静态代码

image-20230123015747559

Last updated

Was this helpful?