乐趣区

关于java:Java-SPI-机制可插拔的奥义所在

大家好,我是小菜。
一个心愿可能成为 吹着牛 X 谈架构 的男人!如果你也想成为我想成为的人,不然点个关注做个伴,让小菜不再孤独!

本文次要介绍 SPI 机制

如有须要,能够参考

如有帮忙,不忘 点赞

微信公众号已开启,菜农曰,没关注的同学们记得关注哦!

咱们上篇文章讲到了 Java 中 Agent 用法,不少小伙伴都感觉该形式比拟偏门,平时开发不罕用(简直没用)。其实不然,不罕用是跟我的项目挂钩,我的项目不罕用不代表该办法机制不罕用,因而很多时候咱们学习不能井底之蛙,认为我的项目中没用到就能够不学,跟着我的项目成长往往不能成长~!

上篇跳转入口:Java 高级用法,写个代理侵入你?

那么这篇咱们将持续讲 Java 中的另一个知识点,也就是 SPI 机制,乍听感觉仍然生疏,这时可别再打退堂鼓!往下看你就会发现原来平时开发中常常看到!

一、SPI

咱们这篇文章以 问题 作为导向,用问题来驱动学习,小菜先抛出几个问题,上面将针对这几个问题进行解释并扩大

  • 什么是 SPI?
  • SPI 和 API 的区别?
  • 平时中有应用到 SPI 吗?

1、什么是 SPI

SPI 是三个单词的缩写 Service Provider Interface,字面意思:服务提供接口。它是 Java 提供的一套用来被第三方实现或者扩大的接口,它能够用来启用框架 扩大和替换 组件。具体作用便是为这些被扩大的 API 寻找服务实现。

而 Java SPI 便是 JDK 内置的一种服务提供发现机制,罕用于创立可扩大、可替换组件的应用程序,是 java 中 模块化 插件化 的要害。

这里咱们提到了两个概念,别离是 模块化 插件化。模块化很好了解,就是将一个我的项目分成多个模块,模块间可能存在相互依赖(也就是通过 maven 的形式),有应用微服务开发的同学就毫不生疏了,如果没有应用微服务开发也不打紧,单体我的项目中为了界定 control,service,repository 层,也会将每个畛域独自提取成模块,而不是以目录的形式~

2、类加载机制

下面咱们曾经说到了 SPI 较为浅显的概念,小菜这里不打算间接深刻 SPI,在深刻 SPI 之前,咱们先理解一下 Java 中的类加载机制。类加载机制可能理论开发中并不会去在意,然而它却无处不在,而这个也是面试的一大热点话题。

在 JVM 中,类加载器默认是应用双亲委派准则,默认的类加载器包含Bootstarp ClassLoaderExtension ClassLoaderSystem ClassLoader(Application ClassLoader),当然可能还有自定义类加载器~ 自定义类加载器能够通过继承 java.lang.classloader 来实现

各个类加载器作用范畴如下:

  • Bootstrap ClassLoader:负责加载 JDK 自带的 rt.jar 包中的类文件,是所有类加载的父类
  • Extension ClassLoader:负责加载 java 的扩大类库从 jre/lib/ectjava.ext.dirs 零碎属性指定的目录下加载类
  • System ClassLoader:负责从 classpath 环境变量中加载类文件

类加载继承关系图如下:

1)双亲委派模型

什么是双亲委派模型?

当一个类加载器收到加载类的工作时,会先交给本人的父加载器去实现,一级一级往上,因而最初都会传递到 Bootstrap ClassLoader 进行加载,只有当父加载器无奈实现加载工作的时候,才会尝试本人进行加载

为什么要这样设计呢?

1、采纳双亲委派准则能够防止雷同类反复加载,每个加载器在进行类加载工作的时候都会委派给本人的父类加载器进行加载,如果父类加载无奈加载才本人进行加载,防止反复加载的场面

2、能够保障类加载的安全性,不论是哪个加载器加载这个类,最终都是委托给顶层的加载器进行加载,保障任何加载器最终失去的都是同一个类对象

加载过程如下:

这样做的缺点?

子类加载器能够应用父类加载器曾经加载过的类,而父类加载器无奈应用子类加载器加载过的类(相似继承的关系)。这里就能够扯到 Java SPI 了,Java 提供了很多服务提供者接口(SPI),它能够容许第三方为这些接口提供实现,比方数据库中的 SPI 服务 – JDBC,这些 SPI 的接口由 Java 外围类提供,实现者的确第三方,这样就会存在问题,提供者由 Bootstrap ClassLoader 加载,而实现者是由第三方自定义类加载器加载,而这个时候顶层类加载就无奈应用子类加载器加载过的类

解决办法

想要解决这个问题就得突破双亲委派准则

能够应用线程上下文类加载器(ContextClassLoader)加载

Java 利用上下文加载器默认是应用 AppClassLoader,想要在父类加载器应用到子类加载器加载的类能够应用 Thread.currentThread().getContextClassLoader()

比方咱们想要加载资源能够应用以下形式:

