前言
主要介绍下 Java
中的 SPI
机制 。
Springboot
的 SPI
机制 咱们在下文 Springboot
的自动装配中再说~ 😝 嘿嘿
至于 [[ dubbo
的 SPI
机制]],还没时间深入了解,简单知道了它的 SPI
的自适应扩展机制,以及下面这些扩展~(超级多扩展的~)🐷
冲冲冲!
什么是SPI呢?
SPI
,全称为Service Provider Interface
,是一种服务发现机制。它通过在ClassPath
路径下的META-INF/services
文件夹查找文件,自动加载文件里所定义的类(定义多少,加载多少)它是
Java
提供的一套用来被第三方实现或者扩展的接口
JAVA SPI 实践
步骤:
- 先定义一个接口和它的实现类
- 再在
resources
下添加META-INF/services
这两个文件夹 在该文件夹中新建一个普通文本,名字为 接口的全名 ,并将 接口的实现类全名 添加到文本中,多个的话进行换行操作
- 最后在代码中调用
ServiceLoader.load
方法即可实现
项目结构图
spi文件内容
源码如下
接口
public interface IJava4ye { void say(); } public class Java4yeImpl implements IJava4ye{ @Override public void say() { System.out.println(" 【java4ye】 杰伦 杰伦!"); } } 复制代码
测试类
public class SpiTest { @Test public void spiTest(){ ServiceLoader<IJava4ye> load = ServiceLoader.load(IJava4ye.class); for (IJava4ye iJava4ye : load) { iJava4ye.say(); } } } 复制代码
结果
是不是非常的方便,还有 IOC
的感觉 哈哈😄
JDBC实例
那么实践完,我们再来看看 JDBC
这个小案例😝
可以看到我们使用的 MySQL
的 JDBC
包中就有这么一个例子~🐷
而这个 java.sql.Driver
文件中就定义了这么一个实现类~👇
com.mysql.cj.jdbc.Driver 复制代码
Driver源码
可以发现这里有一个静态代码块,随着类被加载,它会直接往 DriverManager
中注册这个 Driver
驱动器 。
那有什么作用呢?
简单看下这个 JDBC
的写法 ,可以发现,之前我们都是要手动去写这个
Class.forName
来加载这个驱动器,但是用了这个 SPI
后,那不就可以省略这行代码了吗~😄 哈哈🐷
以后我们都不用管厂商定义了啥,直接使用 DriverManager.getConnection(url, username, password);
就可以获取到这个连接了,代码中就不会出现这种硬编码~ 灵活多了🐷 (当然不开发框架或者自己多折腾好像也用不上呀😝 哈哈哈)
JAVA SPI 原理探索
疑惑
不知道小伙伴们实践完会不会有点小疑惑,这个 SPI
一定要用 ServiceLoader.load
方法去加载的吗?
4ye 也不知道是不是 哈哈,但是 DriverManager
中就是这么使用的😝
详细请看下图👇
破坏双亲委派机制
我们都知道在 Java 中有这个双亲委派机制 ,像上面 DriverManager
这种做法,其实是破坏了这个 双亲委派机制 。
因为这个实现类是属于第三方类库 mysql
的
而这个 Driver
接口是在jdk
自身的 lib
--》 rt.jar
中的
它是由 根类加载器BootstrapClassloader
加载的, 而它的实现类却是在第三方的包中,这样子 BootstrapClassloader
是无法加载的。
那么怎样才能加载第三方包中的类呢?
看看 ServiceLoader
这个类我们就清楚啦~ 😝
ServiceLoader源码
可以发现这里有很显眼的一句话
ClassLoader cl = Thread.currentThread().getContextClassLoader(); 复制代码
通过 当前线程的上下文类加载器 去加载的!
那么这个 当前线程的上下文类加载器 中又是用了哪个 classLoader
呢?
在源码中一番搜索之后,我们可以看到在启动器Launcher
中有这么一段代码,将 AppClassLoader
赋值给 ClassLoader
变量,并将其设置到当前线程中。😋
那么结论也很明显啦,SPI
是通过应用程序类加载器AppClassLoader
去加载第三方包中的类的。
总结
java
提供了这个服务发现机制SPI
,可以方便我们对某个接口进行扩展,实现模块的热插拔,而缺点就是,灵活度不高,配置文件中由多少实现类,都会被加载到内存中,不管有没有使用到~🐷
原理图奉上~