我是靠谱客的博主 谨慎野狼,这篇文章主要介绍[04][02][02] SPI 机制1. 基本概念2. 使用场景3. 使用要求4. JDK SPI 实现5. Spring SPI 实现,现在分享给大家,希望可以做个参考。

文章目录

  • 1. 基本概念
  • 2. 使用场景
  • 3. 使用要求
  • 4. JDK SPI 实现
  • 5. Spring SPI 实现

1. 基本概念

SPI (Service Provider Interface), 是 Java 提供的一套用来被第三方实现或者扩展的 API, 它可以用来启用框架扩展和替换组件

SPI 工作机制

Java SPI 实际上是“基于接口的编程+策略模式+配置文件”组合实现的动态加载机制

2. 使用场景

  • 数据库驱动
  • 日志 SLF4J
  • Spring 使用 SPI
  • Dubbo 使用 SPI, 但对原生 SPI 做了封装, 允许用户扩展实现 Filter 接口

3. 使用要求

要使用 SPI, 需要遵循如下约定

  • SPI 的实现类中必须有一个无参的构造方法
  • 当服务提供者提供了接口的实现, 在 jar 包的 META-INF 目录下创建一个以“接口全限定名”为命名的文件, 内容为实现类的全限定名
  • JDK 中路径时 META-INF/services/
  • Spring 中配置是 META-INF/spring.factories
  • 接口实现类所在的 jar 包放在主程序的 classpath 中
  • 通过 Loader 类将 META-INF 目录下的配置文件加载, 解析文件中的全限定名类名, 将类加载到 JVM
  • JDK 中是使用的是 ServiceLoader 加载 META-INF/services/ 下的文件
  • Spring 中使用的是 SpringFactoriesLoader 加载 META-INF/spring.factories

4. JDK SPI 实现

对数据库驱动源码分析

MySQL 驱动是 om.mysql.cj.jdbc 包下 Driver 实现了接口 JDK 的 java.sql.Driver, 并在 META-INF/services 目录下创建文件名为 java.sql.Driver 的文件, 在文件里填写实现类的全路径信息 com.mysql.cj.jdbc.Driver

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 实现 JDK 的 java.sql.Driver 类 public class Driver extends NonRegisteringDriver implements java.sql.Driver { // SPI 的实现类中必须有一个无参的构造方法 public Driver() throws SQLException { } static { try { DriverManager.registerDriver(new Driver()); } catch (SQLException var1) { throw new RuntimeException("Can't register driver!"); } } }

xx

查看 java.util.ServiceLoader 源码

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public final class ServiceLoader<S> implements Iterable<S> { private static final String PREFIX = "META-INF/services/"; // 代表被加载的类或者接口 private final Class<S> service; // 用于定位,加载和实例化providers的类加载器 private final ClassLoader loader; // 创建ServiceLoader时采用的访问控制上下文 private final AccessControlContext acc; // 缓存providers,按实例化的顺序排列 private LinkedHashMap<String,S> providers = new LinkedHashMap<>(); // 懒查找迭代器 private LazyIterator lookupIterator; ...... }

ServiceLoader 可以跨越 jar 包获取 META-INF/services 目录下的配置文件

复制代码
1
2
3
4
5
6
7
8
9
10
try { String fullName = PREFIX + service.getName(); if (loader == null) configs = ClassLoader.getSystemResources(fullName); else configs = loader.getResources(fullName); } catch (IOException x) { fail(service, "Error locating configuration files", x); }

获取实现类的全类名后, 通过反射方法 Class.forName() 加载类对象

5. Spring SPI 实现

Spring 的 SPI 实现是由 org.springframework.core.io.support 包下的 SpringFactoriesLoader 实现的, 配置文件路径是 META-INF/spring.factories 与 JDK 有差异

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public final class SpringFactoriesLoader { public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"; public static <T> List<T> loadFactories(Class<T> factoryClass, @Nullable ClassLoader classLoader) {} private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) { ... // 扫描 jar 包下 META-INF/spring.factories 文件 Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION)); ... } }

在 Spring 的 spring-data-redis 中对 JDK 和 Spring 都做了 SPI 实现
xx

最后

以上就是谨慎野狼最近收集整理的关于[04][02][02] SPI 机制1. 基本概念2. 使用场景3. 使用要求4. JDK SPI 实现5. Spring SPI 实现的全部内容,更多相关[04][02][02]内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(96)

评论列表共有 0 条评论

立即
投稿
返回
顶部