// 应用线程上下文类加载器加载资源
public static void main(String[] args) throws Exception{
    String name = "java/sql/Array.class";
    Enumeration<URL> urls = Thread.currentThread().getContextClassLoader().getResources(name);
    while (urls.hasMoreElements()) {URL url = urls.nextElement();
        System.out.println(url.toString());
    }
}

3、Java SPI

说完类加载机制,咱们再回到 Java SPI 来,咱们先通过例子相熟下 SPI 的应用形式

应用过程图如下:

更加艰深的了解,SPI 实际上就是一种 策略模式 的实现,基于接口编程再配合上配置文件来读取。这也合乎咱们的编程形式:可插拔~

应用例子如下:

我的项目构造

  • ICustomSvc:服务提供接口(也就是 SPI)
  • CustomSvcOne/CustomSvcTwo:实现者(这里间接在一个我的项目中简略实现,也能够通过 jar 包导入的形式实现)
  • cbuc.life.spi.service.ICustomSvc:配置文件

文件内容

而后咱们启动 CustomTest 查看控制台后果

能够看到是能够加载到咱们的实现类的办法,而这也就意味着曾经实现了 SPI 的性能

1)实现原理

其实咱们下面应用 SPI 的时候能够看到一个要害的类那就是 ServiceLoader,该类位于 java.util 包下,咱们间接点进 load() 办法查看如何调用

点进 load() 办法咱们首席那看到的以下代码

该块代码只是简略的申明了应用线程上下文加载器,咱们持续跟进 ServiceLoader.load(service, cl)

该块代码也没啥内容,申明返回了 ServiceLoader 对象,这个对象有什么文章?咱们能够查看这个类申明

public final class ServiceLoader<S> implements Iterable<S>{}

能够看到这个对象实现了 Iterable 接口,阐明具备迭代的办法,能够猜想这样是为了取出咱们定义 SPI 的所有实现类。

该类的构造函数如下

重点在于 reload() 办法,咱们持续跟进

这里将正文一起截取进去,咱们能够看到这句话 办法将惰性查找实例化,阐明了上述说到实现 Iterable 接口的用途,咱们这里能够先点进 iterator() 办法查看是如何实现的

能够看到有个要害的缓存,该缓存存储 provider, 每次操作的时候都会去该缓存中查找,如果存在则返回,否则采纳 LazyIterator 进行查找,咱们进行进入到 LazyIterator 类中查看如何实现,因为该类代码过长,咱们间接截取要害代码,有趣味的同学能够自行查看残缺代码:

看到该代码的实现登时恍然大悟了,咱们看到了相熟的目录名 META-INF/services/,该代码会去指定目录下获取文件资源,而后通过上传传入的线程上下文类加载器进行类加载,这样子咱们的 SPI 实现类就能够供我的项目应用了~ 看完不得不感叹 妙啊~

到这里为止,咱们就曾经拆解了 JAVA SPI 的应用以及实现原理,看完后是不是感觉该技巧也没有离咱们很远~!

4、小结

应用 Java SPI 机制更好的实现了 可插拔 的开发理念,使得第三方服务模块的拆卸与调用者的业务代码相拆散,也就是 解耦 的概念,咱们应用程序能够依据理论业务须要进行动静插拔。

二、扩大

Spring SPI

当然 SPI 机制不仅仅在 JDK 中实现,咱们日常开发用到的 Spring 以及 Dubbo 框架都有对应的 SPI 机制。在 Spring Boot 中好多配置和实现都有默认的实现,咱们如果想要批改某些配置,咱们只须要在配置文件中写上对应的配置,那么我的项目利用的便是咱们定义的配置内容,而这种形式就是采纳 SPI 实现的。

Java SPI 与 Spring SPI 的区别

  • JDK 应用的加载工具类是 ServiceLoader,而 Spring 应用的是 SpringFactoriesLoader
  • JDK 目录命名形式是META-INF/services/ 提供方接口全类名,而 Spring 应用的是 META-INF/spring-factories

在应用 Spring Boot 中咱们会将想要注入 IOC 容器的类将全类限定名写到 META-INF/spring.factories文件中,在 Spring Boot 程序启动的时候就会由 SpringFactoriesLoader 进行加载,扫描每个 jar 包 class-path 目录下的 META-INF/spring.factories 配置文件,而后解析 properties 文件,找到指定名称的配置后返回

所以说 SPI 在咱们理论开发中随处可见,不止 Spring,比方 JDBC 加载数据库驱动,SLF4J 加载不同提供商的日志实现还有 Dubbo 应用 SPI 的形式实现框架的扩大等等

不要空谈,不要贪懒,和小菜一起做个 吹着牛 X 做架构 的程序猿吧~ 点个关注做个伴,让小菜不再孤独。咱们下文见!

明天的你多致力一点,今天的你就能少说一句求人的话!
我是小菜,一个和你一起变强的男人。 💋
微信公众号已开启,菜农曰,没关注的同学们记得关注哦!

退出移动版