[java学习] JAVA SPI

这一章的复习主要涵盖:

  1. JAVA SPI 的概述
  2. SPI 简易 demo 实现
  3. SPI 源码及实现原理分析

如果有不准确的地方,还望大佬在评论区指出,感激不尽。

文章中的所有的自定义的测试都在自己的 java-review 仓库中,本文对应的链接:

JAVA SPI

SPI 的全称是 Service provider interface

Demo

目录结构

java spi 的固定的目录结构为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# tree src

src
├── main
│   ├── java
│   │   └── org
│   │   └── example
│   │   └── test
│   │   └── javase
│   │   └── spi
│   │   ├── Main.java
│   │   ├── api
│   │   │   └── HelloService.java
│   │   └── impl
│   │   └── HelloServiceImpl.java
│   └── resources
│   └── META-INF
│   └── services
│   └── org.example.test.javase.spi.api.HelloService
└── test
└── java

resource/META-INF/services 目录下,创建一个接口的全类名的纯文本文件,例如这里的: org.example.test.javase.spi.api.HelloService,其内容为实现类的类名,按照行的单位进行排列,例如这里的:org.example.test.javase.spi.impl.HelloServiceImpl

1
2
# cat src/main/resources/META-INF/services/org.example.test.javase.spi.api.HelloService
org.example.test.javase.spi.impl.HelloServiceImpl

示例代码

  1. HelloService 接口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    package org.example.test.javase.spi.api;

    public interface HelloService {

    /**
    * say hello
    */
    void sayHello();

    }
  2. HelloServiceImpl

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    package org.example.test.javase.spi.impl;

    import org.example.test.javase.spi.api.HelloService;s

    public class HelloServiceImpl implements HelloService {

    @Override
    public void sayHello() {
    System.out.println("hello world");
    }
    }
    `
  3. Main

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    package org.example.test.javase.spi;

    import java.util.ServiceLoader;
    import org.example.test.javase.spi.api.HelloService;

    public class Main {

    public static void main(String[] args) {
    ServiceLoader<HelloService> services = ServiceLoader.load(HelloService.class);
    for (HelloService service : services) {
    service.sayHello();
    }
    }

    }

实现原理

java.util.ServiceLoader#load() 方法返回的对象虽然是 ServiceLoader 类型,但其实现的迭代器接口获取迭代对象的时候,就会通过反射创建对应对象实例

调用堆栈

1
2
3
4
5
6
7
8
9
10
`----java.util.ServiceLoader:load()
`----java.util.ServiceLoader:iterator()
`----java.util.ServiceLoader.LazyIterator:hasNextService()
`----java.lang.ClassLoader:getResources()
+----java.util.ServiceLoader:parse()
+----java.util.ServiceLoader:parseLine()

`----java.util.ServiceLoader.LazyIterator:nextService()
+----java.lang.Class:forName()
+----java.lang.Class:cast()

