Java SPI 机制及其实现

前言

第一次接触 SPI 是在看《Java 核心计算卷》中 JDBC 相关的章节的时候,当时看到说在高版本的 JDBC 中能够省略经过 Class.forName 加载驱动这一步, 由于高版本的 JDBC 能够经过 SPI 机制自动加载注册驱动。html

当时看到的时候感受很惊喜,终于不用写那又臭又长的 try-catch 了。java

后来在阅读源码的过程当中又发现 Spring 中也实现了相似于 Java SPI 机制的功能,研究了一下后发现 SPI 机制不管是在使用上仍是在实现上,都是很简单的。mysql

因此,我以为,能够整一篇博客总结一下。git

隐藏内容

上一次写博客仍是 6 月 22 号,断更了 100 多天,感受有点手生 @_@github

ServiceLoader

SPI 的全称为 (Service Provider Interface),是 JDK 内置的一种服务提供发现机制。主要由工具类 java.util.ServiceLoader 提供相应的支持。spring

其中的两个主要角色为:sql

  • Service - 服务,一般为一个接口或一个抽象类,具体类虽然也能够,可是通常不建议那样作
  • Service Provider - 服务提供者,服务的具体实现类

使用时,须要在 META-INF/services 下建立和服务的 全限定名 相同的文件,而后在该文件中写入 服务提供者 的全限定名,能够用 # 做为注释。好比说, 咱们能够在文件 mysql-connector-java/META-INF/services/java.sql.Driver 中发现以下内容:json

com.mysql.jdbc.Driver
com.mysql.fabric.jdbc.FabricMySQLDriver
复制代码

而后,就能够经过 ServiceLoader 来获取这些服务提供者。因为 ServiceLoader 并无提供直接获取服务提供者的方法,所以,只能经过迭代的方式获取:api

ServiceLoader<Service> loader = ServiceLoader.load(Service.class);

for (Service service : loader) {
  // ...
}
复制代码

能够看到,ServiceLoader 的使用仍是很简单的,更多的和 ServiceLoader 相关的内容能够看一下官方文档:ServiceLoader (Java Platform SE 8 )oracle

JDBC 中的使用

若是要找一个使用了 SPI 机制的例子的话,最直接的就是 JDBC 中经过 SPI 的方式加载驱动了,这里能够看一下 JDBC 的使用方式:

public class DriverManager {
  static {
    loadInitialDrivers();
  }

  private static void loadInitialDrivers() {
    AccessController.doPrivileged(new PrivilegedAction<Void>() {
        public Void run() {

          ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
          Iterator<Driver> driversIterator = loadedDrivers.iterator();

          try{
            while(driversIterator.hasNext()) {
              driversIterator.next();
            }
          } catch(Throwable t) {
            // Do nothing
          }
          return null;
        }
      });
  }
}
复制代码

经过上面的简化事后的代码能够发现,在加载 DriverManager 这个类的时候就会经过静态初始化代码块调用执行 loadInitialDrivers 方法,而这个方法会经过 ServiceLoader 加载全部的 Driver 提供者。

而在相应的 Driver 提供类中,好比类 com.mysql.jdbc.Driver 中就存在以下形式的代码:

static {
  try {
    java.sql.DriverManager.registerDriver(new Driver());
  } catch (SQLException E) {
    throw new RuntimeException("Can't register driver!");
  }
}
复制代码

是否是很简单?加载 DriverManager 的时候经过 SPI 机制加载各个 Driver,而后各个 Driver 又在它们本身的静态初始化代码块中将本身注册到 DriverManager。

更多的使用场景

经过 JDBC 中 SPI 机制的使用能够发现,要使用 SPI 的话仍是很简单的,那么,咱们能够在什么地方使用 SPI 呢?

因为 SPI 机制的限制,单个 ServiceLoader 只能加载单个类型的 Service,同时还必须建立相应的文件放到 META-INF/services 目录下,所以,使用场景最好就是相似 JDBC 中这种, 能够经过单个对象来访问其余服务提供者的场景,即:可使用 门面模式 的场景。

好比说,如今 Java 中存在很多经常使用的 JSON 库,好比 Gson、FastJSON、Jackson 等,这些库在使用时均可以经过简单的封装来知足大部分的需求,那么, 咱们就能够考虑经过 SPI 机制来实现一个这些 JSON 库的门面,将 JSON 的处理下放到 Service Provider 来完成,而咱们经过门面来使用这些服务。

这样一来,咱们一方面能够提供本身的默认实现,也能够留出扩展的接口,也就不须要本身手动去加载那些实现了。

实现原理

SPI 不只在使用上很简单,它的实现原理也很简单,关键就在 ClassLoader.getResources 这个方法上,SPI 加载服务的方式就是经过 ClassLoader.getResources 方法找到 META-INF/services 目录下的相应文件, 而后解析文件获得服务提供者的类名。

最后经过 Class.forName() -> clazz.newInstance() 获得实例返回。

很是简单且直白的实现方式,比较值得注意的就是 ClassLoader.getResources 方法的使用了,好比,你能够在一个 Spring 项目下执行以下代码:

public class Test {
  public static void main(String[] args) throws Exception {
    Enumeration<URL> urls = Test.class.getClassLoader().getResources("META-INF/spring.factories");
    while (urls.hasMoreElements()) {
      System.out.println(urls.nextElement());
    }
  }
}
复制代码

这个就是 Spring 中经过 SpringFactoriesLoader 来加载相关的类的起点。

SpringFactoriesLoader

SpringFactoriesLoader 是 Spring 中十分重要的一个扩展机制之一,它的使用方式和实现原理和 SPI 十分类似,只不过,提供了更增强大的功能。

和 SPI 不一样,因为 SpringFactoriesLoader 中的配置文件格式是 properties 文件,所以,不须要要像 SPI 中那样为每一个服务都建立一个文件, 而是选择直接把全部服务都扔到 META-INF/spring.factories 文件中。

好比,spring-boot-autoconfigure 中的部份内容:

# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer

# Auto Configuration Import Listeners
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener

# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnClassCondition

# ...
复制代码

更多的使用能够参考:SpringFactoriesLoader (Spring Framework 5.2.0.RELEASE API)

结语

总的来讲,不管是 ServiceLoader 仍是 SpringFactoriesLoader,它们的基本原理都是同样的,都是经过 ClassLoader.getResources 方法找到相应的配置文件, 而后解析文件获得服务提供者的全限定名。

得益于 Java 强大的反射机制,拿到全限定名后基本上就能够随心所欲了 @_@

隐藏内容

简陋的 JSON 门面:DefaultJsonProviderFactory.java

相关文章
相关标签/搜索