文章

Java SPI

Java SPI

Java SPI

SPI 背景

怎么获取接口的实现类?

  1. 全局扫描全部的 Class,然后判断是否实现了某个接口?代价太大,一般不会这么做
  2. 一种合适的方式就是使用配置文件,把实现类名配置在某个地方,然后读取这个配置文件,获取实现类名。JDK 给我们提供的 ServiceLoader 就是这种方式,通过读取 resources/META-INFO/services 的配置。

SPI 介绍

SPI,全称 Service Provider Interfaces,服务提供接口。是 Java 提供的一套供第三方实现或扩展使用的技术体系。主要通过解耦服务具体实现以及服务使用,使得程序的可扩展性大大增强,甚至可插拔。
基于服务的注册与发现机制,服务提供者向系统注册服务,服务使用者通过查找发现服务,可以达到服务的提供与使用的分离,甚至完成对服务的管理。

完成分离后的服务,使得服务提供方的修改或替换,不会给服务使用方带来代码上的修改,基于面向接口的服务约定,提供方和使用方各自直接面向接口编程,而不用关注对方的具体实现。同时,服务使用方使用到服务时,也才会真正意义上去发现服务,以完成服务的初始化,形成了服务的动态加载。
y3vlf

ServiceLoader

JDK 中,基于 SPI 的思想,提供了默认具体的实现,ServiceLoader。利用 JDK 自带的 ServiceLoader,可以轻松实现面向服务的注册与发现,完成服务提供与使用的解耦。

ServiceLoader 使用

1
2
3
4
1. 创建一个接口文件
2. 在resources资源目录下创建`META-INF/services`文件夹
3. 在services文件夹中创建文件,以接口全名命名
4. 创建接口实现类
  1. 定义接口 me.hacket.javalib.IMyServiceProvider
1
2
3
4
package me.hacket.javalib;
public interface IMyServiceProvider {
    String getName();
}
  1. 在 resources 资源目录下创建 META-INF/services 文件夹,以接口全名命名,并将实现类的全路径配置进去
1
2
me.hacket.javalib.MyServiceProviderImpl1
me.hacket.javalib.MyServiceProviderImpl2
  1. 接口的实现类
1
2
3
4
5
6
7
8
9
10
11
12
13
public class MyServiceProviderImpl1 implements IMyServiceProvider {
    @Override
    public String getName() {
        return "name:ProviderImpl1";
    }
}

public class MyServiceProviderImpl2 implements IMyServiceProvider {
    @Override
    public String getName() {
        return "name:ProviderImpl2";
    }
}
  1. 测试
1
2
3
4
5
6
7
8
public class TestSPIClass {
    public static void main(String[] args) {
        ServiceLoader<IMyServiceProvider> serviceLoader = ServiceLoader.load(IMyServiceProvider.class);
        for (IMyServiceProvider item : serviceLoader) {
            System.out.println(item.getName() + ": " + item.hashCode());
        }
    }
}

ServiceLoader 原理

ServiceLoader#load 开始分析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static <S> ServiceLoader<S> load(Class<S> service) {
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    return ServiceLoader.load(service, cl);
}
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;
    reload();
}
public void reload() {
    providers.clear();
    lookupIterator = new LazyIterator(service, loader);
}

最后调用的是 reload 方法,清空 providers,初始化一个 LazyIterator

然后一般会通过 ServiceLoader 对象遍历,那么就会调用其 iterator 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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 各个方法,从前面我们知道这是一个 LazyIterator。

遍历时会通过 hasNext 判断是否还有下一个元素,next 获取下一个元素。

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
private class LazyIterator implements Iterator<S> {

    Class<S> service;
    ClassLoader loader;
    Enumeration<URL> configs = null;
    Iterator<String> pending = null;
    String nextName = null;

    private LazyIterator(Class<S> service, ClassLoader loader) {
        this.service = service;
        this.loader = loader;
    }

    private boolean hasNextService() {
        if (nextName != null) {
            return true;
        }
        if (configs == null) { // 枚举,就是META-INF/services/接口全名配置的接口所有实现类的全路径
            try {
                // META-INF/services/接口全名
                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;
    }

    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,
                 // Android-changed: Let the ServiceConfigurationError have a cause.
                 "Provider " + cn + " not found", x);
                 // "Provider " + cn + " not found");
        }
        if (!service.isAssignableFrom(c)) {
            // Android-changed: Let the ServiceConfigurationError have a cause.
            ClassCastException cce = new ClassCastException(
                    service.getCanonicalName() + " is not assignable from " + c.getCanonicalName());
            fail(service,
                 "Provider " + cn  + " not a subtype", cce);
            // 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
    }

    public boolean hasNext() {
        return hasNextService();
    }

    public S next() {
        return nextService();
    }

    public void remove() {
        throw new UnsupportedOperationException();
    }
}
  1. META-INF/services/全路径接口 中配置了接口所有的实现类
  2. 通过反射获取所有的实现类,并缓存起来,下次直接返回

AutoService

https://github.com/google/auto/tree/master/service

1
2
3
4
5
6
7
8
package foo.bar;

import javax.annotation.processing.Processor;

@AutoService(Processor.class)
final class MyProcessor implements Processor {
  // …
}

会自动生成 META-INF/services/javax.annotation.processing.Processor,文件包含

1
foo.bar.MyProcessor

应用案例

Processor

编译时注解,用到了 ServiceLoader,javac 会自动加载找到 Processor

Ref

https://cloud.tencent.com/developer/article/1415083

本文由作者按照 CC BY 4.0 进行授权