链路分析

  1. ServiceLoader#load 可以手动指定类加载器,默认使用当前线程的类加载器

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // java.util.ServiceLoader

    public static <S> ServiceLoader<S> load(Class<S> service,
    ClassLoader loader) {
    return new ServiceLoader<>(service, loader);
    }

    private ServiceLoader(Class<S> svc, ClassLoader cl) {
    service = Objects.requireNonNull(svc, "Service interface cannot be null");
    loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
    acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
    reload();
    }
  2. 调用 reload 方法清空 providers,并且重置 lookupIterator 为一个新的懒加载迭代器,这一个作为返现迭代器,负责根据上述的目录结构进行查找加载类

    1
    2
    3
    4
    5
    6
    // java.util.ServiceLoader

    public void reload() {
    providers.clear();
    lookupIterator = new LazyIterator(service, loader);
    }
  3. 调用 ServiceLoader 对象实例的 iterator 获取到迭代器,类加载迭代 providers 属性;这里使用匿名内部类 new Iterator<S> 和 LazyIterator 两种迭代器是为了避免类每次调用时的加载而进行的缓存

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    // java.util.ServiceLoader

    public Iterator<S> iterator() {
    return new Iterator<S>() {
    Iterator<Map.Entry<String,S>> knownProviders
    = providers.entrySet().iterator();
    public boolean hasNext() {
    if (knownProviders.hasNext())
    return true;
    return lookupIterator.hasNext();
    }
    public S next() {
    if (knownProviders.hasNext())
    return knownProviders.next().getValue();
    return lookupIterator.next();
    }
    public void remove() {
    throw new UnsupportedOperationException();
    }
    };
    • lookupIterator 查询迭代器用于从配置中加载类

    • ServiceLoader#iterator() 获得的迭代器为 providers 获取的迭代器

      首先会遍历完 providers 中的对象,然后遍历 lookupIterator 延迟加载

  4. LazyIterator lookupIterator 查找迭代器中的 hasNext() 和 next() 方法都使用 PrivilegedAction 对操作进行了封装,保证在开启了权限的时候才会执行

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    // java.util.ServiceLoader.LazyIterator

    public boolean hasNext() {
    if (acc == null) {
    return hasNextService();
    } else {
    PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
    public Boolean run() { return hasNextService(); }
    };
    return AccessController.doPrivileged(action, acc);
    }
    }
    public S next() {
    if (acc == null) {
    return nextService();
    } else {
    PrivilegedAction<S> action = new PrivilegedAction<S>() {
    public S run() { return nextService(); }
    };
    return AccessController.doPrivileged(action, acc);
    }
    }
  5. 通过迭代获取对象,触发类加载机制,hasNextService 和 nextService 是 spi 的核心方法

    1. ServiceLoader#hasNextService 方法中得到的 config 对象是一个 CompoundEnumeration<E> 内置属性 Enumeration<URL>[] enums 迭代器数组,configs 对象通过 CompoundEnumeration#hasMoreElements 方法迭代的时候,会通过下标迭代 enums 属性中的所有成员的所有迭代器,enums[0].next() 保存的就是根据 fullName 获取到的文件 URL

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      // java.util.ServiceLoader.LazyIterator

      private static final String PREFIX = "META-INF/services/";

      private boolean hasNextService() {
      if (nextName != null) {
      return true;
      }
      if (configs == null) {
      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);
      }
      }
      while ((pending == null) || !pending.hasNext()) {
      if (!configs.hasMoreElements()) {
      return false;
      }
      pending = parse(service, configs.nextElement());
      }
      nextName = pending.next();
      return true;
      }
    2. ServiceLoader#parse 方法通过 URL 获取到 stream,按行将其保存到 name 中,返回其迭代器

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      // java.util.ServiceLoader.LazyIterator

      private Iterator<String> parse(Class<?> service, URL u)
      throws ServiceConfigurationError
      {
      InputStream in = null;
      BufferedReader r = null;
      ArrayList<String> names = new ArrayList<>();
      try {
      in = u.openStream();
      r = new BufferedReader(new InputStreamReader(in, "utf-8"));
      int lc = 1;
      while ((lc = parseLine(service, u, r, lc, names)) >= 0);
      } catch (IOException x) {
      fail(service, "Error reading configuration file", x);
      } finally {
      try {
      if (r != null) r.close();
      if (in != null) in.close();
      } catch (IOException y) {
      fail(service, "Error closing configuration file", y);
      }
      }
      return names.iterator();
      }
    3. nextService 方法根据全类名完成类加载后,通过反射创建对象实例并转化为对应的 Class<T> 的泛型类型后返回对象实例

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      // java.util.ServiceLoader.LazyIterator

      private S nextService() {
      if (!hasNextService())
      throw new NoSuchElementException();
      String cn = nextName;
      nextName = null;
      Class<?> c = null;
      try {
      c = Class.forName(cn, false, loader);
      } catch (ClassNotFoundException x) {
      fail(service,
      "Provider " + cn + " not found");
      }
      if (!service.isAssignableFrom(c)) {
      fail(service,
      "Provider " + cn + " not a subtype");
      }
      try {
      S p = service.cast(c.newInstance());
      providers.put(cn, p);
      return p;
      } catch (Throwable x) {
      fail(service,
      "Provider " + cn + " could not be instantiated",
      x);
      }
      throw new Error(); // This cannot happen
      }