SPI (Service Provider Interface) 介绍

SPI(Service Provider Interface),是 JDK 内置的一种服务提供发现机制,是一种策略模式的实现方式。

通过在 ClassPath 路径下的 META-INF/services 文件夹查找文件,自动加载文件里所定义的类。

SPI 基础使用

我们在 java 同级的目录下,创建 META-INF/services 目录,然后以接口的全限定名创建文件:com.example.spidemo.SPIService

image-20230219171635202.png

文件中定义了接口的具体实现类:

com.example.spidemo.SpiImpl1
com.example.spidemo.SpiImpl2
com.example.spidemo.SpiImpl3

那么,我们就可以使用 ServiceLoader 获取到这个 SPIService 的具体实现了。

public class SpiDemoTest {
    @Test
  
    public void test() {
        ServiceLoader<SPIService> loader = ServiceLoader.load(SPIService.class);
        for (SPIService spiService : loader) {
            spiService.execute();
        }
    }
}

源码分析

ServiceLoader.load() 的执行过程

核心是,创建了一个 LazyIetrator 对象,真正通过反射创建实现类的方法不在这里。

// 我们没传入 classloader,获取当前线程的 classloader  
public static <S> ServiceLoader<S> load(Class<S> service) {
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    return ServiceLoader.load(service, cl);
}

// new 出来一个 ServiceLoader,两个参数:接口名 & classloader
public static <S> ServiceLoader<S> load(Class<S> service,
                                        ClassLoader loader)
{
    return new ServiceLoader<>(service, loader);
}

// 在构造方法中,会调用 reload 方法
private ServiceLoader(Class<S> svc, ClassLoader cl) {
    service = Objects.requireNonNull(svc, "Service interface cannot be null");
    loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
    // Android-changed: Do not use legacy security code.
    // On Android, System.getSecurityManager() is always null.
    // acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
    reload();
}

// 这里 new 出来一个 LazyIterator
public void reload() {
    providers.clear();
    lookupIterator = new LazyIterator(service, loader);
}

// LazyIterator 只是存储了两个参数,没有实际的逻辑
private LazyIterator(Class<S> service, ClassLoader loader) {
    this.service = service;
    this.loader = loader;
}

缓存机制

ServiceLoader 的 iterator 方法实现如下,这里实现了一个简单的缓存机制。

knownProviders 是缓存 providers 的迭代器,在遍历时,会优先从 knownProviders 中读取缓存,如果读到了,就直接返回;如果没读到,再从 loopupIterator 中去读取。

loopupIterator 就是上面的 LazyIterator

// ServiceLoader.java
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();
        }

    };
}

LazyIterator 的遍历过程

// LazyIterator 本身实现了 Iterator 接口
private class LazyIterator
    implements Iterator<S>
{}
      
// 其 hasNext 方法和 next 方法分别调用了 `hasNextService` 方法和 `next` 方法
public boolean hasNext() {
    // Android-changed: do not use legacy security code
    /* 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() {
    // Android-changed: do not use legacy security code
    /* if (acc == null) { */
        return nextService();
    /*
    } else {
        PrivilegedAction<S> action = new PrivilegedAction<S>() {
            public S run() { return nextService(); }
        };
        return AccessController.doPrivileged(action, acc);
    }
    */
}

hasNextService 方法实现

private boolean hasNextService() {
    // 如果已经有缓存的 nextName 变量,那么直接返回 true
    if (nextName != null) {
        return true;
    }
    // 第一次读取文件的过程
    if (configs == null) {
        try {
            // 读取配置文件
            // PREFIX 的值为:"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 变量中
    nextName = pending.next();
    return true;
}

文件解析的实现

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();
        // 创建 BufferedReader
        r = new BufferedReader(new InputStreamReader(in, "utf-8"));
        int lc = 1;
        // 解析每一行内容,放入 names 数组中
        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);
        }
    }
    // 返回 names 数组的迭代器
    return names.iterator();
}

// 解析每一行内容的代码:
private int parseLine(Class<?> service, URL u, BufferedReader r, int lc,
                      List<String> names)
    throws IOException, ServiceConfigurationError
{
    // 读取到每一行内容
    String ln = r.readLine();
    if (ln == null) {
        return -1;
    }
    // 取第一个 「#」 之前的内容
    int ci = ln.indexOf('#');
    if (ci >= 0) ln = ln.substring(0, ci);
    ln = ln.trim();
    int n = ln.length();
    if (n != 0) {
        // 异常检查:不能包含空格和 '\t'
        if ((ln.indexOf(' ') >= 0) || (ln.indexOf('\t') >= 0))
            fail(service, u, lc, "Illegal configuration-file syntax");
        // 异常检查:检查每一个字符是否都合法
        int cp = ln.codePointAt(0);
        if (!Character.isJavaIdentifierStart(cp))
            fail(service, u, lc, "Illegal provider-class name: " + ln);
        for (int i = Character.charCount(cp); i < n; i += Character.charCount(cp)) {
            cp = ln.codePointAt(i);
            if (!Character.isJavaIdentifierPart(cp) && (cp != '.'))
                fail(service, u, lc, "Illegal provider-class name: " + ln);
        }
        // 将类名添加到 names 数组中
        if (!providers.containsKey(ln) && !names.contains(ln))
            names.add(ln);
    }
    // 返回下一行行号
    return lc + 1;
}

nextService 实现

private S nextService() {
    if (!hasNextService())
        throw new NoSuchElementException();
    // 将 nextName 读取到临时变量
    String cn = nextName;
    // 读取后,清楚 nextName 的值
    nextName = null;
    Class<?> c = null;
    try {
        // 通过反射,找到 cn 对应的 class 对象
        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");
    }
    // 判断 c 这个类是不是 service 接口的一个实现类,如果不是,报错
    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 {
        // 实例化实现对象,并强转成 service 类型
        // 这里 S 是泛型,其实指的就是 service 的类型
        S p = service.cast(c.newInstance());
        // 缓存到 LinkedHashMap 中
        providers.put(cn, p);
        // 返回
        return p;
    } catch (Throwable x) {
        fail(service,
             "Provider " + cn + " could not be instantiated",
             x);
    }
    throw new Error();          // This cannot happen
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,393评论 5 467
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,790评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,391评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,703评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,613评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,003评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,507评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,158评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,300评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,256评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,274评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,984评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,569评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,662评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,899评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,268评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,840评论 2 339

推荐阅读更多精彩内容